[med-svn] [praat] 01/08: Imported Upstream version 5.3.82

Rafael Laboissière rlaboiss-guest at moszumanska.debian.org
Thu Aug 7 11:45:37 UTC 2014


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

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

commit 6a637f2816a8ad1cbe30f5ab9f9a3177baa26513
Author: Rafael Laboissiere <rafael at laboissiere.net>
Date:   Wed Aug 6 10:07:15 2014 -0300

    Imported Upstream version 5.3.82
---
 EEG/EEG.cpp                                        |  593 +-
 EEG/EEG.h                                          |   32 +-
 EEG/EEGWindow.cpp                                  |   30 +-
 EEG/EEGWindow.h                                    |    6 +-
 EEG/EEG_def.h                                      |   32 +-
 EEG/ERP.cpp                                        |   53 +-
 EEG/ERP.h                                          |   27 +-
 EEG/ERPTier.cpp                                    |  275 +-
 EEG/ERPTier.h                                      |   27 +-
 EEG/ERPTier_def.h                                  |   28 +-
 EEG/ERPWindow.cpp                                  |   91 +-
 EEG/ERPWindow.h                                    |   12 +-
 EEG/ERPWindow_prefs.h                              |    3 +-
 EEG/ERP_def.h                                      |   15 +-
 EEG/praat_EEG.cpp                                  |  235 +-
 LPC/Cepstrogram.cpp                                |   19 +-
 LPC/Cepstrogram.h                                  |    2 +-
 LPC/Cepstrum.cpp                                   |   20 +-
 LPC/LPC.cpp                                        |    4 +-
 LPC/Sound_and_LPC_robust.cpp                       |    4 +-
 LPC/Sound_and_LPC_robust.h                         |    2 +-
 LPC/manual_LPC.cpp                                 |   75 +-
 LPC/praat_LPC_init.cpp                             |   43 +-
 artsynth/Art_Speaker.cpp                           |    4 +-
 artsynth/Delta.cpp                                 |   10 +-
 contrib/ola/KNN.cpp                                |    2 +-
 dwsys/Eigen.cpp                                    |    4 +-
 dwsys/Graphics_extensions.cpp                      |   24 +-
 dwsys/Graphics_extensions.h                        |    6 +-
 dwsys/NUM2.cpp                                     |   13 +-
 dwsys/NUM2.h                                       |    3 +-
 dwsys/SVD.cpp                                      |   30 +-
 dwtest/KlattGrid_test.praat                        |  524 +-
 dwtest/test_Table_extractMahalanobisWhere.praat    |   31 +
 dwtools/DTW.cpp                                    |    2 +-
 dwtools/DataModeler.cpp                            | 1884 ++++++
 dwtools/DataModeler.h                              |  209 +
 dwtools/DataModeler_def.h                          |   46 +
 dwtools/EEG_extensions.cpp                         |   80 +-
 dwtools/EditDistanceTable.cpp                      |   88 +-
 dwtools/EditDistanceTable_def.h                    |   10 +-
 dwtools/GaussianMixture.cpp                        |    8 +-
 dwtools/ICA.cpp                                    |   37 +-
 dwtools/ICA.h                                      |   14 +-
 dwtools/KlattGrid.cpp                              |  114 +-
 dwtools/KlattGrid.h                                |   11 +-
 dwtools/Ltas_extensions.cpp                        |    2 +-
 dwtools/Ltas_extensions.h                          |    2 +-
 dwtools/MDS.cpp                                    |    6 +-
 dwtools/Makefile                                   |    9 +-
 dwtools/Polynomial.cpp                             |   10 +-
 dwtools/SSCP.cpp                                   |   60 +-
 dwtools/SSCP.h                                     |    6 +-
 dwtools/Sound_extensions.cpp                       |    8 +-
 dwtools/SpeechSynthesizer_and_TextGrid.cpp         |    8 +-
 dwtools/TableOfReal_and_Permutation.cpp            |    5 +-
 dwtools/Table_extensions.cpp                       |  542 +-
 dwtools/Table_extensions.h                         |   24 +-
 dwtools/TextGrid_extensions.cpp                    |    9 +-
 dwtools/manual_BSS.cpp                             |   74 +-
 dwtools/manual_DataModeler.cpp                     |   59 +
 dwtools/manual_HMM.cpp                             |   10 +-
 dwtools/manual_KlattGrid.cpp                       |   28 +-
 dwtools/manual_MDS.cpp                             |   32 +-
 dwtools/manual_dwtools.cpp                         |  674 +-
 dwtools/octave-workspace                           |  Bin 0 -> 43 bytes
 dwtools/praat_BSS_init.cpp                         |   34 +-
 dwtools/praat_DataModeler_init.cpp                 | 1401 ++++
 dwtools/praat_David_init.cpp                       |  297 +-
 dwtools/praat_KlattGrid_init.cpp                   |  160 +-
 external/flac/READ_ME.TXT                          |   23 +-
 external/flac/flac_md5.c                           |   22 +-
 external/glpk/glplpx02.c                           |    1 +
 external/portaudio/Makefile                        |    4 +-
 external/portaudio/READ_ME.TXT                     |   41 +-
 external/portaudio/pa_allocation.h                 |    8 +-
 external/portaudio/pa_asio.cpp                     | 4251 ++++++++++++
 external/portaudio/pa_asio.h                       |  150 +
 external/portaudio/pa_converters.c                 |  130 +-
 external/portaudio/pa_cpuload.c                    |    6 +-
 external/portaudio/pa_debugprint.c                 |   64 +-
 external/portaudio/pa_dither.c                     |   31 +-
 external/portaudio/pa_dither.h                     |   18 +-
 external/portaudio/pa_endianness.h                 |   30 +-
 external/portaudio/pa_front.c                      |   75 +-
 external/portaudio/pa_hostapi.h                    |  143 +-
 external/portaudio/pa_jack.c                       | 1769 +++++
 external/portaudio/{pa_trace.h => pa_jack.h}       |   68 +-
 external/portaudio/pa_linux_alsa.c                 | 2037 ++++--
 external/portaudio/pa_linux_alsa.h                 |   20 +-
 external/portaudio/pa_linux_asihpi.c               | 2893 +++++++++
 external/portaudio/pa_mac_core.c                   | 1116 +++-
 external/portaudio/pa_mac_core.h                   |   64 +-
 external/portaudio/pa_mac_core_blocking.c          |   36 +-
 external/portaudio/pa_mac_core_blocking.h          |    2 +-
 external/portaudio/pa_mac_core_internal.h          |   44 +-
 external/portaudio/pa_mac_core_utilities.c         |  391 +-
 external/portaudio/pa_mac_core_utilities.h         |   37 +-
 external/portaudio/pa_memorybarrier.h              |  128 +
 external/portaudio/pa_process.c                    |  267 +-
 external/portaudio/pa_process.h                    |   18 +-
 external/portaudio/pa_ringbuffer.c                 |  206 +-
 external/portaudio/pa_ringbuffer.h                 |  126 +-
 external/portaudio/pa_stream.c                     |   10 +-
 external/portaudio/pa_stream.h                     |    8 +-
 external/portaudio/pa_trace.c                      |  147 +-
 external/portaudio/pa_trace.h                      |   50 +-
 external/portaudio/pa_unix_hostapis.c              |   14 +-
 external/portaudio/pa_unix_oss.c                   |  214 +-
 external/portaudio/pa_unix_util.c                  |   70 +-
 external/portaudio/pa_util.h                       |    8 +-
 external/portaudio/pa_win_coinitialize.c           |  144 +
 external/portaudio/pa_win_coinitialize.h           |   94 +
 external/portaudio/pa_win_ds.c                     | 1544 +++--
 external/portaudio/pa_win_ds.h                     |   31 +-
 external/portaudio/pa_win_ds_dynlink.c             |   47 +-
 external/portaudio/pa_win_ds_dynlink.h             |   15 +-
 external/portaudio/pa_win_hostapis.c               |   27 +-
 external/portaudio/pa_win_util.c                   |   32 +-
 external/portaudio/pa_win_wasapi.h                 |  391 ++
 external/portaudio/pa_win_waveformat.c             |   47 +-
 external/portaudio/pa_win_waveformat.h             |   23 +-
 external/portaudio/pa_win_wdmks.c                  | 6805 +++++++++++++++-----
 external/portaudio/pa_win_wdmks.h                  |  106 +
 external/portaudio/pa_win_wdmks_utils.c            |  308 +
 external/portaudio/pa_win_wmme.c                   |  819 ++-
 external/portaudio/pa_win_wmme.h                   |   13 +-
 external/portaudio/pa_x86_plain_converters.c       | 1218 ++++
 .../{pa_trace.h => pa_x86_plain_converters.h}      |   41 +-
 external/portaudio/portaudio.h                     |  147 +-
 fon/AmplitudeTier.cpp                              |    4 +-
 fon/AmplitudeTierEditor.cpp                        |   10 +-
 fon/AmplitudeTierEditor.h                          |    4 +-
 fon/DurationTierEditor.cpp                         |   10 +-
 fon/DurationTierEditor.h                           |    4 +-
 fon/Formant.cpp                                    |   12 +-
 fon/Formant.h                                      |    2 +-
 fon/FormantGrid.cpp                                |    7 +-
 fon/FormantGrid.h                                  |    4 +-
 fon/FormantGridEditor.cpp                          |  175 +-
 fon/FormantGridEditor.h                            |    1 +
 fon/FormantTier.cpp                                |    6 +-
 fon/FunctionEditor.cpp                             |  305 +-
 fon/FunctionEditor.h                               |    4 +-
 fon/LongSound.cpp                                  |   25 +-
 fon/ManipulationEditor.cpp                         |   15 +-
 fon/Matrix.cpp                                     |   97 +-
 fon/Matrix.h                                       |   64 +-
 fon/Movie.cpp                                      |   37 +-
 fon/Movie.h                                        |   10 +-
 fon/MovieWindow.cpp                                |  109 +-
 fon/MovieWindow.h                                  |   10 +-
 fon/Movie_def.h                                    |    5 -
 fon/ParamCurve.cpp                                 |   10 +-
 fon/Photo.cpp                                      |  344 +-
 fon/Photo.h                                        |    5 +-
 fon/Photo_def.h                                    |   10 +-
 fon/Pitch.cpp                                      |   44 +-
 fon/Pitch.h                                        |   12 +-
 fon/PitchEditor.cpp                                |   10 +-
 fon/Pitch_Intensity.cpp                            |    6 +-
 fon/Pitch_to_PitchTier.cpp                         |   10 +-
 fon/Pitch_to_PointProcess.cpp                      |    6 +-
 fon/PointEditor.cpp                                |    4 +-
 fon/PointProcess.cpp                               |    4 +-
 fon/PointProcess_and_Sound.cpp                     |    4 +-
 fon/Polygon.cpp                                    |   30 +-
 fon/Praat_tests.cpp                                |   34 +-
 fon/Praat_tests_enums.h                            |    9 +-
 fon/RealTier.cpp                                   |  105 +-
 fon/RealTierEditor.cpp                             |    8 +-
 fon/RealTier_def.h                                 |    3 +-
 fon/Sampled.cpp                                    |   94 +-
 fon/Sampled.h                                      |   45 +-
 fon/SampledXY.cpp                                  |   28 +-
 fon/SampledXY.h                                    |   36 +-
 fon/SampledXY_def.h                                |    7 +-
 fon/Sampled_def.h                                  |   10 +-
 fon/Sound.cpp                                      |   22 +-
 fon/Sound.h                                        |    8 +-
 fon/SoundEditor.cpp                                |   32 +-
 fon/SoundRecorder.cpp                              |   36 +-
 fon/SoundRecorder.h                                |    4 +-
 fon/Sound_and_Spectrogram.cpp                      |   10 +-
 fon/Sound_audio.cpp                                |   17 +-
 fon/Sound_files.cpp                                |   18 +-
 fon/Sound_to_Cochleagram.cpp                       |    4 +-
 fon/Sound_to_Formant.cpp                           |    6 +-
 fon/Sound_to_Intensity.cpp                         |    4 +-
 fon/Sound_to_Pitch kopie.cpp                       |  566 ++
 fon/Sound_to_Pitch.cpp                             |  559 +-
 fon/Sound_to_PointProcess.cpp                      |   10 +-
 fon/SpectrumTier.h                                 |    4 +-
 fon/Spectrum_and_Spectrogram.cpp                   |    4 +-
 fon/Spectrum_to_Excitation.cpp                     |    7 +-
 fon/Spectrum_to_Excitation.h                       |    4 +-
 fon/TextGrid.cpp                                   |  375 +-
 fon/TextGrid.h                                     |   12 +-
 fon/TextGridEditor.cpp                             |   85 +-
 fon/TextGridEditor.h                               |   13 +-
 fon/TextGrid_Sound.cpp                             |  122 +-
 fon/TextGrid_Sound.h                               |    4 +-
 fon/TextGrid_def.h                                 |   14 +-
 fon/TimeSoundAnalysisEditor.cpp                    |   11 +-
 fon/TimeSoundEditor.cpp                            |   20 +-
 fon/Vector.cpp                                     |   22 +-
 fon/manual_Fon.cpp                                 |   43 +-
 fon/manual_Manual.cpp                              |   40 +-
 fon/manual_Picture.cpp                             |  125 +-
 fon/manual_Sampling.cpp                            |   18 +-
 fon/manual_Script.cpp                              | 1628 ++---
 fon/manual_annotation.cpp                          |   14 +-
 fon/manual_formant.cpp                             |    8 +-
 fon/manual_glossary.cpp                            |   58 +-
 fon/manual_references.cpp                          |   24 +-
 fon/manual_sound.cpp                               |   32 +-
 fon/manual_soundFiles.cpp                          |    2 +-
 fon/manual_spectrum.cpp                            |   64 +-
 fon/manual_tutorials.cpp                           |  271 +-
 fon/manual_voice.cpp                               |    8 +-
 fon/praat_Fon.cpp                                  |   68 +-
 fon/praat_Sound_init.cpp                           |  926 +--
 fon/praat_TextGrid_init.cpp                        |  780 ++-
 gram/Makefile                                      |    5 +-
 gram/OTGrammar.cpp                                 |  364 +-
 gram/OTGrammar.h                                   |    8 +-
 gram/OTGrammar_enums.h                             |   21 +-
 gram/OTGrammar_ex_metrics.cpp                      |    5 +-
 gram/OTMulti.cpp                                   |   44 +-
 gram/OTMulti.h                                     |    6 +-
 ...ammar_ex_metrics.cpp => OTMulti_ex_metrics.cpp} |  195 +-
 gram/manual_gram.cpp                               |    8 +-
 gram/praat_gram.cpp                                | 1347 ++--
 kar/longchar.cpp                                   |    4 +-
 main/palias                                        |   66 +
 main/praat.exe.manifest                            |    7 +-
 main/{praat_mac.plist => praat_carbon.plist}       |   14 +-
 main/{praat_mac.plist => praat_cocoa.plist}        |   20 +-
 makefile                                           |    2 +-
 makefiles/makefile.defs.linux.alsa                 |    6 +-
 makefiles/makefile.defs.linux.silent               |    6 +-
 makefiles/makefile.defs.linuxc.alsa                |   23 +
 makefiles/makefile.defs.linuxs.alsa                |   23 +
 makefiles/makefile.defs.mingw32                    |    6 +-
 makefiles/makefile.defs.mingw32-490                |   24 +
 makefiles/makefile.defs.mingw32c                   |    6 +-
 makefiles/makefile.defs.mingw64                    |    6 +-
 makefiles/makefile.defs.mingw64-490                |   24 +
 makefiles/makefile.defs.mingw64c                   |    6 +-
 num/NUM.cpp                                        |    3 +-
 num/NUM.h                                          |   26 +-
 num/NUMrandom.cpp                                  |  453 +-
 stat/Distributions.cpp                             |   34 +-
 stat/Distributions.h                               |    5 +-
 stat/Distributions_and_Strings.cpp                 |    4 +-
 stat/Regression.cpp                                |   11 +-
 stat/Regression.h                                  |    8 +-
 stat/Table.cpp                                     |   18 +-
 stat/Table.h                                       |    6 +-
 stat/TableOfReal.cpp                               |    2 +-
 stat/praat_Stat.cpp                                |  708 +-
 sys/ButtonEditor.cpp                               |   12 +-
 sys/Collection.cpp                                 |   12 +-
 sys/DataEditor.cpp                                 |   10 +-
 sys/DataEditor.h                                   |    2 +-
 sys/DemoEditor.cpp                                 |   22 +-
 sys/Editor.cpp                                     |  113 +-
 sys/Editor.h                                       |    3 +-
 sys/Formula.cpp                                    |  343 +-
 sys/Formula.h                                      |    2 +-
 sys/Graphics.cpp                                   |   75 +-
 sys/Graphics.h                                     |   59 +-
 sys/GraphicsP.h                                    |   74 +-
 sys/GraphicsPostscript.cpp                         |   13 +-
 sys/GraphicsScreen.cpp                             |  346 +-
 sys/Graphics_colour.cpp                            |  171 +-
 sys/Graphics_enums.h                               |   20 +-
 sys/Graphics_image.cpp                             |  151 +-
 sys/Graphics_linesAndAreas.cpp                     |  284 +-
 sys/Graphics_mouse.cpp                             |    2 +-
 sys/Graphics_record.cpp                            |   43 +-
 sys/Graphics_text.cpp                              |  319 +-
 sys/Gui.cpp                                        |    2 +-
 sys/Gui.h                                          |   29 +-
 sys/GuiButton.cpp                                  |    3 +
 sys/GuiCheckButton.cpp                             |    6 +-
 sys/GuiDialog.cpp                                  |   16 +-
 sys/GuiDrawingArea.cpp                             |   58 +-
 sys/GuiFileSelect.cpp                              |    7 +-
 sys/GuiList.cpp                                    |   21 +-
 sys/GuiMenu.cpp                                    |  112 +-
 sys/GuiMenuItem.cpp                                |   22 +-
 sys/GuiOptionMenu.cpp                              |   24 +-
 sys/GuiRadioButton.cpp                             |   37 +-
 sys/GuiScale.cpp                                   |   26 +
 sys/GuiScrollBar.cpp                               |   48 +
 sys/GuiScrolledWindow.cpp                          |   44 +-
 sys/GuiShell.cpp                                   |    2 +-
 sys/GuiText.cpp                                    |   19 +-
 sys/GuiWindow.cpp                                  |   93 +-
 sys/HyperPage.cpp                                  |   60 +-
 sys/HyperPage.h                                    |    3 +-
 sys/Interpreter.cpp                                |  242 +-
 sys/Interpreter.h                                  |    6 +-
 sys/ManPages.cpp                                   |    5 +-
 sys/Manual.cpp                                     |   73 +-
 sys/MelderThread.h                                 |  173 +
 sys/Picture.cpp                                    |   58 +-
 sys/Picture.h                                      |    4 +-
 sys/Printer.cpp                                    |  100 +-
 sys/ScriptEditor.cpp                               |   74 +-
 sys/Strings.cpp                                    |    2 +-
 sys/TextEditor.cpp                                 |   36 +-
 sys/Thing.h                                        |    5 +-
 sys/Ui.cpp                                         |   41 +-
 sys/Ui.h                                           |    1 +
 sys/UiFile.cpp                                     |   18 +-
 sys/UiPause.cpp                                    |   14 +
 sys/abcio.cpp                                      |    4 +-
 sys/melder.cpp                                     |   63 +-
 sys/melder.h                                       |   52 +-
 sys/melder_audio.cpp                               |  489 +-
 sys/melder_audiofiles.cpp                          |   65 +-
 sys/melder_debug.cpp                               |   15 +-
 sys/melder_error.cpp                               |    5 +
 sys/melder_files.cpp                               |   16 +-
 sys/melder_ftoa.cpp                                |   11 +-
 sys/melder_info.cpp                                |  213 +-
 sys/melder_time.cpp                                |   57 +-
 sys/motifEmulator.cpp                              |   30 +-
 sys/praat.cpp                                      |  294 +-
 sys/praat.h                                        |   69 +-
 sys/praatP.h                                       |    3 +-
 sys/praat_actions.cpp                              |   19 +-
 sys/praat_logo.cpp                                 |   34 +-
 sys/praat_menuCommands.cpp                         |   10 +-
 sys/praat_objectMenus.cpp                          |   52 +-
 sys/praat_picture.cpp                              |  669 +-
 sys/praat_script.cpp                               |  229 +-
 sys/praat_script.h                                 |    5 +-
 sys/praat_version.h                                |   10 +-
 sys/sendpraat.c                                    |   12 +-
 test/fon/TextGrid.praat                            |  139 +
 test/fon/data.praat                                |  376 +-
 test/script/procedures.praat                       |   62 +
 test/sys/graphics.praat                            |  Bin 8090 -> 8026 bytes
 test/sys/paul.png                                  |  Bin 0 -> 61068 bytes
 test/sys/procedures2.praat                         |   10 -
 348 files changed, 39970 insertions(+), 12861 deletions(-)

diff --git a/EEG/EEG.cpp b/EEG/EEG.cpp
index f562624..9c9253a 100644
--- a/EEG/EEG.cpp
+++ b/EEG/EEG.cpp
@@ -1,6 +1,6 @@
 /* EEG.cpp
  *
- * Copyright (C) 2011-2012 Paul Boersma
+ * Copyright (C) 2011-2012,2013,2014 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
@@ -44,47 +44,51 @@ Thing_implement (EEG, Function, 0);
 void structEEG :: v_info () {
 	structData :: v_info ();
 	MelderInfo_writeLine (L"Time domain:");
-	MelderInfo_writeLine (L"   Start time: ", Melder_double (xmin), L" seconds");
-	MelderInfo_writeLine (L"   End time: ", Melder_double (xmax), L" seconds");
-	MelderInfo_writeLine (L"   Total duration: ", Melder_double (xmax - xmin), L" seconds");
-	if (d_sound != NULL) {
+	MelderInfo_writeLine (L"   Start time: ", Melder_double (our xmin), L" seconds");
+	MelderInfo_writeLine (L"   End time: ", Melder_double (our xmax), L" seconds");
+	MelderInfo_writeLine (L"   Total duration: ", Melder_double (our xmax - our xmin), L" seconds");
+	if (our sound != NULL) {
 		MelderInfo_writeLine (L"Time sampling of the signal:");
-		MelderInfo_writeLine (L"   Number of samples: ", Melder_integer (d_sound -> nx));
-		MelderInfo_writeLine (L"   Sampling period: ", Melder_double (d_sound -> dx), L" seconds");
-		MelderInfo_writeLine (L"   Sampling frequency: ", Melder_single (1.0 / d_sound -> dx), L" Hz");
-		MelderInfo_writeLine (L"   First sample centred at: ", Melder_double (d_sound -> x1), L" seconds");
+		MelderInfo_writeLine (L"   Number of samples: ", Melder_integer (our sound -> nx));
+		MelderInfo_writeLine (L"   Sampling period: ", Melder_double (our sound -> dx), L" seconds");
+		MelderInfo_writeLine (L"   Sampling frequency: ", Melder_single (1.0 / our sound -> dx), L" Hz");
+		MelderInfo_writeLine (L"   First sample centred at: ", Melder_double (our sound -> x1), L" seconds");
 	}
-	MelderInfo_writeLine (L"Number of cap electrodes: ", Melder_integer (f_getNumberOfCapElectrodes ()));
-	MelderInfo_writeLine (L"Number of external electrodes: ", Melder_integer (f_getNumberOfExternalElectrodes ()));
-	MelderInfo_writeLine (L"Number of extra sensors: ", Melder_integer (f_getNumberOfExtraSensors ()));
+	MelderInfo_writeLine (L"Number of cap electrodes: ", Melder_integer (EEG_getNumberOfCapElectrodes (this)));
+	MelderInfo_writeLine (L"Number of external electrodes: ", Melder_integer (EEG_getNumberOfExternalElectrodes (this)));
+	MelderInfo_writeLine (L"Number of extra sensors: ", Melder_integer (EEG_getNumberOfExtraSensors (this)));
 }
 
 void structEEG :: v_shiftX (double xfrom, double xto) {
 	EEG_Parent :: v_shiftX (xfrom, xto);
-	if (d_sound    != NULL)  Function_shiftXTo (d_sound,    xfrom, xto);
-	if (d_textgrid != NULL)  Function_shiftXTo (d_textgrid, xfrom, xto);
+	if (our sound    != NULL)  Function_shiftXTo (our sound,    xfrom, xto);
+	if (our textgrid != NULL)  Function_shiftXTo (our textgrid, xfrom, xto);
 }
 
 void structEEG :: v_scaleX (double xminfrom, double xmaxfrom, double xminto, double xmaxto) {
 	EEG_Parent :: v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
-	if (d_sound    != NULL)  d_sound    -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
-	if (d_textgrid != NULL)  d_textgrid -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
+	if (our sound    != NULL)  our sound    -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
+	if (our textgrid != NULL)  our textgrid -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
+}
+
+void EEG_init (EEG me, double tmin, double tmax) {
+	my xmin = tmin;
+	my xmax = tmax;
 }
 
 EEG EEG_create (double tmin, double tmax) {
 	try {
 		autoEEG me = Thing_new (EEG);
-		my xmin = tmin;
-		my xmax = tmax;
+		EEG_init (me.peek(), tmin, tmax);
 		return me.transfer();
 	} catch (MelderError) {
 		Melder_throw ("EEG object not created.");
 	}
 }
 
-long structEEG :: f_getChannelNumber (const wchar_t *channelName) {
-	for (long ichan = 1; ichan <= d_numberOfChannels; ichan ++) {
-		if (Melder_wcsequ (d_channelNames [ichan], channelName)) {
+long EEG_getChannelNumber (EEG me, const wchar_t *channelName) {
+	for (long ichan = 1; ichan <= my numberOfChannels; ichan ++) {
+		if (Melder_wcsequ (my channelNames [ichan], channelName)) {
 			return ichan;
 		}
 	}
@@ -96,28 +100,29 @@ EEG EEG_readFromBdfFile (MelderFile file) {
 		autofile f = Melder_fopen (file, "rb");
 		char buffer [81];
 		fread (buffer, 1, 8, f); buffer [8] = '\0';
+		bool is24bit = buffer [0] == (char) 255;
 		fread (buffer, 1, 80, f); buffer [80] = '\0';
-		//Melder_casual ("Local subject identification: \"%s\"", buffer);
+		trace ("Local subject identification: \"%s\"", buffer);
 		fread (buffer, 1, 80, f); buffer [80] = '\0';
-		//Melder_casual ("Local recording identification: \"%s\"", buffer);
+		trace ("Local recording identification: \"%s\"", buffer);
 		fread (buffer, 1, 8, f); buffer [8] = '\0';
-		//Melder_casual ("Start date of recording: \"%s\"", buffer);
+		trace ("Start date of recording: \"%s\"", buffer);
 		fread (buffer, 1, 8, f); buffer [8] = '\0';
-		//Melder_casual ("Start time of recording: \"%s\"", buffer);
+		trace ("Start time of recording: \"%s\"", buffer);
 		fread (buffer, 1, 8, f); buffer [8] = '\0';
 		long numberOfBytesInHeaderRecord = atol (buffer);
-		//Melder_casual ("Number of bytes in header record: %ld", numberOfBytesInHeaderRecord);
+		trace ("Number of bytes in header record: %ld", numberOfBytesInHeaderRecord);
 		fread (buffer, 1, 44, f); buffer [44] = '\0';
-		//Melder_casual ("Version of data format: \"%s\"", buffer);
+		trace ("Version of data format: \"%s\"", buffer);
 		fread (buffer, 1, 8, f); buffer [8] = '\0';
 		long numberOfDataRecords = strtol (buffer, NULL, 10);
-		//Melder_casual ("Number of data records: %ld", numberOfDataRecords);
+		trace ("Number of data records: %ld", numberOfDataRecords);
 		fread (buffer, 1, 8, f); buffer [8] = '\0';
 		double durationOfDataRecord = atof (buffer);
-		//Melder_casual ("Duration of a data record: \"%f\"", durationOfDataRecord);
+		trace ("Duration of a data record: \"%f\"", durationOfDataRecord);
 		fread (buffer, 1, 4, f); buffer [4] = '\0';
 		long numberOfChannels = atol (buffer);
-		//Melder_casual ("Number of channels in data record: %ld", numberOfChannels);
+		trace ("Number of channels in data record: %ld", numberOfChannels);
 		if (numberOfBytesInHeaderRecord != (numberOfChannels + 1) * 256)
 			Melder_throw ("Number of bytes in header record (", numberOfBytesInHeaderRecord,
 				") doesn't match number of channels (", numberOfChannels, ").");
@@ -135,7 +140,9 @@ EEG EEG_readFromBdfFile (MelderFile file) {
 				}
 			}
 			channelNames [ichannel] = Melder_wcsdup (Melder_peekUtf8ToWcs (buffer));
+			trace ("Channel <<%ls>>", channelNames [ichannel]);
 		}
+		bool hasLetters = wcsequ (channelNames [numberOfChannels], L"EDF Annotations");
 		double samplingFrequency = NUMundefined;
 		for (long channel = 1; channel <= numberOfChannels; channel ++) {
 			fread (buffer, 1, 80, f); buffer [80] = '\0';   // transducer type
@@ -184,137 +191,219 @@ EEG EEG_readFromBdfFile (MelderFile file) {
 		}
 		double duration = numberOfDataRecords * durationOfDataRecord;
 		autoEEG him = EEG_create (0, duration);
-		his d_numberOfChannels = numberOfChannels;
+		his numberOfChannels = numberOfChannels;
 		autoSound me = Sound_createSimple (numberOfChannels, duration, samplingFrequency);
+		Melder_assert (my nx == numberOfSamplesPerDataRecord * numberOfDataRecords);
+		autoNUMvector <unsigned char> dataBuffer (0L, 3 * numberOfSamplesPerDataRecord - 1);
 		for (long record = 1; record <= numberOfDataRecords; record ++) {
 			for (long channel = 1; channel <= numberOfChannels; channel ++) {
 				double factor = channel == numberOfChannels ? 1.0 : physicalMinimum [channel] / digitalMinimum [channel];
-				if (channel < numberOfChannels - his f_getNumberOfExtraSensors ()) factor /= 1000000.0;
-				for (long i = 1; i <= numberOfSamplesPerDataRecord; i ++) {
-					long sample = i + (record - 1) * numberOfSamplesPerDataRecord;
-					Melder_assert (sample <= my nx);
-					my z [channel] [sample] = bingeti3LE (f) * factor;
+				if (channel < numberOfChannels - EEG_getNumberOfExtraSensors (him.peek())) factor /= 1000000.0;
+				if (is24bit) {
+					fread (& dataBuffer [0], 3, numberOfSamplesPerDataRecord, f);
+					unsigned char *p = & dataBuffer [0];
+					for (long i = 1; i <= numberOfSamplesPerDataRecord; i ++) {
+						long sample = i + (record - 1) * numberOfSamplesPerDataRecord;
+						Melder_assert (sample <= my nx);
+						uint8_t lowByte = *p ++, midByte = *p ++, highByte = *p ++;
+						uint32_t externalValue = ((uint32_t) highByte << 16) | ((uint32_t) midByte << 8) | (uint32_t) lowByte;
+						if ((highByte & 128) != 0)   // is the 24-bit sign bit on?
+							externalValue |= 0xFF000000;   // extend negative sign to 32 bits
+						my z [channel] [sample] = (int32_t) externalValue * factor;
+					}
+				} else {
+					fread (& dataBuffer [0], 2, numberOfSamplesPerDataRecord, f);
+					unsigned char *p = & dataBuffer [0];
+					for (long i = 1; i <= numberOfSamplesPerDataRecord; i ++) {
+						long sample = i + (record - 1) * numberOfSamplesPerDataRecord;
+						Melder_assert (sample <= my nx);
+						uint8_t lowByte = *p ++, highByte = *p ++;
+						uint16_t externalValue = ((uint16_t) highByte << 8) | (uint16_t) lowByte;
+						my z [channel] [sample] = (int16_t) externalValue * factor;
+					}
 				}
 			}
 		}
-		autoTextGrid thee = TextGrid_create (0, duration, L"S1 S2 S3 S4 S5 S6 S7 S8", L"");
-		for (int bit = 1; bit <= 8; bit ++) {
-			unsigned long bitValue = 1 << (bit - 1);
-			IntervalTier tier = (IntervalTier) thy tiers -> item [bit];
+		int numberOfStatusBits = 8;
+		for (long i = 1; i <= my nx; i ++) {
+			unsigned long value = (long) my z [numberOfChannels] [i];
+			if (value & 0x0000FF00) {
+				numberOfStatusBits = 16;
+			}
+		}
+		autoTextGrid thee;
+		if (hasLetters) {
+			thee.reset (TextGrid_create (0, duration, L"Mark Trigger", L"Mark Trigger"));
+			autoMelderString letters;
+			double time = NUMundefined;
 			for (long i = 1; i <= my nx; i ++) {
-				unsigned long previousValue = i == 1 ? 0 : (long) my z [numberOfChannels] [i - 1];
-				unsigned long thisValue = (long) my z [numberOfChannels] [i];
-				if ((thisValue & bitValue) != (previousValue & bitValue)) {
-					double time = i == 1 ? 0.0 : my x1 + (i - 1.5) * my dx;
-					if (time != 0.0)
-						TextGrid_insertBoundary (thee.peek(), bit, time);
-					if ((thisValue & bitValue) != 0)
-						TextGrid_setIntervalText (thee.peek(), bit, tier -> intervals -> size, L"1");
+				unsigned long value = (long) my z [numberOfChannels] [i];
+				for (int byte = 1; byte <= numberOfStatusBits / 8; byte ++) {
+					unsigned long mask = byte == 1 ? 0x000000ff : 0x0000ff00;
+					wchar_t kar = byte == 1 ? (value & mask) : (value & mask) >> 8;
+					if (kar != '\0' && kar != 20) {
+						MelderString_appendCharacter (& letters, kar);
+					} else if (letters. string [0] != '\0') {
+						if (letters. string [0] == '+') {
+							if (NUMdefined (time)) {
+								try {
+									TextGrid_insertPoint (thee.peek(), 1, time, L"");
+								} catch (MelderError) {
+									Melder_throw ("Did not insert empty mark (", letters. string, ") on Mark tier.");
+								}
+								time = NUMundefined;   // defensive
+							}
+							time = Melder_atof (& letters. string [1]);
+							MelderString_empty (& letters);
+						} else {
+							if (! NUMdefined (time)) {
+								Melder_throw ("Undefined time for label at sample ", i, ".");
+							}
+							try {
+								if (Melder_wcsnequ (letters. string, L"Trigger-", 8)) {
+									try {
+										TextGrid_insertPoint (thee.peek(), 2, time, & letters. string [8]);
+									} catch (MelderError) {
+										Melder_clearError ();
+										trace ("Duplicate trigger at %f seconds: %ls", time, & letters. string [8]);
+									}
+								} else {
+									TextGrid_insertPoint (thee.peek(), 1, time, & letters. string [0]);
+								}
+							} catch (MelderError) {
+								Melder_throw ("Did not insert mark (", letters. string, ") on Trigger tier.");
+							}
+							time = NUMundefined;   // crucial
+							MelderString_empty (& letters);
+						}
+					}
+				}
+			}
+			if (NUMdefined (time)) {
+				TextGrid_insertPoint (thee.peek(), 1, time, L"");
+				time = NUMundefined;   // defensive
+			}
+		} else {
+			thee.reset (TextGrid_create (0, duration,
+				numberOfStatusBits == 8 ? L"S1 S2 S3 S4 S5 S6 S7 S8" : L"S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 S14 S15 S16", L""));
+			for (int bit = 1; bit <= numberOfStatusBits; bit ++) {
+				unsigned long bitValue = 1 << (bit - 1);
+				IntervalTier tier = (IntervalTier) thy tiers -> item [bit];
+				for (long i = 1; i <= my nx; i ++) {
+					unsigned long previousValue = i == 1 ? 0 : (long) my z [numberOfChannels] [i - 1];
+					unsigned long thisValue = (long) my z [numberOfChannels] [i];
+					if ((thisValue & bitValue) != (previousValue & bitValue)) {
+						double time = i == 1 ? 0.0 : my x1 + (i - 1.5) * my dx;
+						if (time != 0.0)
+							TextGrid_insertBoundary (thee.peek(), bit, time);
+						if ((thisValue & bitValue) != 0)
+							TextGrid_setIntervalText (thee.peek(), bit, tier -> intervals -> size, L"1");
+					}
 				}
 			}
 		}
 		f.close (file);
-		his d_channelNames = channelNames.transfer();
-		his d_sound = me.transfer();
-		his d_textgrid = thee.transfer();
-		if (his f_getNumberOfCapElectrodes () == 32) {
-			his f_setChannelName (1, L"Fp1");
-			his f_setChannelName (2, L"AF3");
-			his f_setChannelName (3, L"F7");
-			his f_setChannelName (4, L"F3");
-			his f_setChannelName (5, L"FC1");
-			his f_setChannelName (6, L"FC5");
-			his f_setChannelName (7, L"T7");
-			his f_setChannelName (8, L"C3");
-			his f_setChannelName (9, L"CP1");
-			his f_setChannelName (10, L"CP5");
-			his f_setChannelName (11, L"P7");
-			his f_setChannelName (12, L"P3");
-			his f_setChannelName (13, L"Pz");
-			his f_setChannelName (14, L"PO3");
-			his f_setChannelName (15, L"O1");
-			his f_setChannelName (16, L"Oz");
-			his f_setChannelName (17, L"O2");
-			his f_setChannelName (18, L"PO4");
-			his f_setChannelName (19, L"P4");
-			his f_setChannelName (20, L"P8");
-			his f_setChannelName (21, L"CP6");
-			his f_setChannelName (22, L"CP2");
-			his f_setChannelName (23, L"C4");
-			his f_setChannelName (24, L"T8");
-			his f_setChannelName (25, L"FC6");
-			his f_setChannelName (26, L"FC2");
-			his f_setChannelName (27, L"F4");
-			his f_setChannelName (28, L"F8");
-			his f_setChannelName (29, L"AF4");
-			his f_setChannelName (30, L"Fp2");
-			his f_setChannelName (31, L"Fz");
-			his f_setChannelName (32, L"Cz");
-		} else if (his f_getNumberOfCapElectrodes () == 64) {
-			his f_setChannelName (1, L"Fp1");
-			his f_setChannelName (2, L"AF7");
-			his f_setChannelName (3, L"AF3");
-			his f_setChannelName (4, L"F1");
-			his f_setChannelName (5, L"F3");
-			his f_setChannelName (6, L"F5");
-			his f_setChannelName (7, L"F7");
-			his f_setChannelName (8, L"FT7");
-			his f_setChannelName (9, L"FC5");
-			his f_setChannelName (10, L"FC3");
-			his f_setChannelName (11, L"FC1");
-			his f_setChannelName (12, L"C1");
-			his f_setChannelName (13, L"C3");
-			his f_setChannelName (14, L"C5");
-			his f_setChannelName (15, L"T7");
-			his f_setChannelName (16, L"TP7");
-			his f_setChannelName (17, L"CP5");
-			his f_setChannelName (18, L"CP3");
-			his f_setChannelName (19, L"CP1");
-			his f_setChannelName (20, L"P1");
-			his f_setChannelName (21, L"P3");
-			his f_setChannelName (22, L"P5");
-			his f_setChannelName (23, L"P7");
-			his f_setChannelName (24, L"P9");
-			his f_setChannelName (25, L"PO7");
-			his f_setChannelName (26, L"PO3");
-			his f_setChannelName (27, L"O1");
-			his f_setChannelName (28, L"Iz");
-			his f_setChannelName (29, L"Oz");
-			his f_setChannelName (30, L"POz");
-			his f_setChannelName (31, L"Pz");
-			his f_setChannelName (32, L"CPz");
-			his f_setChannelName (33, L"Fpz");
-			his f_setChannelName (34, L"Fp2");
-			his f_setChannelName (35, L"AF8");
-			his f_setChannelName (36, L"AF4");
-			his f_setChannelName (37, L"AFz");
-			his f_setChannelName (38, L"Fz");
-			his f_setChannelName (39, L"F2");
-			his f_setChannelName (40, L"F4");
-			his f_setChannelName (41, L"F6");
-			his f_setChannelName (42, L"F8");
-			his f_setChannelName (43, L"FT8");
-			his f_setChannelName (44, L"FC6");
-			his f_setChannelName (45, L"FC4");
-			his f_setChannelName (46, L"FC2");
-			his f_setChannelName (47, L"FCz");
-			his f_setChannelName (48, L"Cz");
-			his f_setChannelName (49, L"C2");
-			his f_setChannelName (50, L"C4");
-			his f_setChannelName (51, L"C6");
-			his f_setChannelName (52, L"T8");
-			his f_setChannelName (53, L"TP8");
-			his f_setChannelName (54, L"CP6");
-			his f_setChannelName (55, L"CP4");
-			his f_setChannelName (56, L"CP2");
-			his f_setChannelName (57, L"P2");
-			his f_setChannelName (58, L"P4");
-			his f_setChannelName (59, L"P6");
-			his f_setChannelName (60, L"P8");
-			his f_setChannelName (61, L"P10");
-			his f_setChannelName (62, L"PO8");
-			his f_setChannelName (63, L"PO4");
-			his f_setChannelName (64, L"O2");
+		his channelNames = channelNames.transfer();
+		his sound = me.transfer();
+		his textgrid = thee.transfer();
+		if (EEG_getNumberOfCapElectrodes (him.peek()) == 32) {
+			EEG_setChannelName (him.peek(), 1, L"Fp1");
+			EEG_setChannelName (him.peek(), 2, L"AF3");
+			EEG_setChannelName (him.peek(), 3, L"F7");
+			EEG_setChannelName (him.peek(), 4, L"F3");
+			EEG_setChannelName (him.peek(), 5, L"FC1");
+			EEG_setChannelName (him.peek(), 6, L"FC5");
+			EEG_setChannelName (him.peek(), 7, L"T7");
+			EEG_setChannelName (him.peek(), 8, L"C3");
+			EEG_setChannelName (him.peek(), 9, L"CP1");
+			EEG_setChannelName (him.peek(), 10, L"CP5");
+			EEG_setChannelName (him.peek(), 11, L"P7");
+			EEG_setChannelName (him.peek(), 12, L"P3");
+			EEG_setChannelName (him.peek(), 13, L"Pz");
+			EEG_setChannelName (him.peek(), 14, L"PO3");
+			EEG_setChannelName (him.peek(), 15, L"O1");
+			EEG_setChannelName (him.peek(), 16, L"Oz");
+			EEG_setChannelName (him.peek(), 17, L"O2");
+			EEG_setChannelName (him.peek(), 18, L"PO4");
+			EEG_setChannelName (him.peek(), 19, L"P4");
+			EEG_setChannelName (him.peek(), 20, L"P8");
+			EEG_setChannelName (him.peek(), 21, L"CP6");
+			EEG_setChannelName (him.peek(), 22, L"CP2");
+			EEG_setChannelName (him.peek(), 23, L"C4");
+			EEG_setChannelName (him.peek(), 24, L"T8");
+			EEG_setChannelName (him.peek(), 25, L"FC6");
+			EEG_setChannelName (him.peek(), 26, L"FC2");
+			EEG_setChannelName (him.peek(), 27, L"F4");
+			EEG_setChannelName (him.peek(), 28, L"F8");
+			EEG_setChannelName (him.peek(), 29, L"AF4");
+			EEG_setChannelName (him.peek(), 30, L"Fp2");
+			EEG_setChannelName (him.peek(), 31, L"Fz");
+			EEG_setChannelName (him.peek(), 32, L"Cz");
+		} else if (EEG_getNumberOfCapElectrodes (him.peek()) == 64) {
+			EEG_setChannelName (him.peek(), 1, L"Fp1");
+			EEG_setChannelName (him.peek(), 2, L"AF7");
+			EEG_setChannelName (him.peek(), 3, L"AF3");
+			EEG_setChannelName (him.peek(), 4, L"F1");
+			EEG_setChannelName (him.peek(), 5, L"F3");
+			EEG_setChannelName (him.peek(), 6, L"F5");
+			EEG_setChannelName (him.peek(), 7, L"F7");
+			EEG_setChannelName (him.peek(), 8, L"FT7");
+			EEG_setChannelName (him.peek(), 9, L"FC5");
+			EEG_setChannelName (him.peek(), 10, L"FC3");
+			EEG_setChannelName (him.peek(), 11, L"FC1");
+			EEG_setChannelName (him.peek(), 12, L"C1");
+			EEG_setChannelName (him.peek(), 13, L"C3");
+			EEG_setChannelName (him.peek(), 14, L"C5");
+			EEG_setChannelName (him.peek(), 15, L"T7");
+			EEG_setChannelName (him.peek(), 16, L"TP7");
+			EEG_setChannelName (him.peek(), 17, L"CP5");
+			EEG_setChannelName (him.peek(), 18, L"CP3");
+			EEG_setChannelName (him.peek(), 19, L"CP1");
+			EEG_setChannelName (him.peek(), 20, L"P1");
+			EEG_setChannelName (him.peek(), 21, L"P3");
+			EEG_setChannelName (him.peek(), 22, L"P5");
+			EEG_setChannelName (him.peek(), 23, L"P7");
+			EEG_setChannelName (him.peek(), 24, L"P9");
+			EEG_setChannelName (him.peek(), 25, L"PO7");
+			EEG_setChannelName (him.peek(), 26, L"PO3");
+			EEG_setChannelName (him.peek(), 27, L"O1");
+			EEG_setChannelName (him.peek(), 28, L"Iz");
+			EEG_setChannelName (him.peek(), 29, L"Oz");
+			EEG_setChannelName (him.peek(), 30, L"POz");
+			EEG_setChannelName (him.peek(), 31, L"Pz");
+			EEG_setChannelName (him.peek(), 32, L"CPz");
+			EEG_setChannelName (him.peek(), 33, L"Fpz");
+			EEG_setChannelName (him.peek(), 34, L"Fp2");
+			EEG_setChannelName (him.peek(), 35, L"AF8");
+			EEG_setChannelName (him.peek(), 36, L"AF4");
+			EEG_setChannelName (him.peek(), 37, L"AFz");
+			EEG_setChannelName (him.peek(), 38, L"Fz");
+			EEG_setChannelName (him.peek(), 39, L"F2");
+			EEG_setChannelName (him.peek(), 40, L"F4");
+			EEG_setChannelName (him.peek(), 41, L"F6");
+			EEG_setChannelName (him.peek(), 42, L"F8");
+			EEG_setChannelName (him.peek(), 43, L"FT8");
+			EEG_setChannelName (him.peek(), 44, L"FC6");
+			EEG_setChannelName (him.peek(), 45, L"FC4");
+			EEG_setChannelName (him.peek(), 46, L"FC2");
+			EEG_setChannelName (him.peek(), 47, L"FCz");
+			EEG_setChannelName (him.peek(), 48, L"Cz");
+			EEG_setChannelName (him.peek(), 49, L"C2");
+			EEG_setChannelName (him.peek(), 50, L"C4");
+			EEG_setChannelName (him.peek(), 51, L"C6");
+			EEG_setChannelName (him.peek(), 52, L"T8");
+			EEG_setChannelName (him.peek(), 53, L"TP8");
+			EEG_setChannelName (him.peek(), 54, L"CP6");
+			EEG_setChannelName (him.peek(), 55, L"CP4");
+			EEG_setChannelName (him.peek(), 56, L"CP2");
+			EEG_setChannelName (him.peek(), 57, L"P2");
+			EEG_setChannelName (him.peek(), 58, L"P4");
+			EEG_setChannelName (him.peek(), 59, L"P6");
+			EEG_setChannelName (him.peek(), 60, L"P8");
+			EEG_setChannelName (him.peek(), 61, L"P10");
+			EEG_setChannelName (him.peek(), 62, L"PO8");
+			EEG_setChannelName (him.peek(), 63, L"PO4");
+			EEG_setChannelName (him.peek(), 64, L"O2");
 		}
 		return him.transfer();
 	} catch (MelderError) {
@@ -330,23 +419,23 @@ static void detrend (double *a, long numberOfSamples) {
 	}
 }
 
-void structEEG :: f_detrend () {
-	for (long ichan = 1; ichan <= d_numberOfChannels - f_getNumberOfExtraSensors (); ichan ++) {
-		detrend (d_sound -> z [ichan], d_sound -> nx);
+void EEG_detrend (EEG me) {
+	for (long ichan = 1; ichan <= my numberOfChannels - EEG_getNumberOfExtraSensors (me); ichan ++) {
+		detrend (my sound -> z [ichan], my sound -> nx);
 	}
 }
 
-void structEEG :: f_filter (double lowFrequency, double lowWidth, double highFrequency, double highWidth, bool doNotch50Hz) {
+void EEG_filter (EEG me, double lowFrequency, double lowWidth, double highFrequency, double highWidth, bool doNotch50Hz) {
 	try {
 /*
 	long nsampFFT = 1;
-	while (nsampFFT < d_sound -> nx)
+	while (nsampFFT < my sound -> nx)
 		nsampFFT *= 2;
 	autoNUMfft_Table fftTable;
 	NUMfft_Table_init (& fftTable, nsampFFT);
 */
-		for (long ichan = 1; ichan <= d_numberOfChannels - f_getNumberOfExtraSensors (); ichan ++) {
-			autoSound channel = Sound_extractChannel (d_sound, ichan);
+		for (long ichan = 1; ichan <= my numberOfChannels - EEG_getNumberOfExtraSensors (me); ichan ++) {
+			autoSound channel = Sound_extractChannel (my sound, ichan);
 			autoSpectrum spec = Sound_to_Spectrum (channel.peek(), TRUE);
 			Spectrum_passHannBand (spec.peek(), lowFrequency, 0.0, lowWidth);
 			Spectrum_passHannBand (spec.peek(), 0.0, highFrequency, highWidth);
@@ -354,121 +443,132 @@ void structEEG :: f_filter (double lowFrequency, double lowWidth, double highFre
 				Spectrum_stopHannBand (spec.peek(), 48.0, 52.0, 1.0);
 			}
 			autoSound him = Spectrum_to_Sound (spec.peek());
-			NUMvector_copyElements (his z [1], d_sound -> z [ichan], 1, d_sound -> nx);
+			NUMvector_copyElements (his z [1], my sound -> z [ichan], 1, my sound -> nx);
 		}
 	} catch (MelderError) {
-		Melder_throw (this, ": not filtered.");
+		Melder_throw (me, ": not filtered.");
 	}
 }
 
-void structEEG :: f_setChannelName (long channelNumber, const wchar_t *a_name) {
+void EEG_setChannelName (EEG me, long channelNumber, const wchar_t *a_name) {
 	autostring l_name = Melder_wcsdup (a_name);
-	Melder_free (d_channelNames [channelNumber]);
-	d_channelNames [channelNumber] = l_name.transfer();
+	Melder_free (my channelNames [channelNumber]);
+	my channelNames [channelNumber] = l_name.transfer();
 }
 
-void structEEG :: f_setExternalElectrodeNames (const wchar_t *nameExg1, const wchar_t *nameExg2, const wchar_t *nameExg3, const wchar_t *nameExg4,
+void EEG_setExternalElectrodeNames (EEG me,
+	const wchar_t *nameExg1, const wchar_t *nameExg2, const wchar_t *nameExg3, const wchar_t *nameExg4,
 	const wchar_t *nameExg5, const wchar_t *nameExg6, const wchar_t *nameExg7, const wchar_t *nameExg8)
 {
-	if (f_getNumberOfExternalElectrodes () != 8)
+	if (EEG_getNumberOfExternalElectrodes (me) != 8)
 		Melder_throw (L"There aren't 8 external electrodes.");
-	const long firstExternalElectrode = f_getNumberOfCapElectrodes () + 1;
-	f_setChannelName (firstExternalElectrode, nameExg1);
-	f_setChannelName (firstExternalElectrode + 1, nameExg2);
-	f_setChannelName (firstExternalElectrode + 2, nameExg3);
-	f_setChannelName (firstExternalElectrode + 3, nameExg4);
-	f_setChannelName (firstExternalElectrode + 4, nameExg5);
-	f_setChannelName (firstExternalElectrode + 5, nameExg6);
-	f_setChannelName (firstExternalElectrode + 6, nameExg7);
-	f_setChannelName (firstExternalElectrode + 7, nameExg8);
+	const long firstExternalElectrode = EEG_getNumberOfCapElectrodes (me) + 1;
+	EEG_setChannelName (me, firstExternalElectrode, nameExg1);
+	EEG_setChannelName (me, firstExternalElectrode + 1, nameExg2);
+	EEG_setChannelName (me, firstExternalElectrode + 2, nameExg3);
+	EEG_setChannelName (me, firstExternalElectrode + 3, nameExg4);
+	EEG_setChannelName (me, firstExternalElectrode + 4, nameExg5);
+	EEG_setChannelName (me, firstExternalElectrode + 5, nameExg6);
+	EEG_setChannelName (me, firstExternalElectrode + 6, nameExg7);
+	EEG_setChannelName (me, firstExternalElectrode + 7, nameExg8);
 }
 
-void structEEG :: f_subtractReference (const wchar_t *channelNumber1_text, const wchar_t *channelNumber2_text) {
-	long channelNumber1 = f_getChannelNumber (channelNumber1_text);
+void EEG_subtractReference (EEG me, const wchar_t *channelNumber1_text, const wchar_t *channelNumber2_text) {
+	long channelNumber1 = EEG_getChannelNumber (me, channelNumber1_text);
 	if (channelNumber1 == 0)
-		Melder_throw (this, ": no channel named \"", channelNumber1_text, "\".");
-	long channelNumber2 = f_getChannelNumber (channelNumber2_text);
+		Melder_throw (me, ": no channel named \"", channelNumber1_text, "\".");
+	long channelNumber2 = EEG_getChannelNumber (me, channelNumber2_text);
 	if (channelNumber2 == 0 && channelNumber2_text [0] != '\0')
-		Melder_throw (this, ": no channel named \"", channelNumber2_text, "\".");
-	const long numberOfElectrodeChannels = d_numberOfChannels - f_getNumberOfExtraSensors ();
-	for (long isamp = 1; isamp <= d_sound -> nx; isamp ++) {
-		double referenceValue = channelNumber2 == 0 ? d_sound -> z [channelNumber1] [isamp] :
-			0.5 * (d_sound -> z [channelNumber1] [isamp] + d_sound -> z [channelNumber2] [isamp]);
+		Melder_throw (me, ": no channel named \"", channelNumber2_text, "\".");
+	const long numberOfElectrodeChannels = my numberOfChannels - EEG_getNumberOfExtraSensors (me);
+	for (long isamp = 1; isamp <= my sound -> nx; isamp ++) {
+		double referenceValue = channelNumber2 == 0 ? my sound -> z [channelNumber1] [isamp] :
+			0.5 * (my sound -> z [channelNumber1] [isamp] + my sound -> z [channelNumber2] [isamp]);
 		for (long ichan = 1; ichan <= numberOfElectrodeChannels; ichan ++) {
-			d_sound -> z [ichan] [isamp] -= referenceValue;
+			my sound -> z [ichan] [isamp] -= referenceValue;
 		}
 	}
 }
 
-void structEEG :: f_subtractMeanChannel (long fromChannel, long toChannel) {
-	if (fromChannel < 1 || fromChannel > d_numberOfChannels)
+void EEG_subtractMeanChannel (EEG me, long fromChannel, long toChannel) {
+	if (fromChannel < 1 || fromChannel > my numberOfChannels)
 		Melder_throw ("No channel ", fromChannel, ".");
-	if (toChannel < 1 || toChannel > d_numberOfChannels)
+	if (toChannel < 1 || toChannel > my numberOfChannels)
 		Melder_throw ("No channel ", toChannel, ".");
 	if (fromChannel > toChannel)
 		Melder_throw ("Channel range cannot run from ", fromChannel, " to ", toChannel, ". Please reverse.");
-	const long numberOfElectrodeChannels = d_numberOfChannels - f_getNumberOfExtraSensors ();
-	for (long isamp = 1; isamp <= d_sound -> nx; isamp ++) {
+	const long numberOfElectrodeChannels = my numberOfChannels - EEG_getNumberOfExtraSensors (me);
+	for (long isamp = 1; isamp <= my sound -> nx; isamp ++) {
 		double referenceValue = 0.0;
 		for (long ichan = fromChannel; ichan <= toChannel; ichan ++) {
-			referenceValue += d_sound -> z [ichan] [isamp];
+			referenceValue += my sound -> z [ichan] [isamp];
 		}
 		referenceValue /= (toChannel - fromChannel + 1);
 		for (long ichan = 1; ichan <= numberOfElectrodeChannels; ichan ++) {
-			d_sound -> z [ichan] [isamp] -= referenceValue;
+			my sound -> z [ichan] [isamp] -= referenceValue;
 		}
 	}
 }
 
-void structEEG :: f_setChannelToZero (long channelNumber) {
+void EEG_setChannelToZero (EEG me, long channelNumber) {
 	try {
-		if (channelNumber < 1 || channelNumber > d_numberOfChannels)
+		if (channelNumber < 1 || channelNumber > my numberOfChannels)
 			Melder_throw ("No channel ", channelNumber, ".");
-		long numberOfSamples = d_sound -> nx;
-		double *channel = d_sound -> z [channelNumber];
+		long numberOfSamples = my sound -> nx;
+		double *channel = my sound -> z [channelNumber];
 		for (long isample = 1; isample <= numberOfSamples; isample ++) {
 			channel [isample] = 0.0;
 		}
 	} catch (MelderError) {
-		Melder_throw (this, ": channel ", channelNumber, " not set to zero.");
+		Melder_throw (me, ": channel ", channelNumber, " not set to zero.");
 	}
 }
 
-void structEEG :: f_setChannelToZero (const wchar_t *channelName) {
+void EEG_setChannelToZero (EEG me, const wchar_t *channelName) {
 	try {
-		long channelNumber = f_getChannelNumber (channelName);
+		long channelNumber = EEG_getChannelNumber (me, channelName);
 		if (channelNumber == 0)
 			Melder_throw ("No channel named \"", channelName, "\".");
-		f_setChannelToZero (channelNumber);
+		EEG_setChannelToZero (me, channelNumber);
+	} catch (MelderError) {
+		Melder_throw (me, ": channel ", channelName, " not set to zero.");
+	}
+}
+
+void EEG_removeTriggers (EEG me, int which_Melder_STRING, const wchar_t *criterion) {
+	try {
+		if (my textgrid -> numberOfTiers () < 2 || ! Melder_wcsequ (my textgrid -> tier (2) -> name, L"Trigger"))
+			Melder_throw (me, " does not have a Trigger channel.");
+		my textgrid -> removePoints (2, which_Melder_STRING, criterion);
 	} catch (MelderError) {
-		Melder_throw (this, ": channel ", channelName, " not set to zero.");
+		Melder_throw (me, ": triggers not removed.");
 	}
 }
 
-EEG structEEG :: f_extractChannel (long channelNumber) {
+EEG EEG_extractChannel (EEG me, long channelNumber) {
 	try {
-		if (channelNumber < 1 || channelNumber > d_numberOfChannels)
+		if (channelNumber < 1 || channelNumber > my numberOfChannels)
 			Melder_throw ("No channel ", channelNumber, ".");
-		autoEEG thee = EEG_create (xmin, xmax);
-		thee -> d_numberOfChannels = 1;
-		thee -> d_channelNames = NUMvector <wchar_t *> (1, 1);
-		thee -> d_channelNames [1] = Melder_wcsdup (d_channelNames [1]);
-		thee -> d_sound = Sound_extractChannel (d_sound, channelNumber);
-		thee -> d_textgrid = Data_copy (d_textgrid);
+		autoEEG thee = EEG_create (my xmin, my xmax);
+		thy numberOfChannels = 1;
+		thy channelNames = NUMvector <wchar_t *> (1, 1);
+		thy channelNames [1] = Melder_wcsdup (my channelNames [1]);
+		thy sound = Sound_extractChannel (my sound, channelNumber);
+		thy textgrid = Data_copy (my textgrid);
 		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": channel ", channelNumber, " not extracted.");
+		Melder_throw (me, ": channel ", channelNumber, " not extracted.");
 	}
 }
 
-EEG structEEG :: f_extractChannel (const wchar_t *channelName) {
+EEG EEG_extractChannel (EEG me, const wchar_t *channelName) {
 	try {
-		long channelNumber = f_getChannelNumber (channelName);
+		long channelNumber = EEG_getChannelNumber (me, channelName);
 		if (channelNumber == 0)
 			Melder_throw ("No channel named \"", channelName, "\".");
-		return f_extractChannel (channelNumber);
+		return EEG_extractChannel (me, channelNumber);
 	} catch (MelderError) {
-		Melder_throw (this, ": channel ", channelName, " not extracted.");
+		Melder_throw (me, ": channel ", channelName, " not extracted.");
 	}
 }
 
@@ -477,15 +577,15 @@ EEG EEGs_concatenate (Collection me) {
 		if (my size < 1)
 			Melder_throw ("Cannot concatenate zero EEG objects.");
 		EEG first = (EEG) my item [1];
-		long numberOfChannels = first -> d_numberOfChannels;
-		wchar_t **channelNames = first -> d_channelNames;
+		long numberOfChannels = first -> numberOfChannels;
+		wchar_t **channelNames = first -> channelNames;
 		for (long ieeg = 2; ieeg <= my size; ieeg ++) {
 			EEG other = (EEG) my item [ieeg];
-			if (other -> d_numberOfChannels != numberOfChannels)
+			if (other -> numberOfChannels != numberOfChannels)
 				Melder_throw ("The number of channels of ", other, " does not match the number of channels of ", first, ".");
 			for (long ichan = 1; ichan <= numberOfChannels; ichan ++) {
-				if (! Melder_wcsequ (other -> d_channelNames [ichan], channelNames [ichan]))
-					Melder_throw ("Channel ", ichan, " has a different name in ", other, " (", other -> d_channelNames [ichan], ") than in ", first, " (", channelNames [ichan], ").");
+				if (! Melder_wcsequ (other -> channelNames [ichan], channelNames [ichan]))
+					Melder_throw ("Channel ", ichan, " has a different name in ", other, " (", other -> channelNames [ichan], ") than in ", first, " (", channelNames [ichan], ").");
 			}
 		}
 		autoOrdered soundCollection = Ordered_create ();
@@ -494,50 +594,65 @@ EEG EEGs_concatenate (Collection me) {
 		Collection_dontOwnItems (textgridCollection.peek());
 		for (long ieeg = 1; ieeg <= my size; ieeg ++) {
 			EEG eeg = (EEG) my item [ieeg];
-			Collection_addItem (soundCollection.peek(), eeg -> d_sound);
-			Collection_addItem (textgridCollection.peek(), eeg -> d_textgrid);
+			Collection_addItem (soundCollection.peek(), eeg -> sound);
+			Collection_addItem (textgridCollection.peek(), eeg -> textgrid);
 		}
 		autoEEG thee = Thing_new (EEG);
-		thy d_numberOfChannels = numberOfChannels;
-		thy d_channelNames = NUMvector <wchar_t *> (1, numberOfChannels);
+		thy numberOfChannels = numberOfChannels;
+		thy channelNames = NUMvector <wchar_t *> (1, numberOfChannels);
 		for (long ichan = 1; ichan <= numberOfChannels; ichan ++) {
-			thy d_channelNames [ichan] = Melder_wcsdup (channelNames [ichan]);
+			thy channelNames [ichan] = Melder_wcsdup (channelNames [ichan]);
 		}
-		thy d_sound = Sounds_concatenate_e (soundCollection.peek(), 0.0);
-		thy d_textgrid = TextGrids_concatenate (textgridCollection.peek());
-		thy xmin = thy d_textgrid -> xmin;
-		thy xmax = thy d_textgrid -> xmax;
+		thy sound = Sounds_concatenate_e (soundCollection.peek(), 0.0);
+		thy textgrid = TextGrids_concatenate (textgridCollection.peek());
+		thy xmin = thy textgrid -> xmin;
+		thy xmax = thy textgrid -> xmax;
 		return thee.transfer();
 	} catch (MelderError) {
 		Melder_throw ("TextGrids not concatenated.");
 	}
 }
 
-EEG structEEG :: f_extractPart (double tmin, double tmax, bool preserveTimes) {
+EEG EEG_extractPart (EEG me, double tmin, double tmax, bool preserveTimes) {
 	try {
 		autoEEG thee = Thing_new (EEG);
-		thy d_numberOfChannels = d_numberOfChannels;
-		thy d_channelNames = NUMvector <wchar_t *> (1, d_numberOfChannels);
-		for (long ichan = 1; ichan <= d_numberOfChannels; ichan ++) {
-			thy d_channelNames [ichan] = Melder_wcsdup (d_channelNames [ichan]);
+		thy numberOfChannels = my numberOfChannels;
+		thy channelNames = NUMvector <wchar_t *> (1, my numberOfChannels);
+		for (long ichan = 1; ichan <= my numberOfChannels; ichan ++) {
+			thy channelNames [ichan] = Melder_wcsdup (my channelNames [ichan]);
 		}
-		thy d_sound = Sound_extractPart (d_sound, tmin, tmax, kSound_windowShape_RECTANGULAR, 1.0, preserveTimes);
-		thy d_textgrid = TextGrid_extractPart (d_textgrid, tmin, tmax, preserveTimes);
-		thy xmin = thy d_textgrid -> xmin;
-		thy xmax = thy d_textgrid -> xmax;
+		thy sound = Sound_extractPart (my sound, tmin, tmax, kSound_windowShape_RECTANGULAR, 1.0, preserveTimes);
+		thy textgrid = TextGrid_extractPart (my textgrid, tmin, tmax, preserveTimes);
+		thy xmin = thy textgrid -> xmin;
+		thy xmax = thy textgrid -> xmax;
 		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": part not extracted.");
+		Melder_throw (me, ": part not extracted.");
 	}
 }
 
-void structEEG :: f_replaceTextGrid (TextGrid textgrid) {
+void EEG_replaceTextGrid (EEG me, TextGrid textgrid) {
 	try {
 		autoTextGrid textgrid2 = Data_copy (textgrid);
-		forget (d_textgrid);
-		d_textgrid = textgrid2.transfer();
+		forget (my textgrid);
+		my textgrid = textgrid2.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": TextGrid not replaced with ", textgrid, ".");
+	}
+}
+
+MixingMatrix EEG_to_MixingMatrix (EEG me, long maxNumberOfIterations, double tol, int method) {
+	try {
+		autoCrossCorrelationTables tables = Sound_to_CrossCorrelationTables (my sound, 0.0, 0.0, 0.002, 1);
+		autoMixingMatrix thee = MixingMatrix_create (my sound -> ny, my sound -> ny);
+		for (long ichan = 1; ichan <= my numberOfChannels; ichan ++) {
+			TableOfReal_setRowLabel (thee.peek(), ichan, my channelNames [ichan]);
+			TableOfReal_setColumnLabel (thee.peek(), ichan, Melder_wcscat (L"ic", Melder_integer (ichan)));
+		}
+		MixingMatrix_and_CrossCorrelationTables_improveUnmixing (thee.peek(), tables.peek(), maxNumberOfIterations, tol, method);
+		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": TextGrid not replaced with ", textgrid, ".");
+		Melder_throw (me, ": no MixingMatrix created.");
 	}
 }
 
diff --git a/EEG/EEG.h b/EEG/EEG.h
index dca7b2d..5bc223f 100644
--- a/EEG/EEG.h
+++ b/EEG/EEG.h
@@ -2,7 +2,7 @@
 #define _EEG_h_
 /* EEG.h
  *
- * Copyright (C) 2011-2012 Paul Boersma
+ * Copyright (C) 2011-2012,2014 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
@@ -21,6 +21,7 @@
 
 #include "Sound.h"
 #include "TextGrid.h"
+#include "../dwtools/ICA.h"
 
 #include "EEG_def.h"
 oo_CLASS_CREATE (EEG, Function);
@@ -31,5 +32,34 @@ EEG EEG_readFromBdfFile (MelderFile file);
 
 EEG EEGs_concatenate (Collection me);
 
+void EEG_init (EEG me, double tmin, double tmax);
+long EEG_getChannelNumber (EEG me, const wchar_t *channelName);
+void EEG_setChannelName (EEG me, long channelNumber, const wchar_t *a_name);
+static inline long EEG_getNumberOfCapElectrodes (EEG me) {
+	return (my numberOfChannels - 1) & ~ 15L;   // BUG
+}
+static inline long EEG_getNumberOfExtraSensors (EEG me) {
+	return my numberOfChannels == 1 ? 0 : my numberOfChannels & 1 ? 1 : 8;   // BUG
+}
+static inline long EEG_getNumberOfExternalElectrodes (EEG me) {
+	return my numberOfChannels - EEG_getNumberOfCapElectrodes (me) - EEG_getNumberOfExtraSensors (me);
+}
+void EEG_setExternalElectrodeNames (EEG me, const wchar_t *nameExg1, const wchar_t *nameExg2, const wchar_t *nameExg3, const wchar_t *nameExg4,
+	const wchar_t *nameExg5, const wchar_t *nameExg6, const wchar_t *nameExg7, const wchar_t *nameExg8);
+void EEG_detrend (EEG me);
+void EEG_filter (EEG me, double lowFrequency, double lowWidth, double highFrequency, double highWidth, bool doNotch50Hz);
+void EEG_subtractReference (EEG me, const wchar_t *channelNumber1, const wchar_t *channelNumber2);
+void EEG_subtractMeanChannel (EEG me, long fromChannel, long toChannel);
+void EEG_setChannelToZero (EEG me, long channelNumber);
+void EEG_setChannelToZero (EEG me, const wchar_t *channelName);
+void EEG_removeTriggers (EEG me, int which_Melder_STRING, const wchar_t *criterion);
+EEG EEG_extractChannel (EEG me, long channelNumber);
+EEG EEG_extractChannel (EEG me, const wchar_t *channelName);
+static inline Sound EEG_extractSound (EEG me) { return Data_copy (my sound); }
+static inline TextGrid EEG_extractTextGrid (EEG me) { return Data_copy (my textgrid); }
+EEG EEG_extractPart (EEG me, double tmin, double tmax, bool preserveTimes);
+void EEG_replaceTextGrid (EEG me, TextGrid textgrid);
+MixingMatrix EEG_to_MixingMatrix (EEG me, long maxNumberOfIterations, double tol, int method);
+
 /* End of file EEG.h */
 #endif
diff --git a/EEG/EEGWindow.cpp b/EEG/EEGWindow.cpp
index fe70f4e..af38765 100644
--- a/EEG/EEGWindow.cpp
+++ b/EEG/EEGWindow.cpp
@@ -1,6 +1,6 @@
 /* EEGWindow.cpp
  *
- * Copyright (C) 2011-2012,2013 Paul Boersma
+ * Copyright (C) 2011-2012,2013,2014 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
@@ -41,47 +41,47 @@ void structEEGWindow :: v_createHelpMenuItems (EditorMenu menu) {
 }
 
 const wchar_t * structEEGWindow :: v_getChannelName (long channelNumber) {
-	Melder_assert (d_eeg != NULL);
-	return d_eeg -> d_channelNames [channelNumber];
-}
-
-void structEEGWindow :: f_init (const wchar_t *title, EEG eeg) {
-	d_eeg = eeg;   // before initing, because initing will already draw!
-	structTextGridEditor :: f_init (title, eeg -> d_textgrid, eeg -> d_sound, false, NULL);
+	Melder_assert (our d_eeg != NULL);
+	return our d_eeg -> channelNames [channelNumber];
 }
 
 static void menu_cb_ExtractSelectedEEG_preserveTimes (EDITOR_ARGS) {
 	EDITOR_IAM (EEGWindow);
 	if (my d_endSelection <= my d_startSelection) Melder_throw ("No selection.");
-	autoEEG extract = my d_eeg -> f_extractPart (my d_startSelection, my d_endSelection, true);
+	autoEEG extract = EEG_extractPart (my d_eeg, my d_startSelection, my d_endSelection, true);
 	my broadcastPublication (extract.transfer());
 }
 
 static void menu_cb_ExtractSelectedEEG_timeFromZero (EDITOR_ARGS) {
 	EDITOR_IAM (EEGWindow);
 	if (my d_endSelection <= my d_startSelection) Melder_throw ("No selection.");
-	autoEEG extract = my d_eeg -> f_extractPart (my d_startSelection, my d_endSelection, false);
+	autoEEG extract = EEG_extractPart (my d_eeg, my d_startSelection, my d_endSelection, false);
 	my broadcastPublication (extract.transfer());
 }
 
 void structEEGWindow :: v_createMenuItems_file_extract (EditorMenu menu) {
 	EEGWindow_Parent :: v_createMenuItems_file_extract (menu);
-	d_extractSelectedEEGPreserveTimesButton =
+	our d_extractSelectedEEGPreserveTimesButton =
 		EditorMenu_addCommand (menu, L"Extract selected EEG (preserve times)", 0, menu_cb_ExtractSelectedEEG_preserveTimes);
-	d_extractSelectedEEGTimeFromZeroButton =
+	our d_extractSelectedEEGTimeFromZeroButton =
 		EditorMenu_addCommand (menu, L"Extract selected EEG (time from zero)", 0, menu_cb_ExtractSelectedEEG_timeFromZero);
 }
 
 void structEEGWindow :: v_updateMenuItems_file () {
 	EEGWindow_Parent :: v_updateMenuItems_file ();
-	d_extractSelectedEEGPreserveTimesButton -> f_setSensitive (d_endSelection > d_startSelection);
-	d_extractSelectedEEGTimeFromZeroButton -> f_setSensitive (d_endSelection > d_startSelection);
+	our d_extractSelectedEEGPreserveTimesButton -> f_setSensitive (d_endSelection > d_startSelection);
+	our d_extractSelectedEEGTimeFromZeroButton -> f_setSensitive (d_endSelection > d_startSelection);
+}
+
+void EEGWindow_init (EEGWindow me, const wchar_t *title, EEG eeg) {
+	my d_eeg = eeg;   // before initing, because initing will already draw!
+	TextGridEditor_init (me, title, eeg -> textgrid, eeg -> sound, false, NULL, NULL);
 }
 
 EEGWindow EEGWindow_create (const wchar_t *title, EEG eeg) {
 	try {
 		autoEEGWindow me = Thing_new (EEGWindow);
-		my f_init (title, eeg);
+		EEGWindow_init (me.peek(), title, eeg);
 		return me.transfer();
 	} catch (MelderError) {
 		Melder_throw ("EEG window not created.");
diff --git a/EEG/EEGWindow.h b/EEG/EEGWindow.h
index 36cd5dc..84274b7 100644
--- a/EEG/EEGWindow.h
+++ b/EEG/EEGWindow.h
@@ -2,7 +2,7 @@
 #define _EEGWindow_h_
 /* EEGWindow.h
  *
- * Copyright (C) 2011-2012,2013 Paul Boersma
+ * Copyright (C) 2011-2012,2013,2014 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
@@ -26,8 +26,6 @@ Thing_define (EEGWindow, TextGridEditor) { public:
 	// new data:
 		EEG d_eeg;
 		GuiMenuItem d_extractSelectedEEGPreserveTimesButton, d_extractSelectedEEGTimeFromZeroButton;
-	// functions:
-		void f_init (const wchar_t *title, EEG eeg);
 	// overridden methods:
 		virtual bool v_hasPitch     () { return false; }
 		virtual bool v_hasIntensity () { return false; }
@@ -41,6 +39,8 @@ Thing_define (EEGWindow, TextGridEditor) { public:
 	#include "EEGWindow_prefs.h"
 };
 
+void EEGWindow_init (EEGWindow me, const wchar_t *title, EEG eeg);
+
 EEGWindow EEGWindow_create (const wchar_t *title, EEG eeg);
 
 /* End of file EEGWindow.h */
diff --git a/EEG/EEG_def.h b/EEG/EEG_def.h
index 81c8241..635316d 100644
--- a/EEG/EEG_def.h
+++ b/EEG/EEG_def.h
@@ -1,6 +1,6 @@
 /* EEG_def.h
  *
- * Copyright (C) 2011 Paul Boersma
+ * Copyright (C) 2011,2014 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
@@ -21,34 +21,12 @@
 #define ooSTRUCT EEG
 oo_DEFINE_CLASS (EEG, Function)
 
-	oo_LONG (d_numberOfChannels)
-	oo_STRING_VECTOR (d_channelNames, d_numberOfChannels)
-	oo_OBJECT (Sound, 2, d_sound)
-	oo_OBJECT (TextGrid, 0, d_textgrid)
+	oo_LONG (numberOfChannels)
+	oo_STRING_VECTOR (channelNames, numberOfChannels)
+	oo_OBJECT (Sound, 2, sound)
+	oo_OBJECT (TextGrid, 0, textgrid)
 
 	#if oo_DECLARING
-		// functions:
-		public:
-			void f_init (double tmin, double tmax);
-			long f_getChannelNumber (const wchar_t *channelName);
-			void f_setChannelName (long channelNumber, const wchar_t *a_name);
-			long f_getNumberOfCapElectrodes () { return (d_numberOfChannels - 1) & ~ 15L; }   // BUG
-			long f_getNumberOfExternalElectrodes () { return d_numberOfChannels - f_getNumberOfCapElectrodes () - f_getNumberOfExtraSensors (); }
-			long f_getNumberOfExtraSensors () { return d_numberOfChannels & 1 ? 1 : 8; }   // BUG
-			void f_setExternalElectrodeNames (const wchar_t *nameExg1, const wchar_t *nameExg2, const wchar_t *nameExg3, const wchar_t *nameExg4,
-				const wchar_t *nameExg5, const wchar_t *nameExg6, const wchar_t *nameExg7, const wchar_t *nameExg8);
-			void f_detrend ();
-			void f_filter (double lowFrequency, double lowWidth, double highFrequency, double highWidth, bool doNotch50Hz);
-			void f_subtractReference (const wchar_t *channelNumber1, const wchar_t *channelNumber2);
-			void f_subtractMeanChannel (long fromChannel, long toChannel);
-			void f_setChannelToZero (long channelNumber);
-			void f_setChannelToZero (const wchar_t *channelName);
-			EEG f_extractChannel (long channelNumber);
-			EEG f_extractChannel (const wchar_t *channelName);
-			Sound f_extractSound () { return Data_copy (d_sound); }
-			TextGrid f_extractTextGrid () { return Data_copy (d_textgrid); }
-			EEG f_extractPart (double tmin, double tmax, bool preserveTimes);
-			void f_replaceTextGrid (TextGrid textgrid);
 		// overridden methods:
 		protected:
 			virtual void v_info ();
diff --git a/EEG/ERP.cpp b/EEG/ERP.cpp
index a7cd681..f26ec7c 100644
--- a/EEG/ERP.cpp
+++ b/EEG/ERP.cpp
@@ -1,6 +1,6 @@
 /* ERP.cpp
  *
- * Copyright (C) 2011-2012 Paul Boersma
+ * Copyright (C) 2011-2012,2013,2014 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
@@ -42,34 +42,34 @@
 
 Thing_implement (ERP, Sound, 2);
 
-long structERP :: f_getChannelNumber (const wchar_t *channelName) {
-	for (long ichan = 1; ichan <= ny; ichan ++) {
-		if (Melder_wcsequ (d_channelNames [ichan], channelName)) {
+long ERP_getChannelNumber (ERP me, const wchar_t *channelName) {
+	for (long ichan = 1; ichan <= my ny; ichan ++) {
+		if (Melder_wcsequ (my channelNames [ichan], channelName)) {
 			return ichan;
 		}
 	}
 	return 0;
 }
 
-void structERP :: f_draw (Graphics graphics, long channelNumber, double tmin, double tmax, double vmin, double vmax, bool garnish) {
-	if (channelNumber < 1 || channelNumber > this -> ny) return;
+void ERP_drawChannel_number (ERP me, Graphics graphics, long channelNumber, double tmin, double tmax, double vmin, double vmax, bool garnish) {
+	if (channelNumber < 1 || channelNumber > my ny) return;
 	/*
 	 * Automatic domain.
 	 */
 	if (tmin == tmax) {
-		tmin = this -> xmin;
-		tmax = this -> xmax;
+		tmin = my xmin;
+		tmax = my xmax;
 	}
 	/*
 	 * Domain expressed in sample numbers.
 	 */
 	long ixmin, ixmax;
-	Matrix_getWindowSamplesX (this, tmin, tmax, & ixmin, & ixmax);
+	Matrix_getWindowSamplesX (me, tmin, tmax, & ixmin, & ixmax);
 	/*
 	 * Automatic vertical range.
 	 */
 	if (vmin == vmax) {
-		Matrix_getWindowExtrema (this, ixmin, ixmax, channelNumber, channelNumber, & vmin, & vmax);
+		Matrix_getWindowExtrema (me, ixmin, ixmax, channelNumber, channelNumber, & vmin, & vmax);
 		if (vmin == vmax) {
 			vmin -= 1.0;
 			vmax += 1.0;
@@ -80,11 +80,11 @@ void structERP :: f_draw (Graphics graphics, long channelNumber, double tmin, do
 	 */
 	Graphics_setInner (graphics);
 	Graphics_setWindow (graphics, tmin, tmax, vmin, vmax);
-	Graphics_function (graphics, this -> z [channelNumber], ixmin, ixmax, Matrix_columnToX (this, ixmin), Matrix_columnToX (this, ixmax));
+	Graphics_function (graphics, my z [channelNumber], ixmin, ixmax, Matrix_columnToX (me, ixmin), Matrix_columnToX (me, ixmax));
 	Graphics_unsetInner (graphics);
 	if (garnish) {
 		Graphics_drawInnerBox (graphics);
-		Graphics_textTop (graphics, true, Melder_wcscat (L"Channel ", d_channelNames [channelNumber]));
+		Graphics_textTop (graphics, true, Melder_wcscat (L"Channel ", my channelNames [channelNumber]));
 		Graphics_textBottom (graphics, true, L"Time (s)");
 		Graphics_marksBottom (graphics, 2, true, true, false);
 		if (0.0 > tmin && 0.0 < tmax)
@@ -99,12 +99,11 @@ void structERP :: f_draw (Graphics graphics, long channelNumber, double tmin, do
 
 }
 
-void structERP :: f_draw (Graphics graphics, const wchar_t *channelName, double tmin, double tmax, double vmin, double vmax, bool garnish) {
-	f_draw (graphics, f_getChannelNumber (channelName), tmin, tmax, vmin, vmax, garnish);
+void ERP_drawChannel_name (ERP me, Graphics graphics, const wchar_t *channelName, double tmin, double tmax, double vmin, double vmax, bool garnish) {
+	ERP_drawChannel_number (me, graphics, ERP_getChannelNumber (me, channelName), tmin, tmax, vmin, vmax, garnish);
 }
 
-Table structERP :: f_tabulate (bool includeSampleNumbers, bool includeTime, int timeDecimals, int voltageDecimals, int units)
-{
+Table ERP_tabulate (ERP me, bool includeSampleNumbers, bool includeTime, int timeDecimals, int voltageDecimals, int units) {
 	double voltageScaling = 1.0;
 	const wchar_t *unitText = L"(V)";
 	if (units == 2) {
@@ -113,34 +112,34 @@ Table structERP :: f_tabulate (bool includeSampleNumbers, bool includeTime, int
 		unitText = L"(uV)";
 	}
 	try {
-		autoTable thee = Table_createWithoutColumnNames (nx, includeSampleNumbers + includeTime + ny);
+		autoTable thee = Table_createWithoutColumnNames (my nx, includeSampleNumbers + includeTime + my ny);
 		long icol = 0;
 		if (includeSampleNumbers) Table_setColumnLabel (thee.peek(), ++ icol, L"sample");
 		if (includeTime) Table_setColumnLabel (thee.peek(), ++ icol, L"time(s)");
-		for (long ichan = 1; ichan <= ny; ichan ++) {
-			Table_setColumnLabel (thee.peek(), ++ icol, Melder_wcscat (d_channelNames [ichan], unitText));
+		for (long ichan = 1; ichan <= my ny; ichan ++) {
+			Table_setColumnLabel (thee.peek(), ++ icol, Melder_wcscat (my channelNames [ichan], unitText));
 		}
-		for (long isamp = 1; isamp <= nx; isamp ++) {
+		for (long isamp = 1; isamp <= my nx; isamp ++) {
 			icol = 0;
 			if (includeSampleNumbers) Table_setNumericValue (thee.peek(), isamp, ++ icol, isamp);
-			if (includeTime) Table_setStringValue (thee.peek(), isamp, ++ icol, Melder_fixed (x1 + (isamp - 1) * dx, timeDecimals));
-			for (long ichan = 1; ichan <= ny; ichan ++) {
-				Table_setStringValue (thee.peek(), isamp, ++ icol, Melder_fixed (voltageScaling * z [ichan] [isamp], voltageDecimals));
+			if (includeTime) Table_setStringValue (thee.peek(), isamp, ++ icol, Melder_fixed (my x1 + (isamp - 1) * my dx, timeDecimals));
+			for (long ichan = 1; ichan <= my ny; ichan ++) {
+				Table_setStringValue (thee.peek(), isamp, ++ icol, Melder_fixed (voltageScaling * my z [ichan] [isamp], voltageDecimals));
 			}
 		}
 		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": not converted to Table.");
+		Melder_throw (me, ": not converted to Table.");
 	}
 }
 
-Sound structERP :: f_downToSound () {
+Sound ERP_downto_Sound (ERP me) {
 	try {
 		autoSound thee = Thing_new (Sound);
-		structSound :: v_copy (thee.peek());
+		my structSound :: v_copy (thee.peek());
 		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": not converted to Sound.");
+		Melder_throw (me, ": not converted to Sound.");
 	}
 }
 
diff --git a/EEG/ERP.h b/EEG/ERP.h
index bff71e7..39ae252 100644
--- a/EEG/ERP.h
+++ b/EEG/ERP.h
@@ -2,7 +2,7 @@
 #define _ERP_h_
 /* ERP.h
  *
- * Copyright (C) 2011 Paul Boersma
+ * Copyright (C) 2011,2014 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
@@ -24,5 +24,30 @@
 #include "ERP_def.h"
 oo_CLASS_CREATE (ERP, Sound);
 
+/**
+	Look up the channel number from its name.
+*/
+long ERP_getChannelNumber (ERP me, const wchar_t *channelName);
+
+/**
+ * Draw the scalp distribution.
+ * @param tmin: the time window.
+ * @param tmax: the time window.
+ */
+void ERP_drawScalp (ERP me,
+	Graphics graphics, double tmin, double tmax, double vmin, double vmax,
+	enum kGraphics_colourScale colourScale, bool garnish);
+void ERP_drawScalp_garnish (Graphics graphics, double vmin, double vmax, enum kGraphics_colourScale colourScale);
+
+void ERP_drawChannel_number (ERP me, Graphics graphics, long channelNumber, double tmin, double tmax, double vmin, double vmax, bool garnish);
+void ERP_drawChannel_name (ERP me, Graphics graphics, const wchar_t *channelName, double tmin, double tmax, double vmin, double vmax, bool garnish);
+
+Table ERP_tabulate (ERP me, bool includeSampleNumbers, bool includeTime, int timeDecimals, int voltageDecimals, int units);
+
+/**
+	Extract the Sound part from the ERP. The channel names are lost.
+*/
+Sound ERP_downto_Sound (ERP me);
+
 /* End of file ERP.h */
 #endif
diff --git a/EEG/ERPTier.cpp b/EEG/ERPTier.cpp
index 1c37e3f..a12d465 100644
--- a/EEG/ERPTier.cpp
+++ b/EEG/ERPTier.cpp
@@ -1,6 +1,6 @@
 /* ERPTier.cpp
  *
- * Copyright (C) 2011-2012 Paul Boersma
+ * Copyright (C) 2011-2012,2014 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
@@ -48,50 +48,50 @@ Thing_implement (ERPTier, Function, 0);
 
 void structERPTier :: v_shiftX (double xfrom, double xto) {
 	ERPTier_Parent :: v_shiftX (xfrom, xto);
-	//if (d_sound    != NULL)  Function_shiftXTo (d_sound,    xfrom, xto);
-	//if (d_textgrid != NULL)  Function_shiftXTo (d_textgrid, xfrom, xto);
+	//if (our sound    != NULL)  Function_shiftXTo (our sound,    xfrom, xto);
+	//if (our textgrid != NULL)  Function_shiftXTo (our textgrid, xfrom, xto);
 }
 
 void structERPTier :: v_scaleX (double xminfrom, double xmaxfrom, double xminto, double xmaxto) {
 	ERPTier_Parent :: v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
-	//if (d_sound    != NULL)  d_sound    -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
-	//if (d_textgrid != NULL)  d_textgrid -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
+	//if (our sound    != NULL)  our sound    -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
+	//if (our textgrid != NULL)  our textgrid -> v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
 }
 
-long structERPTier :: f_getChannelNumber (const wchar_t *channelName) {
-	for (long ichan = 1; ichan <= d_numberOfChannels; ichan ++) {
-		if (Melder_wcsequ (d_channelNames [ichan], channelName)) {
+long ERPTier_getChannelNumber (ERPTier me, const wchar_t *channelName) {
+	for (long ichan = 1; ichan <= my numberOfChannels; ichan ++) {
+		if (Melder_wcsequ (my channelNames [ichan], channelName)) {
 			return ichan;
 		}
 	}
 	return 0;
 }
 
-double structERPTier :: f_getMean (long pointNumber, long channelNumber, double tmin, double tmax) {
-	if (pointNumber < 1 || pointNumber > d_events -> size) return NUMundefined;
-	if (channelNumber < 1 || channelNumber > d_numberOfChannels) return NUMundefined;
-	ERPPoint point = f_peekEvent (pointNumber);
-	return Vector_getMean (point -> d_erp, tmin, tmax, channelNumber);
+double ERPTier_getMean (ERPTier me, long pointNumber, long channelNumber, double tmin, double tmax) {
+	if (pointNumber < 1 || pointNumber > my events -> size) return NUMundefined;
+	if (channelNumber < 1 || channelNumber > my numberOfChannels) return NUMundefined;
+	ERPPoint point = my event (pointNumber);
+	return Vector_getMean (point -> erp, tmin, tmax, channelNumber);
 }
 
-double structERPTier :: f_getMean (long pointNumber, const wchar_t *channelName, double tmin, double tmax) {
-	return f_getMean (pointNumber, f_getChannelNumber (channelName), tmin, tmax);
+double ERPTier_getMean (ERPTier me, long pointNumber, const wchar_t *channelName, double tmin, double tmax) {
+	return ERPTier_getMean (me, pointNumber, ERPTier_getChannelNumber (me, channelName), tmin, tmax);
 }
 
-ERPTier EEG_to_ERPTier (EEG me, double fromTime, double toTime, int markerBit) {
+static ERPTier EEG_PointProcess_to_ERPTier (EEG me, PointProcess events, double fromTime, double toTime) {
 	try {
 		autoERPTier thee = Thing_new (ERPTier);
 		Function_init (thee.peek(), fromTime, toTime);
-		thy d_numberOfChannels = my d_numberOfChannels - my f_getNumberOfExtraSensors ();
-		thy d_channelNames = NUMvector <wchar_t *> (1, thy d_numberOfChannels);
-		for (long ichan = 1; ichan <= thy d_numberOfChannels; ichan ++) {
-			thy d_channelNames [ichan] = Melder_wcsdup (my d_channelNames [ichan]);
+		thy numberOfChannels = my numberOfChannels - EEG_getNumberOfExtraSensors (me);
+		Melder_assert (thy numberOfChannels > 0);
+		thy channelNames = NUMvector <wchar_t *> (1, thy numberOfChannels);
+		for (long ichan = 1; ichan <= thy numberOfChannels; ichan ++) {
+			thy channelNames [ichan] = Melder_wcsdup (my channelNames [ichan]);
 		}
-		autoPointProcess events = TextGrid_getStartingPoints (my d_textgrid, markerBit, kMelder_string_EQUAL_TO, L"1");
 		long numberOfEvents = events -> nt;
-		thy d_events = SortedSetOfDouble_create ();
+		thy events = SortedSetOfDouble_create ();
 		double soundDuration = toTime - fromTime;
-		double samplingPeriod = my d_sound -> dx;
+		double samplingPeriod = my sound -> dx;
 		long numberOfSamples = floor (soundDuration / samplingPeriod) + 1;
 		if (numberOfSamples < 1)
 			Melder_throw (L"Time window too short.");
@@ -102,18 +102,18 @@ ERPTier EEG_to_ERPTier (EEG me, double fromTime, double toTime, int markerBit) {
 			double eegEventTime = events -> t [ievent];
 			autoERPPoint event = Thing_new (ERPPoint);
 			event -> number = eegEventTime;
-			event -> d_erp = Sound_create (thy d_numberOfChannels, fromTime, toTime, numberOfSamples, samplingPeriod, firstTime);
+			event -> erp = Sound_create (thy numberOfChannels, fromTime, toTime, numberOfSamples, samplingPeriod, firstTime);
 			double erpEventTime = 0.0;
-			double eegSample = 1 + (eegEventTime - my d_sound -> x1) / samplingPeriod;
+			double eegSample = 1 + (eegEventTime - my sound -> x1) / samplingPeriod;
 			double erpSample = 1 + (erpEventTime - firstTime) / samplingPeriod;
 			long sampleDifference = round (eegSample - erpSample);
-			for (long ichannel = 1; ichannel <= thy d_numberOfChannels; ichannel ++) {
+			for (long ichannel = 1; ichannel <= thy numberOfChannels; ichannel ++) {
 				for (long isample = 1; isample <= numberOfSamples; isample ++) {
 					long jsample = isample + sampleDifference;
-					event -> d_erp -> z [ichannel] [isample] = jsample < 1 || jsample > my d_sound -> nx ? 0.0 : my d_sound -> z [ichannel] [jsample];
+					event -> erp -> z [ichannel] [isample] = jsample < 1 || jsample > my sound -> nx ? 0.0 : my sound -> z [ichannel] [jsample];
 				}
 			}
-			Collection_addItem (thy d_events, event.transfer());
+			Collection_addItem (thy events, event.transfer());
 		}
 		return thee.transfer();
 	} catch (MelderError) {
@@ -121,18 +121,98 @@ ERPTier EEG_to_ERPTier (EEG me, double fromTime, double toTime, int markerBit) {
 	}
 }
 
-void structERPTier :: f_subtractBaseline (double tmin, double tmax) {
-	long numberOfEvents = d_events -> size;
+ERPTier EEG_to_ERPTier_bit (EEG me, double fromTime, double toTime, int markerBit) {
+	try {
+		autoPointProcess events = TextGrid_getStartingPoints (my textgrid, markerBit, kMelder_string_EQUAL_TO, L"1");
+		autoERPTier thee = EEG_PointProcess_to_ERPTier (me, events.peek(), fromTime, toTime);
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": ERPTier not created.");
+	}
+}
+
+static PointProcess TextGrid_getStartingPoints_multiNumeric (TextGrid me, uint16_t number) {
+	try {
+		autoPointProcess thee = NULL;
+		int numberOfBits = my numberOfTiers();
+		for (int ibit = 0; ibit < numberOfBits; ibit ++) {
+			(void) TextGrid_checkSpecifiedTierIsIntervalTier (me, ibit + 1);
+			if (number & (1 << ibit)) {
+				autoPointProcess bitEvents = TextGrid_getStartingPoints (me, ibit + 1, kMelder_string_EQUAL_TO, L"1");
+				if (thee.peek()) {
+					autoPointProcess intersection = PointProcesses_intersection (thee.peek(), bitEvents.peek());
+					thee.reset (intersection.transfer());
+				} else {
+					thee.reset (bitEvents.transfer());
+				}
+			}
+		}
+		for (int ibit = 0; ibit < numberOfBits; ibit ++) {
+			autoPointProcess bitEvents = TextGrid_getStartingPoints (me, ibit + 1, kMelder_string_EQUAL_TO, L"1");
+			if (! (number & (1 << ibit))) {
+				if (thee.peek()) {
+					autoPointProcess difference = PointProcesses_difference (thee.peek(), bitEvents.peek());
+					thee.reset (difference.transfer());
+				} else {
+					thee.reset (PointProcess_create (my xmin, my xmax, 10));
+				}
+			}
+		}
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": starting points not converted to PointProcess.");
+	}
+}
+
+ERPTier EEG_to_ERPTier_marker (EEG me, double fromTime, double toTime, uint16_t marker) {
+	try {
+		autoPointProcess events = TextGrid_getStartingPoints_multiNumeric (my textgrid, marker);
+		autoERPTier thee = EEG_PointProcess_to_ERPTier (me, events.peek(), fromTime, toTime);
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": ERPTier not created.");
+	}
+}
+
+ERPTier EEG_to_ERPTier_triggers (EEG me, double fromTime, double toTime,
+	int which_Melder_STRING, const wchar_t *criterion)
+{
+	try {
+		autoPointProcess events = TextGrid_getPoints (my textgrid, 2, which_Melder_STRING, criterion);
+		autoERPTier thee = EEG_PointProcess_to_ERPTier (me, events.peek(), fromTime, toTime);
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": ERPTier not created.");
+	}
+}
+
+ERPTier EEG_to_ERPTier_triggers_preceded (EEG me, double fromTime, double toTime,
+	int which_Melder_STRING, const wchar_t *criterion,
+	int which_Melder_STRING_precededBy, const wchar_t *criterion_precededBy)
+{
+	try {
+		autoPointProcess events = TextGrid_getPoints_preceded (my textgrid, 2,
+			which_Melder_STRING, criterion,
+			which_Melder_STRING_precededBy, criterion_precededBy);
+		autoERPTier thee = EEG_PointProcess_to_ERPTier (me, events.peek(), fromTime, toTime);
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": ERPTier not created.");
+	}
+}
+
+void ERPTier_subtractBaseline (ERPTier me, double tmin, double tmax) {
+	long numberOfEvents = my events -> size;
 	if (numberOfEvents < 1)
 		return;   // nothing to do
-	ERPPoint firstEvent = f_peekEvent (1);
-	long numberOfChannels = firstEvent -> d_erp -> ny;
-	long numberOfSamples = firstEvent -> d_erp -> nx;
+	ERPPoint firstEvent = my event (1);
+	long numberOfChannels = firstEvent -> erp -> ny;
+	long numberOfSamples = firstEvent -> erp -> nx;
 	for (long ievent = 1; ievent <= numberOfEvents; ievent ++) {
-		ERPPoint event = f_peekEvent (ievent);
+		ERPPoint event = my event (ievent);
 		for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
-			double mean = Vector_getMean (event -> d_erp, tmin, tmax, ichannel);
-			double *channel = event -> d_erp -> z [ichannel];
+			double mean = Vector_getMean (event -> erp, tmin, tmax, ichannel);
+			double *channel = event -> erp -> z [ichannel];
 			for (long isample = 1; isample <= numberOfSamples; isample ++) {
 				channel [isample] -= mean;
 			}
@@ -140,21 +220,21 @@ void structERPTier :: f_subtractBaseline (double tmin, double tmax) {
 	}
 }
 
-void structERPTier :: f_rejectArtefacts (double threshold) {
-	long numberOfEvents = d_events -> size;
+void ERPTier_rejectArtefacts (ERPTier me, double threshold) {
+	long numberOfEvents = my events -> size;
 	if (numberOfEvents < 1)
 		return;   // nothing to do
-	ERPPoint firstEvent = f_peekEvent (1);
-	long numberOfChannels = firstEvent -> d_erp -> ny;
-	long numberOfSamples = firstEvent -> d_erp -> nx;
+	ERPPoint firstEvent = my event (1);
+	long numberOfChannels = firstEvent -> erp -> ny;
+	long numberOfSamples = firstEvent -> erp -> nx;
 	if (numberOfSamples < 1)
 		return;   // nothing to do
 	for (long ievent = numberOfEvents; ievent >= 1; ievent --) {   // cycle down because of removal
-		ERPPoint event = f_peekEvent (ievent);
-		double minimum = event -> d_erp -> z [1] [1];
+		ERPPoint event = my event (ievent);
+		double minimum = event -> erp -> z [1] [1];
 		double maximum = minimum;
 		for (long ichannel = 1; ichannel <= (numberOfChannels & ~ 15); ichannel ++) {
-			double *channel = event -> d_erp -> z [ichannel];
+			double *channel = event -> erp -> z [ichannel];
 			for (long isample = 1; isample <= numberOfSamples; isample ++) {
 				double value = channel [isample];
 				if (value < minimum) minimum = value;
@@ -162,53 +242,53 @@ void structERPTier :: f_rejectArtefacts (double threshold) {
 			}
 		}
 		if (minimum < - threshold || maximum > threshold) {
-			Collection_removeItem (d_events, ievent);
+			Collection_removeItem (my events, ievent);
 		}
 	}
 }
 
-ERP structERPTier :: f_extractERP (long eventNumber) {
+ERP ERPTier_extractERP (ERPTier me, long eventNumber) {
 	try {
-		long numberOfEvents = d_events -> size;
+		long numberOfEvents = my events -> size;
 		if (numberOfEvents < 1)
 			Melder_throw ("No events.");
-		f_checkEventNumber (eventNumber);
-		ERPPoint event = f_peekEvent (eventNumber);
-		long numberOfChannels = event -> d_erp -> ny;
-		long numberOfSamples = event -> d_erp -> nx;
+		ERPTier_checkEventNumber (me, eventNumber);
+		ERPPoint event = my event (eventNumber);
+		long numberOfChannels = event -> erp -> ny;
+		long numberOfSamples = event -> erp -> nx;
 		autoERP thee = Thing_new (ERP);
-		event -> d_erp -> structSound :: v_copy (thee.peek());
+		event -> erp -> structSound :: v_copy (thee.peek());
 		for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
-			double *oldChannel = event -> d_erp -> z [ichannel];
+			double *oldChannel = event -> erp -> z [ichannel];
 			double *newChannel = thy z [ichannel];
 			for (long isample = 1; isample <= numberOfSamples; isample ++) {
 				newChannel [isample] = oldChannel [isample];
 			}
 		}
-		thy d_channelNames = NUMvector <wchar_t *> (1, thy ny);
+		thy channelNames = NUMvector <wchar_t *> (1, thy ny);
 		for (long ichan = 1; ichan <= thy ny; ichan ++) {
-			thy d_channelNames [ichan] = Melder_wcsdup (d_channelNames [ichan]);
+			thy channelNames [ichan] = Melder_wcsdup (my channelNames [ichan]);
 		}
 		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": ERP not extracted.");
+		Melder_throw (me, ": ERP not extracted.");
 	}
 }
 
-ERP structERPTier :: f_toERP_mean () {
+ERP ERPTier_to_ERP_mean (ERPTier me) {
 	try {
-		long numberOfEvents = d_events -> size;
+		long numberOfEvents = my events -> size;
 		if (numberOfEvents < 1)
 			Melder_throw ("No events.");
-		ERPPoint firstEvent = f_peekEvent (1);
-		long numberOfChannels = firstEvent -> d_erp -> ny;
-		long numberOfSamples = firstEvent -> d_erp -> nx;
+		ERPPoint firstEvent = my event (1);
+		long numberOfChannels = firstEvent -> erp -> ny;
+		long numberOfSamples = firstEvent -> erp -> nx;
 		autoERP mean = Thing_new (ERP);
-		firstEvent -> d_erp -> structSound :: v_copy (mean.peek());
+		firstEvent -> erp -> structSound :: v_copy (mean.peek());
 		for (long ievent = 2; ievent <= numberOfEvents; ievent ++) {
-			ERPPoint event = f_peekEvent (ievent);
+			ERPPoint event = my event (ievent);
 			for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
-				double *erpChannel = event -> d_erp -> z [ichannel];
+				double *erpChannel = event -> erp -> z [ichannel];
 				double *meanChannel = mean -> z [ichannel];
 				for (long isample = 1; isample <= numberOfSamples; isample ++) {
 					meanChannel [isample] += erpChannel [isample];
@@ -222,76 +302,79 @@ ERP structERPTier :: f_toERP_mean () {
 				meanChannel [isample] *= factor;
 			}
 		}
-		mean -> d_channelNames = NUMvector <wchar_t *> (1, mean -> ny);
+		mean -> channelNames = NUMvector <wchar_t *> (1, mean -> ny);
 		for (long ichan = 1; ichan <= mean -> ny; ichan ++) {
-			mean -> d_channelNames [ichan] = Melder_wcsdup (d_channelNames [ichan]);
+			mean -> channelNames [ichan] = Melder_wcsdup (my channelNames [ichan]);
 		}
 		return mean.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": mean not computed.");
+		Melder_throw (me, ": mean not computed.");
 	}
 }
 
-ERPTier structERPTier :: f_extractEventsWhereColumn_number (Table table, long columnNumber, int which_Melder_NUMBER, double criterion) {
+ERPTier ERPTier_extractEventsWhereColumn_number (ERPTier me, Table table, long columnNumber, int which_Melder_NUMBER, double criterion) {
 	try {
 		Table_checkSpecifiedColumnNumberWithinRange (table, columnNumber);
 		Table_numericize_Assert (table, columnNumber);   // extraction should work even if cells are not defined
-		if (d_events -> size != table -> rows -> size)
-			Melder_throw (this, " & ", table, ": the number of rows in the table (", table -> rows -> size, ") doesn't match the number of events (", d_events -> size, ").");
+		if (my events -> size != table -> rows -> size)
+			Melder_throw (me, " & ", table, ": the number of rows in the table (", table -> rows -> size,
+				") doesn't match the number of events (", my events -> size, ").");
 		autoERPTier thee = Thing_new (ERPTier);
-		Function_init (thee.peek(), this -> xmin, this -> xmax);
-		thy d_numberOfChannels = this -> d_numberOfChannels;
-		thy d_channelNames = NUMvector <wchar_t *> (1, thy d_numberOfChannels);
-		for (long ichan = 1; ichan <= thy d_numberOfChannels; ichan ++) {
-			thy d_channelNames [ichan] = Melder_wcsdup (this -> d_channelNames [ichan]);
+		Function_init (thee.peek(), my xmin, my xmax);
+		thy numberOfChannels = my numberOfChannels;
+		thy channelNames = NUMvector <wchar_t *> (1, thy numberOfChannels);
+		for (long ichan = 1; ichan <= thy numberOfChannels; ichan ++) {
+			thy channelNames [ichan] = Melder_wcsdup (my channelNames [ichan]);
 		}
-		thy d_events = SortedSetOfDouble_create ();
-		for (long ievent = 1; ievent <= d_events -> size; ievent ++) {
-			ERPPoint oldEvent = this -> f_peekEvent (ievent);
+		thy events = SortedSetOfDouble_create ();
+		for (long ievent = 1; ievent <= my events -> size; ievent ++) {
+			ERPPoint oldEvent = my event (ievent);
 			TableRow row = table -> f_peekRow (ievent);
 			if (Melder_numberMatchesCriterion (row -> cells [columnNumber]. number, which_Melder_NUMBER, criterion)) {
 				autoERPPoint newEvent = Data_copy (oldEvent);
-				Collection_addItem (thy d_events, newEvent.transfer());
+				Collection_addItem (thy events, newEvent.transfer());
 			}
 		}
-		if (thy d_events -> size == 0) {
+		if (thy events -> size == 0) {
 			Melder_warning ("No event matches criterion.");
 		}
 		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": events not extracted.");
+		Melder_throw (me, ": events not extracted.");
 	}
 }
 
-ERPTier structERPTier :: f_extractEventsWhereColumn_string (Table table, long columnNumber, int which_Melder_STRING, const wchar_t *criterion) {
+ERPTier ERPTier_extractEventsWhereColumn_string (ERPTier me, Table table,
+	long columnNumber, int which_Melder_STRING, const wchar_t *criterion)
+{
 	try {
 		Table_checkSpecifiedColumnNumberWithinRange (table, columnNumber);
-		if (d_events -> size != table -> rows -> size)
-			Melder_throw (this, " & ", table, ": the number of rows in the table (", table -> rows -> size, ") doesn't match the number of events (", d_events -> size, ").");
+		if (my events -> size != table -> rows -> size)
+			Melder_throw (me, " & ", table, ": the number of rows in the table (", table -> rows -> size,
+				") doesn't match the number of events (", my events -> size, ").");
 		autoERPTier thee = Thing_new (ERPTier);
-		Function_init (thee.peek(), this -> xmin, this -> xmax);
-		thy d_numberOfChannels = this -> d_numberOfChannels;
-		thy d_channelNames = NUMvector <wchar_t *> (1, thy d_numberOfChannels);
-		for (long ichan = 1; ichan <= thy d_numberOfChannels; ichan ++) {
-			thy d_channelNames [ichan] = Melder_wcsdup (this -> d_channelNames [ichan]);
+		Function_init (thee.peek(), my xmin, my xmax);
+		thy numberOfChannels = my numberOfChannels;
+		thy channelNames = NUMvector <wchar_t *> (1, thy numberOfChannels);
+		for (long ichan = 1; ichan <= thy numberOfChannels; ichan ++) {
+			thy channelNames [ichan] = Melder_wcsdup (my channelNames [ichan]);
 		}
-		thy d_events = SortedSetOfDouble_create ();
-		for (long ievent = 1; ievent <= d_events -> size; ievent ++) {
-			ERPPoint oldEvent = this -> f_peekEvent (ievent);
+		thy events = SortedSetOfDouble_create ();
+		for (long ievent = 1; ievent <= my events -> size; ievent ++) {
+			ERPPoint oldEvent = my event (ievent);
 			TableRow row = table -> f_peekRow (ievent);
 			if (Melder_stringMatchesCriterion (row -> cells [columnNumber]. string, which_Melder_STRING, criterion)) {
 				autoERPPoint newEvent = Data_copy (oldEvent);
-				Collection_addItem (thy d_events, newEvent.transfer());
+				Collection_addItem (thy events, newEvent.transfer());
 			}
 		}
-		if (thy d_events -> size == 0) {
+		if (thy events -> size == 0) {
 			Melder_warning ("No event matches criterion.");
 		}
 		return thee.transfer();
 	} catch (MelderError) {
-		Melder_throw (this, ": events not extracted.");
+		Melder_throw (me, ": events not extracted.");
 	}
 }
 
-
 /* End of file ERPTier.cpp */
diff --git a/EEG/ERPTier.h b/EEG/ERPTier.h
index 671435e..200168b 100644
--- a/EEG/ERPTier.h
+++ b/EEG/ERPTier.h
@@ -2,7 +2,7 @@
 #define _ERPTier_h_
 /* ERPTier.h
  *
- * Copyright (C) 2011 Paul Boersma
+ * Copyright (C) 2011,2014 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
@@ -26,7 +26,30 @@
 oo_CLASS_CREATE (ERPPoint, AnyPoint);
 oo_CLASS_CREATE (ERPTier, Function);
 
-ERPTier EEG_to_ERPTier (EEG me, double fromTime, double toTime, int markerBit);
+
+long ERPTier_getChannelNumber (ERPTier me, const wchar_t *channelName);
+static inline void ERPTier_checkEventNumber (ERPTier me, long eventNumber) {
+	if (eventNumber < 1)
+		Melder_throw ("The specified event number is ", eventNumber, " but should have been positive.");
+	if (eventNumber > my events -> size)
+		Melder_throw ("The specified event number (", eventNumber, ") exceeds the number of events (", my events -> size, ").");
+}
+double ERPTier_getMean (ERPTier me, long pointNumber, long channelNumber, double tmin, double tmax);
+double ERPTier_getMean (ERPTier me, long pointNumber, const wchar_t *channelName, double tmin, double tmax);
+void ERPTier_subtractBaseline (ERPTier me, double tmin, double tmax);
+void ERPTier_rejectArtefacts (ERPTier me, double threshold);
+ERP ERPTier_extractERP (ERPTier me, long pointNumber);
+ERP ERPTier_to_ERP_mean (ERPTier me);
+ERPTier ERPTier_extractEventsWhereColumn_number (ERPTier me, Table table, long columnNumber, int which_Melder_NUMBER, double criterion);
+ERPTier ERPTier_extractEventsWhereColumn_string (ERPTier me, Table table, long columnNumber, int which_Melder_STRING, const wchar_t *criterion);
+
+ERPTier EEG_to_ERPTier_bit (EEG me, double fromTime, double toTime, int markerBit);
+ERPTier EEG_to_ERPTier_marker (EEG me, double fromTime, double toTime, uint16_t marker);
+ERPTier EEG_to_ERPTier_triggers (EEG me, double fromTime, double toTime,
+	int which_Melder_STRING, const wchar_t *criterion);
+ERPTier EEG_to_ERPTier_triggers_preceded (EEG me, double fromTime, double toTime,
+	int which_Melder_STRING, const wchar_t *criterion,
+	int which_Melder_STRING_precededBy, const wchar_t *criterion_precededBy);
 
 /* End of file ERPTier.h */
 #endif
diff --git a/EEG/ERPTier_def.h b/EEG/ERPTier_def.h
index 98d1414..3443f5a 100644
--- a/EEG/ERPTier_def.h
+++ b/EEG/ERPTier_def.h
@@ -1,6 +1,6 @@
 /* ERPTier_def.h
  *
- * Copyright (C) 2011 Paul Boersma
+ * Copyright (C) 2011,2014 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
@@ -21,7 +21,7 @@
 #define ooSTRUCT ERPPoint
 oo_DEFINE_CLASS (ERPPoint, AnyPoint)
 
-	oo_OBJECT (Sound, 2, d_erp)
+	oo_OBJECT (Sound, 2, erp)
 
 oo_END_CLASS (ERPPoint)
 #undef ooSTRUCT
@@ -30,31 +30,15 @@ oo_END_CLASS (ERPPoint)
 #define ooSTRUCT ERPTier
 oo_DEFINE_CLASS (ERPTier, Function)
 
-	oo_COLLECTION (SortedSetOfDouble, d_events, ERPPoint, 0)
+	oo_COLLECTION (SortedSetOfDouble, events, ERPPoint, 0)
 
-	oo_LONG (d_numberOfChannels)
-	oo_STRING_VECTOR (d_channelNames, d_numberOfChannels)
+	oo_LONG (numberOfChannels)
+	oo_STRING_VECTOR (channelNames, numberOfChannels)
 
 	#if oo_DECLARING
 		// functions:
 		public:
-			void f_init (double tmin, double tmax);
-			long f_getChannelNumber (const wchar_t *channelName);
-			void f_checkEventNumber (long eventNumber) {
-				if (eventNumber < 1)
-					Melder_throw ("The specified event number is ", eventNumber, " but should have been positive.");
-				if (eventNumber > d_events -> size)
-					Melder_throw ("The specified event number (", eventNumber, ") exceeds the number of events (", d_events -> size, ").");
-			}
-			ERPPoint f_peekEvent (long i) { return static_cast <ERPPoint> (d_events -> item [i]); }
-			double f_getMean (long pointNumber, long channelNumber, double tmin, double tmax);
-			double f_getMean (long pointNumber, const wchar_t *channelName, double tmin, double tmax);
-			void f_subtractBaseline (double tmin, double tmax);
-			void f_rejectArtefacts (double threshold);
-			ERP f_extractERP (long pointNumber);
-			ERP f_toERP_mean ();
-			ERPTier f_extractEventsWhereColumn_number (Table table, long columnNumber, int which_Melder_NUMBER, double criterion);
-			ERPTier f_extractEventsWhereColumn_string (Table table, long columnNumber, int which_Melder_STRING, const wchar_t *criterion);
+			ERPPoint event (long i) { return static_cast <ERPPoint> (our events -> item [i]); }
 		// overridden methods:
 		protected:
 			virtual int v_domainQuantity () { return MelderQuantity_TIME_SECONDS; }
diff --git a/EEG/ERPWindow.cpp b/EEG/ERPWindow.cpp
index b6a9b85..1cd35f6 100644
--- a/EEG/ERPWindow.cpp
+++ b/EEG/ERPWindow.cpp
@@ -1,6 +1,6 @@
 /* ERPWindow.cpp
  *
- * Copyright (C) 2012,2013 Paul Boersma
+ * Copyright (C) 2012,2013,2014 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,6 +18,7 @@
  */
 
 #include "ERPWindow.h"
+#include "EditorM.h"
 #include "Preferences.h"
 
 Thing_implement (ERPWindow, SoundEditor, 0);
@@ -149,15 +150,33 @@ static BiosemiLocationData biosemiCapCoordinates32 [1+32] =
 	{   0,   0 },   // 32 Cz
 };
 
-void structERP :: f_drawScalp (Graphics graphics, double tmin, double tmax, double vmin, double vmax, bool garnish) {
+void ERP_drawScalp_garnish (Graphics graphics, double vmin, double vmax, enum kGraphics_colourScale colourScale) {
+	long n = 201;
+	autoNUMmatrix <double> legend (1, n, 1, 2);
+	for (long irow = 1; irow <= n; irow ++) {
+		for (long icol = 1; icol <= 2; icol ++) {
+			legend [irow] [icol] = (irow - 1) / (n - 1.0);
+		}
+	}
+	Graphics_setColourScale (graphics, colourScale);
+	Graphics_image (graphics, legend.peek(), 1, 2, 0.85, 0.98, 1, n, -0.8, +0.8, 0.0, 1.0);
+	Graphics_setColourScale (graphics, kGraphics_colourScale_GREY);
+	Graphics_rectangle (graphics, 0.85, 0.98, -0.8, +0.8);
+	Graphics_setTextAlignment (graphics, Graphics_RIGHT, Graphics_TOP);
+	Graphics_text2 (graphics, 1.0, -0.8, Melder_double (vmin * 1e6), L" \\muV");
+	Graphics_setTextAlignment (graphics, Graphics_RIGHT, Graphics_BOTTOM);
+	Graphics_text2 (graphics, 1.0, +0.8, Melder_double (vmax * 1e6), L" \\muV");
+}
+
+void ERP_drawScalp (ERP me, Graphics graphics, double tmin, double tmax, double vmin, double vmax, enum kGraphics_colourScale colourScale, bool garnish) {
 	Graphics_setInner (graphics);
 	Graphics_setWindow (graphics, -1.0, 1.0, -1.0, 1.0);
 	//Graphics_setGrey (graphics, 1.0);
 	//Graphics_fillRectangle (graphics, -1.1, 1.1, -1.01, 1.19);
 	//Graphics_setColour (graphics, Graphics_BLACK);
 	long numberOfDrawableChannels =
-			this -> ny >= 64 && Melder_wcsequ (this -> d_channelNames [64], L"O2") ? 64 :
-			this -> ny >= 32 && Melder_wcsequ (this -> d_channelNames [32], L"Cz") ? 32 :
+			my ny >= 64 && Melder_wcsequ (my channelNames [64], L"O2") ? 64 :
+			my ny >= 32 && Melder_wcsequ (my channelNames [32], L"Cz") ? 32 :
 			0;
 	BiosemiLocationData *biosemiLocationData = numberOfDrawableChannels == 64 ? biosemiCapCoordinates64 : numberOfDrawableChannels == 32 ? biosemiCapCoordinates32 : 0;
 	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
@@ -174,8 +193,8 @@ void structERP :: f_drawScalp (Graphics graphics, double tmin, double tmax, doub
 	autoNUMvector <double> mean (1, numberOfDrawableChannels);
 	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
 		mean [ichan] = tmin == tmax ?
-				Sampled_getValueAtX (this, tmin, ichan, 0, true) :
-				Vector_getMean (this, tmin, tmax, ichan);
+				Sampled_getValueAtX (me, tmin, ichan, 0, true) :
+				Vector_getMean (me, tmin, tmax, ichan);
 	}
 	autoNUMmatrix <double> image (1, n, 1, n);
 	for (long irow = 1; irow <= n; irow ++) {
@@ -202,21 +221,24 @@ void structERP :: f_drawScalp (Graphics graphics, double tmin, double tmax, doub
 			}
 		}
 	}
+	double whiteValue = colourScale == kGraphics_colourScale_BLUE_TO_RED ? 0.5 * (vmin + vmax) : vmin;
+	Graphics_setColourScale (graphics, colourScale);
 	for (long irow = 1; irow <= n; irow ++) {
 		double y = -1.0 + (irow - 1) * d;
 		for (long icol = 1; icol <= n; icol ++) {
 			double x = -1.0 + (icol - 1) * d;
 			if (x * x + y * y > 1.0) {
-				image [irow] [icol] = vmin;
+				image [irow] [icol] = whiteValue;
 			}
 		}
 	}
 	Graphics_image (graphics, image.peek(), 1, n, -1.0-0.5/n, 1.0+0.5/n, 1, n, -1.0-0.5/n, 1.0+0.5/n, vmin, vmax);
+	Graphics_setColourScale (graphics, kGraphics_colourScale_GREY);
 	Graphics_setLineWidth (graphics, 2.0);
 	/*
 	 * Nose.
 	 */
-	Graphics_setGrey (graphics, 0.5);
+	Graphics_setGrey (graphics, colourScale == kGraphics_colourScale_BLUE_TO_RED ? 1.0 : 0.5);
 	{// scope
 		double x [3] = { -0.08, 0.0, 0.08 }, y [3] = { 0.99, 1.18, 0.99 };
 		Graphics_fillArea (graphics, 3, x, y);
@@ -227,7 +249,7 @@ void structERP :: f_drawScalp (Graphics graphics, double tmin, double tmax, doub
 	/*
 	 * Ears.
 	 */
-	Graphics_setGrey (graphics, 0.5);
+	Graphics_setGrey (graphics, colourScale == kGraphics_colourScale_BLUE_TO_RED ? 1.0 : 0.5);
 	Graphics_fillRectangle (graphics, -1.09, -1.00, -0.08, 0.08);
 	Graphics_fillRectangle (graphics, 1.09, 1.00, -0.08, 0.08);
 	Graphics_setColour (graphics, Graphics_BLACK);
@@ -244,30 +266,19 @@ void structERP :: f_drawScalp (Graphics graphics, double tmin, double tmax, doub
 	Graphics_setLineWidth (graphics, 1.0);
 	Graphics_unsetInner (graphics);
 	if (garnish) {
-		autoNUMmatrix <double> legend (1, n, 1, 2);
-		for (long irow = 1; irow <= n; irow ++) {
-			for (long icol = 1; icol <= 2; icol ++) {
-				legend [irow] [icol] = (irow - 1) / (n - 1.0);
-			}
-		}
-		Graphics_image (graphics, legend.peek(), 1, 2, 0.78, 0.98, 1, n, -0.8, +0.8, 0.0, 1.0);
-		Graphics_rectangle (graphics, 0.78, 0.98, -0.8, +0.8);
-		Graphics_setTextAlignment (graphics, Graphics_RIGHT, Graphics_TOP);
-		Graphics_text2 (graphics, 1.0, -0.8, Melder_double (vmin * 1e6), L" \\muV");
-		Graphics_setTextAlignment (graphics, Graphics_RIGHT, Graphics_BOTTOM);
-		Graphics_text2 (graphics, 1.0, +0.8, Melder_double (vmax * 1e6), L" \\muV");
+		ERP_drawScalp_garnish (graphics, vmin, vmax, colourScale);
 	}
 }
 
 void structERPWindow :: v_drawSelectionViewer () {
 	ERP erp = (ERP) data;
 	Graphics_setWindow (d_graphics, -1.1, 1.1, -1.01, 1.19);
-	Graphics_setGrey (d_graphics, 0.85);
+	Graphics_setColour (d_graphics, Graphics_WINDOW_BACKGROUND_COLOUR);
 	Graphics_fillRectangle (d_graphics, -1.1, 1.1, -1.01, 1.19);
 	Graphics_setColour (d_graphics, Graphics_BLACK);
 	long numberOfDrawableChannels =
-			erp -> ny >= 64 && Melder_wcsequ (erp -> d_channelNames [64], L"O2") ? 64 :
-			erp -> ny >= 32 && Melder_wcsequ (erp -> d_channelNames [32], L"Cz") ? 32 :
+			erp -> ny >= 64 && Melder_wcsequ (erp -> channelNames [64], L"O2") ? 64 :
+			erp -> ny >= 32 && Melder_wcsequ (erp -> channelNames [32], L"Cz") ? 32 :
 			0;
 	BiosemiLocationData *biosemiLocationData = numberOfDrawableChannels == 64 ? biosemiCapCoordinates64 : numberOfDrawableChannels == 32 ? biosemiCapCoordinates32 : 0;
 	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
@@ -281,9 +292,9 @@ void structERPWindow :: v_drawSelectionViewer () {
 	}
 	long n = 201;
 	double d = 2.0 / (n - 1);
-	autoNUMvector <double> mean (1, numberOfDrawableChannels);
+	autoNUMvector <double> means (1, numberOfDrawableChannels);
 	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
-		mean [ichan] =
+		means [ichan] =
 			d_startSelection == d_endSelection ?
 				Sampled_getValueAtX (erp, d_startSelection, ichan, 0, true) :
 				Vector_getMean (erp, d_startSelection, d_endSelection, ichan);
@@ -300,11 +311,11 @@ void structERPWindow :: v_drawSelectionViewer () {
 					double dy = y - biosemiLocationData [ichan]. topY;
 					double distance = sqrt (dx * dx + dy * dy);
 					if (distance < 1e-12) {
-						value = mean [ichan];
+						value = means [ichan];
 						break;
 					}
 					distance = distance * distance * distance * distance * distance * distance;
-					sum += mean [ichan] / distance;
+					sum += means [ichan] / distance;
 					weight += 1.0 / distance;
 				}
 				if (value == NUMundefined)
@@ -338,16 +349,20 @@ void structERPWindow :: v_drawSelectionViewer () {
 		for (long icol = 1; icol <= n; icol ++) {
 			double x = -1.0 + (icol - 1) * d;
 			if (x * x + y * y > 1.0) {
-				image [irow] [icol] = minimum + 0.1875 * (maximum - minimum);   // -0.625 * absoluteExtremum;
+				image [irow] [icol] = minimum +
+					( our p_scalp_colourScale == kGraphics_colourScale_BLUE_TO_RED ? 0.46 : 0.1875 ) * (maximum - minimum);
+					   // -0.625 * absoluteExtremum;
 			}
 		}
 	}
+	Graphics_setColourScale (d_graphics, our p_scalp_colourScale);
 	Graphics_image (d_graphics, image.peek(), 1, n, -1.0-0.5/n, 1.0+0.5/n, 1, n, -1.0-0.5/n, 1.0+0.5/n, minimum, maximum);
+	Graphics_setColourScale (d_graphics, kGraphics_colourScale_GREY);
 	Graphics_setLineWidth (d_graphics, 2.0);
 	/*
 	 * Nose.
 	 */
-	Graphics_setGrey (d_graphics, 0.5);
+	Graphics_setGrey (d_graphics, our p_scalp_colourScale == kGraphics_colourScale_BLUE_TO_RED ? 1.0 : 0.5);
 	{// scope
 		double x [3] = { -0.08, 0.0, 0.08 }, y [3] = { 0.99, 1.18, 0.99 };
 		Graphics_fillArea (d_graphics, 3, x, y);
@@ -358,7 +373,7 @@ void structERPWindow :: v_drawSelectionViewer () {
 	/*
 	 * Ears.
 	 */
-	Graphics_setGrey (d_graphics, 0.5);
+	Graphics_setGrey (d_graphics, our p_scalp_colourScale == kGraphics_colourScale_BLUE_TO_RED ? 1.0 : 0.5);
 	Graphics_fillRectangle (d_graphics, -1.09, -1.00, -0.08, 0.08);
 	Graphics_fillRectangle (d_graphics, 1.09, 1.00, -0.08, 0.08);
 	Graphics_setColour (d_graphics, Graphics_BLACK);
@@ -375,11 +390,23 @@ void structERPWindow :: v_drawSelectionViewer () {
 	Graphics_setLineWidth (d_graphics, 1.0);
 }
 
+void structERPWindow :: v_prefs_addFields (EditorCommand cmd) {
+	Any radio;
+	OPTIONMENU_ENUM (L"Scalp colour space", kGraphics_colourScale, kGraphics_colourScale_BLUE_TO_RED)
+}
+void structERPWindow :: v_prefs_setValues (EditorCommand cmd) {
+	SET_ENUM (L"Scalp colour space", kGraphics_colourScale, p_scalp_colourScale)
+}
+void structERPWindow :: v_prefs_getValues (EditorCommand cmd) {
+	pref_scalp_colourScale () = p_scalp_colourScale = GET_ENUM (kGraphics_colourScale, L"Scalp colour space");
+	FunctionEditor_redraw (this);
+}
+
 ERPWindow ERPWindow_create (const wchar_t *title, ERP data) {
 	Melder_assert (data != NULL);
 	try {
 		autoERPWindow me = Thing_new (ERPWindow);
-		me -> structSoundEditor :: f_init (title, data);
+		my structSoundEditor :: f_init (title, data);
 		return me.transfer();
 	} catch (MelderError) {
 		Melder_throw ("ERP window not created.");
diff --git a/EEG/ERPWindow.h b/EEG/ERPWindow.h
index d0904f2..fb8184b 100644
--- a/EEG/ERPWindow.h
+++ b/EEG/ERPWindow.h
@@ -2,7 +2,7 @@
 #define _ERPWindow_h_
 /* ERPWindow.h
  *
- * Copyright (C) 2012,2013 Paul Boersma
+ * Copyright (C) 2012,2013,2014 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
@@ -25,17 +25,23 @@
 Thing_define (ERPWindow, SoundEditor) { public:
 	// overridden methods:
 		virtual const wchar_t * v_getChannelName (long channelNumber) {
-			ERP erp = (ERP) this -> data;
-			return erp -> d_channelNames [channelNumber];
+			ERP erp = (ERP) our data;
+			return erp -> channelNames [channelNumber];
 		}
 		virtual void v_drawSelectionViewer ();
 		virtual bool v_hasPitch     () { return false; }
 		virtual bool v_hasIntensity () { return false; }
 		virtual bool v_hasFormants  () { return false; }
 		virtual bool v_hasPulses    () { return false; }
+		virtual void v_prefs_addFields (EditorCommand cmd);
+		virtual void v_prefs_setValues (EditorCommand cmd);
+		virtual void v_prefs_getValues (EditorCommand cmd);
 	#include "ERPWindow_prefs.h"
 };
 
+/**
+	Create an ERPWindow.
+*/
 ERPWindow ERPWindow_create (const wchar_t *title, ERP data);
 
 /* End of file ERPWindow.h */
diff --git a/EEG/ERPWindow_prefs.h b/EEG/ERPWindow_prefs.h
index 1111161..27743bd 100644
--- a/EEG/ERPWindow_prefs.h
+++ b/EEG/ERPWindow_prefs.h
@@ -1,6 +1,6 @@
 /* ERPWindow_prefs.h
  *
- * Copyright (C) 2013 Paul Boersma
+ * Copyright (C) 2013,2014 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
@@ -20,6 +20,7 @@
 prefs_begin (ERPWindow)
 	// overridden:
 		prefs_add_bool   (ERPWindow, showSelectionViewer,            1, true)
+		prefs_add_enum_with_data   (ERPWindow, scalp_colourScale,              1, kGraphics_colourScale, BLUE_TO_RED)
 		prefs_add_enum   (ERPWindow, sound_scalingStrategy,          1, kTimeSoundEditor_scalingStrategy, DEFAULT)
 		prefs_add_double (ERPWindow, sound_scaling_height,           1, L"20e-6")
 		prefs_add_double (ERPWindow, sound_scaling_minimum,          1, L"-10e-6")
diff --git a/EEG/ERP_def.h b/EEG/ERP_def.h
index 92ccf1a..5d41eb3 100644
--- a/EEG/ERP_def.h
+++ b/EEG/ERP_def.h
@@ -1,6 +1,6 @@
 /* ERP_def.h
  *
- * Copyright (C) 2011-2012 Paul Boersma
+ * Copyright (C) 2011-2012,2014 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
@@ -21,18 +21,7 @@
 #define ooSTRUCT ERP
 oo_DEFINE_CLASS (ERP, Sound)
 
-	oo_STRING_VECTOR (d_channelNames, ny)
-
-	#if oo_DECLARING
-		// functions:
-		public:
-			long f_getChannelNumber (const wchar_t *channelName);
-			void f_draw (Graphics graphics, long channelNumber, double tmin, double tmax, double vmin, double vmax, bool garnish);
-			void f_draw (Graphics graphics, const wchar_t *channelName, double tmin, double tmax, double vmin, double vmax, bool garnish);
-			void f_drawScalp (Graphics graphics, double tmin, double tmax, double vmin, double vmax, bool garnish);
-			Table f_tabulate (bool includeSampleNumbers, bool includeTime, int timeDecimals, int voltageDecimals, int units);
-			Sound f_downToSound ();
-	#endif
+	oo_STRING_VECTOR (channelNames, ny)
 
 oo_END_CLASS (ERP)
 #undef ooSTRUCT
diff --git a/EEG/praat_EEG.cpp b/EEG/praat_EEG.cpp
index 55391fd..37185f5 100644
--- a/EEG/praat_EEG.cpp
+++ b/EEG/praat_EEG.cpp
@@ -1,6 +1,6 @@
 /* praat_EEG.cpp
  *
- * Copyright (C) 2011-2012 Paul Boersma
+ * Copyright (C) 2011-2012,2013,2014 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
@@ -38,7 +38,7 @@ END
 DIRECT (EEG_detrend)
 	LOOP {
 		iam (EEG);
-		my f_detrend ();
+		EEG_detrend (me);
 		praat_dataChanged (me);
 	}
 END
@@ -56,19 +56,24 @@ FORM (EEG_editExternalElectrodeNames, L"Edit external electrode names", 0)
 int IOBJECT;
 LOOP {
 	iam (EEG);
-	SET_STRING (L"External electrode 1", my d_channelNames [my d_numberOfChannels - 15])
-	SET_STRING (L"External electrode 2", my d_channelNames [my d_numberOfChannels - 14])
-	SET_STRING (L"External electrode 3", my d_channelNames [my d_numberOfChannels - 13])
-	SET_STRING (L"External electrode 4", my d_channelNames [my d_numberOfChannels - 12])
-	SET_STRING (L"External electrode 5", my d_channelNames [my d_numberOfChannels - 11])
-	SET_STRING (L"External electrode 6", my d_channelNames [my d_numberOfChannels - 10])
-	SET_STRING (L"External electrode 7", my d_channelNames [my d_numberOfChannels -  9])
-	SET_STRING (L"External electrode 8", my d_channelNames [my d_numberOfChannels -  8])
+	if (EEG_getNumberOfExternalElectrodes (me) == 8) {
+		const long offsetExternalElectrode = EEG_getNumberOfCapElectrodes (me);
+		SET_STRING (L"External electrode 1", my channelNames [offsetExternalElectrode + 1])
+		SET_STRING (L"External electrode 2", my channelNames [offsetExternalElectrode + 2])
+		SET_STRING (L"External electrode 3", my channelNames [offsetExternalElectrode + 3])
+		SET_STRING (L"External electrode 4", my channelNames [offsetExternalElectrode + 4])
+		SET_STRING (L"External electrode 5", my channelNames [offsetExternalElectrode + 5])
+		SET_STRING (L"External electrode 6", my channelNames [offsetExternalElectrode + 6])
+		SET_STRING (L"External electrode 7", my channelNames [offsetExternalElectrode + 7])
+		SET_STRING (L"External electrode 8", my channelNames [offsetExternalElectrode + 8])
+	}
 }
 DO
 	LOOP {
 		iam (EEG);
-		my f_setExternalElectrodeNames (GET_STRING (L"External electrode 1"), GET_STRING (L"External electrode 2"), GET_STRING (L"External electrode 3"),
+		if (EEG_getNumberOfExternalElectrodes (me) != 8)
+			Melder_throw ("You can do this only if there are 8 external electrodes.");
+		EEG_setExternalElectrodeNames (me, GET_STRING (L"External electrode 1"), GET_STRING (L"External electrode 2"), GET_STRING (L"External electrode 3"),
 			GET_STRING (L"External electrode 4"), GET_STRING (L"External electrode 5"), GET_STRING (L"External electrode 6"),
 			GET_STRING (L"External electrode 7"), GET_STRING (L"External electrode 8"));
 		praat_dataChanged (me);
@@ -82,7 +87,7 @@ DO
 	LOOP {
 		iam (EEG);
 		const wchar_t *channelName = GET_STRING (L"Channel name");
-		autoEEG thee = my f_extractChannel (channelName);
+		autoEEG thee = EEG_extractChannel (me, channelName);
 		praat_new (thee.transfer(), my name, L"_", channelName);
 	}
 END
@@ -95,7 +100,7 @@ FORM (EEG_extractPart, L"EEG: Extract part", 0)
 DO
 	LOOP {
 		iam (EEG);
-		autoEEG thee = my f_extractPart (GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Preserve times"));
+		autoEEG thee = EEG_extractPart (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Preserve times"));
 		praat_new (thee.transfer(), my name, L"_part");
 	}
 END
@@ -103,8 +108,8 @@ END
 DIRECT (EEG_extractSound)
 	LOOP {
 		iam (EEG);
-		if (! my d_sound) Melder_throw (me, ": I don't contain a waveform.");
-		autoSound thee = my f_extractSound ();
+		if (! my sound) Melder_throw (me, ": I don't contain a waveform.");
+		autoSound thee = EEG_extractSound (me);
 		praat_new (thee.transfer(), NULL);
 	}
 END
@@ -112,8 +117,8 @@ END
 DIRECT (EEG_extractTextGrid)
 	LOOP {
 		iam (EEG);
-		if (! my d_textgrid) Melder_throw (me, ": I don't contain marks.");
-		autoTextGrid thee = my f_extractTextGrid ();
+		if (! my textgrid) Melder_throw (me, ": I don't contain marks.");
+		autoTextGrid thee = EEG_extractTextGrid (me);
 		praat_new (thee.transfer(), NULL);
 	}
 END
@@ -128,7 +133,7 @@ FORM (EEG_filter, L"Filter", 0)
 DO
 	LOOP {
 		iam (EEG);
-		my f_filter (GET_REAL (L"Low frequency"), GET_REAL (L"Low width"), GET_REAL (L"High frequency"), GET_REAL (L"High width"), GET_INTEGER (L"Notch at 50 Hz"));
+		EEG_filter (me, GET_REAL (L"Low frequency"), GET_REAL (L"Low width"), GET_REAL (L"High frequency"), GET_REAL (L"High width"), GET_INTEGER (L"Notch at 50 Hz"));
 		praat_dataChanged (me);
 	}
 END
@@ -140,9 +145,9 @@ DO
 	LOOP {
 		iam (EEG);
 		long channelNumber = GET_INTEGER (L"Channel number");
-		if (channelNumber > my d_numberOfChannels)
-			Melder_throw (me, ": there are only ", my d_numberOfChannels, " channels.");
-		Melder_information (my d_channelNames [channelNumber]);
+		if (channelNumber > my numberOfChannels)
+			Melder_throw (me, ": there are only ", my numberOfChannels, " channels.");
+		Melder_information (my channelNames [channelNumber]);
 	}
 END
 
@@ -152,7 +157,19 @@ FORM (EEG_getChannelNumber, L"Get channel number", 0)
 DO
 	LOOP {
 		iam (EEG);
-		Melder_information (Melder_integer (my f_getChannelNumber (GET_STRING (L"Channel name"))));
+		Melder_information (Melder_integer (EEG_getChannelNumber (me, GET_STRING (L"Channel name"))));
+	}
+END
+
+FORM (EEG_removeTriggers, L"Remove triggers", 0)
+	OPTIONMENU_ENUM (L"Remove every trigger that...", kMelder_string, DEFAULT)
+	SENTENCE (L"...the text", L"hi")
+	OK
+DO
+	LOOP {
+		iam (EEG);
+		EEG_removeTriggers (me, GET_ENUM (kMelder_string, L"Remove every trigger that..."), GET_STRING (L"...the text"));
+		praat_dataChanged (me);
 	}
 END
 
@@ -163,7 +180,7 @@ FORM (EEG_setChannelName, L"Set channel name", 0)
 DO
 	LOOP {
 		iam (EEG);
-		my f_setChannelName (GET_INTEGER (L"Channel number"), GET_STRING (L"New name"));
+		EEG_setChannelName (me, GET_INTEGER (L"Channel number"), GET_STRING (L"New name"));
 		praat_dataChanged (me);
 	}
 END
@@ -174,7 +191,7 @@ FORM (EEG_setChannelToZero, L"Set channel to zero", 0)
 DO
 	LOOP {
 		iam (EEG);
-		my f_setChannelToZero (GET_STRING (L"Channel"));
+		EEG_setChannelToZero (me, GET_STRING (L"Channel"));
 		praat_dataChanged (me);
 	}
 END
@@ -187,7 +204,7 @@ FORM (EEG_subtractMeanChannel, L"Subtract mean channel", 0)
 DO
 	LOOP {
 		iam (EEG);
-		my f_subtractMeanChannel (GET_INTEGER (L"From channel"), GET_INTEGER (L"To channel"));
+		EEG_subtractMeanChannel (me, GET_INTEGER (L"From channel"), GET_INTEGER (L"To channel"));
 		praat_dataChanged (me);
 	}
 END
@@ -199,12 +216,12 @@ FORM (EEG_subtractReference, L"Subtract reference", 0)
 DO
 	LOOP {
 		iam (EEG);
-		my f_subtractReference (GET_STRING (L"Reference channel 1"), GET_STRING (L"Reference channel 2"));
+		EEG_subtractReference (me, GET_STRING (L"Reference channel 1"), GET_STRING (L"Reference channel 2"));
 		praat_dataChanged (me);
 	}
 END
 
-FORM (EEG_to_ERPTier, L"To ERPTier", 0)
+FORM (EEG_to_ERPTier_bit, L"To ERPTier (bit)", 0)
 	REAL (L"From time (s)", L"-0.11")
 	REAL (L"To time (s)", L"0.39")
 	NATURAL (L"Marker bit", L"8")
@@ -213,11 +230,75 @@ DO
 	LOOP {
 		iam (EEG);
 		int markerBit = GET_INTEGER (L"Marker bit");
-		autoERPTier thee = EEG_to_ERPTier (me, GET_REAL (L"From time"), GET_REAL (L"To time"), markerBit);
+		autoERPTier thee = EEG_to_ERPTier_bit (me, GET_REAL (L"From time"), GET_REAL (L"To time"), markerBit);
 		praat_new (thee.transfer(), my name, L"_bit", Melder_integer (markerBit));
 	}
 END
 
+FORM (EEG_to_ERPTier_marker, L"To ERPTier (marker)", 0)
+	REAL (L"From time (s)", L"-0.11")
+	REAL (L"To time (s)", L"0.39")
+	NATURAL (L"Marker number", L"12")
+	OK
+DO
+	LOOP {
+		iam (EEG);
+		uint16_t markerNumber = GET_INTEGER (L"Marker number");
+		autoERPTier thee = EEG_to_ERPTier_marker (me, GET_REAL (L"From time"), GET_REAL (L"To time"), markerNumber);
+		praat_new (thee.transfer(), my name, L"_", Melder_integer (markerNumber));
+	}
+END
+
+FORM (EEG_to_ERPTier_triggers, L"To ERPTier (triggers)", 0)
+	REAL (L"From time (s)", L"-0.11")
+	REAL (L"To time (s)", L"0.39")
+	OPTIONMENU_ENUM (L"Get every event with a trigger that", kMelder_string, DEFAULT)
+	SENTENCE (L"...the text", L"1")
+	OK
+DO
+	LOOP {
+		iam (EEG);
+		autoERPTier thee = EEG_to_ERPTier_triggers (me, GET_REAL (L"From time"), GET_REAL (L"To time"),
+			GET_ENUM (kMelder_string, L"Get every event with a trigger that"), GET_STRING (L"...the text"));
+		praat_new (thee.transfer(), my name, L"_trigger", GET_STRING (L"...the text"));
+	}
+END
+
+FORM (EEG_to_ERPTier_triggers_preceded, L"To ERPTier (triggers, preceded)", 0)
+	REAL (L"From time (s)", L"-0.11")
+	REAL (L"To time (s)", L"0.39")
+	OPTIONMENU_ENUM (L"Get every event with a trigger that", kMelder_string, DEFAULT)
+	SENTENCE (L"...the text", L"1")
+	OPTIONMENU_ENUM (L"and is preceded by a trigger that", kMelder_string, DEFAULT)
+	SENTENCE (L" ...the text", L"4")
+	OK
+DO
+	LOOP {
+		iam (EEG);
+		autoERPTier thee = EEG_to_ERPTier_triggers_preceded (me, GET_REAL (L"From time"), GET_REAL (L"To time"),
+			GET_ENUM (kMelder_string, L"Get every event with a trigger that"), GET_STRING (L"...the text"),
+			GET_ENUM (kMelder_string, L"and is preceded by a trigger that"), GET_STRING (L" ...the text"));
+		praat_new (thee.transfer(), my name, L"_trigger", GET_STRING (L" ...the text"));
+	}
+END
+
+FORM (EEG_to_MixingMatrix, L"To MixingMatrix", 0)
+	NATURAL (L"Maximum number of iterations", L"100")
+	POSITIVE (L"Tolerance", L"0.001")
+	OPTIONMENU (L"Diagonalization method", 2)
+		OPTION (L"qdiag")
+		OPTION (L"ffdiag")
+	OK
+DO
+	LOOP {
+		iam (EEG);
+		autoMixingMatrix thee = EEG_to_MixingMatrix (me,
+			GET_INTEGER (L"Maximum number of iterations"), GET_REAL (L"Tolerance"),
+			GET_INTEGER (L"Diagonalization method"));
+		praat_new (thee.transfer(), my name);
+	}
+END
+
 static void cb_EEGWindow_publication (Editor editor, void *closure, Data publication) {
 	(void) editor;
 	(void) closure;
@@ -253,7 +334,7 @@ END
 
 DIRECT (EEG_TextGrid_replaceTextGrid)
 	EEG me = FIRST (EEG);
-	me -> f_replaceTextGrid (FIRST (TextGrid));
+	EEG_replaceTextGrid (me, FIRST (TextGrid));
 	praat_dataChanged (me);
 END
 
@@ -262,7 +343,7 @@ END
 DIRECT (ERP_downto_Sound)
 	LOOP {
 		iam (ERP);
-		autoSound thee = my f_downToSound ();
+		autoSound thee = ERP_downto_Sound (me);
 		praat_new (thee.transfer(), NULL);
 	}
 END
@@ -279,7 +360,7 @@ FORM (ERP_downto_Table, L"ERP: Down to Table", 0)
 DO
 	LOOP {
 		iam (ERP);
-		autoTable thee = my f_tabulate (GET_INTEGER (L"Include sample number"),
+		autoTable thee = ERP_tabulate (me, GET_INTEGER (L"Include sample number"),
 			GET_INTEGER (L"Include time"), GET_INTEGER (L"Time decimals"), GET_INTEGER (L"Voltage decimals"), GET_INTEGER (L"Voltage units"));
 		praat_new (thee.transfer(), my name);
 	}
@@ -297,7 +378,7 @@ DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (ERP);
-		me -> f_draw (GRAPHICS, GET_STRING (L"Channel name"), GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+		ERP_drawChannel_name (me, GRAPHICS, GET_STRING (L"Channel name"), GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
 			GET_REAL (L"left Voltage range"), GET_REAL (L"right Voltage range"), GET_INTEGER (L"Garnish"));
 	}
 END
@@ -313,11 +394,39 @@ DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (ERP);
-		me -> f_drawScalp (GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
-			GET_REAL (L"left Voltage range"), GET_REAL (L"right Voltage range"), GET_INTEGER (L"Garnish"));
+		ERP_drawScalp (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+			GET_REAL (L"left Voltage range"), GET_REAL (L"right Voltage range"), kGraphics_colourScale_GREY, GET_INTEGER (L"Garnish"));
 	}
 END
 
+FORM (ERP_drawScalp_colour, L"ERP: Draw scalp (colour)", 0)
+	REAL (L"left Time range (s)", L"0.1")
+	REAL (L"right Time range", L"0.2")
+	REAL (L"left Voltage range (V)", L"10e-6")
+	REAL (L"right Voltage range", L"-10e-6")
+	RADIO_ENUM (L"Colour scale", kGraphics_colourScale, BLUE_TO_RED)
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (ERP);
+		ERP_drawScalp (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+			GET_REAL (L"left Voltage range"), GET_REAL (L"right Voltage range"), GET_ENUM(kGraphics_colourScale, L"Colour scale"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+FORM (ERP_drawScalp_garnish, L"ERP: Draw scalp (garnish)", 0)
+	REAL (L"left Voltage range (V)", L"10e-6")
+	REAL (L"right Voltage range", L"-10e-6")
+	RADIO_ENUM (L"Colour scale", kGraphics_colourScale, BLUE_TO_RED)
+	OK
+DO
+	autoPraatPicture picture;
+	ERP_drawScalp_garnish (GRAPHICS,
+		GET_REAL (L"left Voltage range"), GET_REAL (L"right Voltage range"), GET_ENUM(kGraphics_colourScale, L"Colour scale"));
+END
+
 FORM (ERP_extractOneChannelAsSound, L"ERP: Extract one channel as Sound", 0)
 	WORD (L"Channel name", L"Cz")
 	OK
@@ -325,7 +434,7 @@ DO
 	LOOP {
 		iam (ERP);
 		const wchar_t *channelName = GET_STRING (L"Channel name");
-		long channelNumber = my f_getChannelNumber (channelName);
+		long channelNumber = ERP_getChannelNumber (me, channelName);
 		if (channelNumber == 0) Melder_throw (me, ": no channel named \"", channelName, "\".");
 		autoSound thee = Sound_extractChannel (me, channelNumber);
 		praat_new (thee.transfer(), my name, L"_", channelName);
@@ -386,7 +495,7 @@ DO
 		long channelNumber = GET_INTEGER (L"Channel number");
 		if (channelNumber > my ny)
 			Melder_throw (me, ": there are only ", my ny, " channels.");
-		Melder_information (my d_channelNames [channelNumber]);
+		Melder_information (my channelNames [channelNumber]);
 	}
 END
 
@@ -396,7 +505,7 @@ FORM (ERP_getChannelNumber, L"Get channel number", 0)
 DO
 	LOOP {
 		iam (ERP);
-		Melder_information (Melder_integer (my f_getChannelNumber (GET_STRING (L"Channel name"))));
+		Melder_information (Melder_integer (ERP_getChannelNumber (me, GET_STRING (L"Channel name"))));
 	}
 END
 
@@ -415,7 +524,7 @@ DO
 	LOOP {
 		iam (ERP);
 		const wchar_t *channelName = GET_STRING (L"Channel name");
-		long channelNumber = my f_getChannelNumber (channelName);
+		long channelNumber = ERP_getChannelNumber (me, channelName);
 		if (channelNumber == 0) Melder_throw (me, ": no channel named \"", channelName, "\".");
 		double maximum;
 		Vector_getMaximumAndX (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), channelNumber, GET_INTEGER (L"Interpolation") - 1, & maximum, NULL);
@@ -432,7 +541,7 @@ DO
 	LOOP {
 		iam (ERP);
 		const wchar_t *channelName = GET_STRING (L"Channel name");
-		long channelNumber = my f_getChannelNumber (channelName);
+		long channelNumber = ERP_getChannelNumber (me, channelName);
 		if (channelNumber == 0) Melder_throw (me, ": no channel named \"", channelName, "\".");
 		double mean = Vector_getMean (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), channelNumber);
 		Melder_informationReal (mean, L"Volt");
@@ -454,7 +563,7 @@ DO
 	LOOP {
 		iam (ERP);
 		const wchar_t *channelName = GET_STRING (L"Channel name");
-		long channelNumber = my f_getChannelNumber (channelName);
+		long channelNumber = ERP_getChannelNumber (me, channelName);
 		if (channelNumber == 0) Melder_throw (me, ": no channel named \"", channelName, "\".");
 		double minimum;
 		Vector_getMinimumAndX (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), channelNumber, GET_INTEGER (L"Interpolation") - 1, & minimum, NULL);
@@ -477,7 +586,7 @@ DO
 	LOOP {
 		iam (ERP);
 		const wchar_t *channelName = GET_STRING (L"Channel name");
-		long channelNumber = my f_getChannelNumber (channelName);
+		long channelNumber = ERP_getChannelNumber (me, channelName);
 		if (channelNumber == 0) Melder_throw (me, ": no channel named \"", channelName, "\".");
 		double timeOfMaximum;
 		Vector_getMaximumAndX (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), channelNumber, GET_INTEGER (L"Interpolation") - 1, NULL, & timeOfMaximum);
@@ -500,7 +609,7 @@ DO
 	LOOP {
 		iam (ERP);
 		const wchar_t *channelName = GET_STRING (L"Channel name");
-		long channelNumber = my f_getChannelNumber (channelName);
+		long channelNumber = ERP_getChannelNumber (me, channelName);
 		if (channelNumber == 0) Melder_throw (me, ": no channel named \"", channelName, "\".");
 		double timeOfMinimum;
 		Vector_getMinimumAndX (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), channelNumber, GET_INTEGER (L"Interpolation") - 1, NULL, & timeOfMinimum);
@@ -548,9 +657,9 @@ DO
 	LOOP {
 		iam (ERPTier);
 		long channelNumber = GET_INTEGER (L"Channel number");
-		if (channelNumber > my d_numberOfChannels)
-			Melder_throw (me, ": there are only ", my d_numberOfChannels, " channels.");
-		Melder_information (my d_channelNames [channelNumber]);
+		if (channelNumber > my numberOfChannels)
+			Melder_throw (me, ": there are only ", my numberOfChannels, " channels.");
+		Melder_information (my channelNames [channelNumber]);
 	}
 END
 
@@ -560,7 +669,7 @@ FORM (ERPTier_getChannelNumber, L"Get channel number", 0)
 DO
 	LOOP {
 		iam (ERPTier);
-		Melder_information (Melder_integer (my f_getChannelNumber (GET_STRING (L"Channel name"))));
+		Melder_information (Melder_integer (ERPTier_getChannelNumber (me, GET_STRING (L"Channel name"))));
 	}
 END
 
@@ -573,7 +682,7 @@ FORM (ERPTier_getMean, L"ERPTier: Get mean", L"ERPTier: Get mean...")
 DO
 	LOOP {
 		iam (ERPTier);
-		double mean = my f_getMean (GET_INTEGER (L"Point number"), GET_STRING (L"Channel name"), GET_REAL (L"left Time range"), GET_REAL (L"right Time range"));
+		double mean = ERPTier_getMean (me, GET_INTEGER (L"Point number"), GET_STRING (L"Channel name"), GET_REAL (L"left Time range"), GET_REAL (L"right Time range"));
 		Melder_informationReal (mean, L"Volt");
 	}
 END
@@ -584,7 +693,7 @@ FORM (ERPTier_rejectArtefacts, L"Reject artefacts", 0)
 DO
 	LOOP {
 		iam (ERPTier);
-		my f_rejectArtefacts (GET_REAL (L"Threshold"));
+		ERPTier_rejectArtefacts (me, GET_REAL (L"Threshold"));
 		praat_dataChanged (me);
 	}
 END
@@ -608,7 +717,7 @@ FORM (ERPTier_subtractBaseline, L"Subtract baseline", 0)
 DO
 	LOOP {
 		iam (ERPTier);
-		my f_subtractBaseline (GET_REAL (L"From time"), GET_REAL (L"To time"));
+		ERPTier_subtractBaseline (me, GET_REAL (L"From time"), GET_REAL (L"To time"));
 		praat_dataChanged (me);
 	}
 END
@@ -619,7 +728,7 @@ FORM (ERPTier_to_ERP, L"ERPTier: To ERP", 0)
 DO
 	LOOP {
 		iam (ERPTier);
-		autoERP thee = my f_extractERP (GET_INTEGER (L"Event number"));
+		autoERP thee = ERPTier_extractERP (me, GET_INTEGER (L"Event number"));
 		praat_new (thee.transfer(), my name, L"_mean");
 	}
 END
@@ -627,7 +736,7 @@ END
 DIRECT (ERPTier_to_ERP_mean)
 	LOOP {
 		iam (ERPTier);
-		autoERP thee = my f_toERP_mean ();
+		autoERP thee = ERPTier_to_ERP_mean (me);
 		praat_new (thee.transfer(), my name, L"_mean");
 	}
 END
@@ -643,7 +752,7 @@ DO
 	ERPTier erpTier = FIRST (ERPTier);
 	Table table = FIRST (Table);
 	long columnNumber = Table_getColumnIndexFromColumnLabel (table, GET_STRING (L"Extract all events where column..."));
-	autoERPTier thee = erpTier -> f_extractEventsWhereColumn_number (table, columnNumber, GET_ENUM (kMelder_number, L"...is..."), GET_REAL (L"...the number"));
+	autoERPTier thee = ERPTier_extractEventsWhereColumn_number (erpTier, table, columnNumber, GET_ENUM (kMelder_number, L"...is..."), GET_REAL (L"...the number"));
 	praat_new (thee.transfer(), erpTier -> name);
 END
 
@@ -656,7 +765,7 @@ DO
 	ERPTier erpTier = FIRST (ERPTier);
 	Table table = FIRST (Table);
 	long columnNumber = Table_getColumnIndexFromColumnLabel (table, GET_STRING (L"Extract all events where column..."));
-	autoERPTier thee = erpTier -> f_extractEventsWhereColumn_string (table, columnNumber, GET_ENUM (kMelder_string, L"..."), GET_STRING (L"...the text"));
+	autoERPTier thee = ERPTier_extractEventsWhereColumn_string (erpTier, table, columnNumber, GET_ENUM (kMelder_string, L"..."), GET_STRING (L"...the text"));
 	praat_new (thee.transfer(), erpTier -> name);
 END
 
@@ -668,9 +777,12 @@ DIRECT (ERPTier_help) Melder_help (L"ERPTier"); END
 /***** file recognizers *****/
 
 static Any bdfFileRecognizer (int nread, const char *header, MelderFile file) {
+	(void) header;
 	const wchar_t *fileName = MelderFile_name (file);
-	bool isBdfFile = wcsstr (fileName, L".bdf") != NULL || wcsstr (fileName, L".BDF") != NULL;
-	bool isEdfFile = wcsstr (fileName, L".edf") != NULL || wcsstr (fileName, L".EDF") != NULL;
+	bool isBdfFile = Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".bdf") ||
+	                 Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".BDF");
+	bool isEdfFile = Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".edf") ||
+	                 Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".EDF");
 	if (nread < 512 || (! isBdfFile && ! isEdfFile)) return NULL;
 	return EEG_readFromBdfFile (file);
 }
@@ -694,16 +806,23 @@ void praat_EEG_init (void) {
 	praat_addAction1 (classEEG, 0, L"Modify -", 0, 0, 0);
 		praat_addAction1 (classEEG, 0, L"Set channel name...", 0, 1, DO_EEG_setChannelName);
 		praat_addAction1 (classEEG, 1, L"Edit external electrode names...", 0, 1, DO_EEG_editExternalElectrodeNames);
-		praat_addAction1 (classEEG, 0, L"-- processing --", 0, 1, DO_EEG_detrend);
+		praat_addAction1 (classEEG, 0, L"-- processing --", 0, 1, 0);
 		praat_addAction1 (classEEG, 0, L"Subtract reference...", 0, 1, DO_EEG_subtractReference);
 		praat_addAction1 (classEEG, 0, L"Subtract mean channel...", 0, 1, DO_EEG_subtractMeanChannel);
 		praat_addAction1 (classEEG, 0, L"Detrend", 0, 1, DO_EEG_detrend);
 		praat_addAction1 (classEEG, 0, L"Filter...", 0, 1, DO_EEG_filter);
+		praat_addAction1 (classEEG, 0, L"Remove triggers...", 0, 1, DO_EEG_removeTriggers);
 		praat_addAction1 (classEEG, 0, L"Set channel to zero...", 0, 1, DO_EEG_setChannelToZero);
 	praat_addAction1 (classEEG, 0, L"Analyse", 0, 0, 0);
 		praat_addAction1 (classEEG, 0, L"Extract channel...", 0, 0, DO_EEG_extractChannel);
 		praat_addAction1 (classEEG, 1, L"Extract part...", 0, 0, DO_EEG_extractPart);
-		praat_addAction1 (classEEG, 0, L"To ERPTier...", 0, 0, DO_EEG_to_ERPTier);
+		praat_addAction1 (classEEG, 0, L"To ERPTier -", 0, 0, 0);
+		praat_addAction1 (classEEG, 0, L"To ERPTier (bit)...", 0, 1, DO_EEG_to_ERPTier_bit);
+		praat_addAction1 (classEEG, 0, L"To ERPTier (marker)...", 0, 1, DO_EEG_to_ERPTier_marker);
+		praat_addAction1 (classEEG, 0, L"To ERPTier (triggers)...", 0, 1, DO_EEG_to_ERPTier_triggers);
+		praat_addAction1 (classEEG, 0, L"To ERPTier (triggers, preceded)...", 0, 1, DO_EEG_to_ERPTier_triggers_preceded);
+		praat_addAction1 (classEEG, 0, L"To ERPTier...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_EEG_to_ERPTier_bit);
+		praat_addAction1 (classEEG, 0, L"To MixingMatrix...", 0, 0, DO_EEG_to_MixingMatrix);
 	praat_addAction1 (classEEG, 0, L"Synthesize", 0, 0, 0);
 		praat_addAction1 (classEEG, 0, L"Concatenate", 0, 0, DO_EEGs_concatenate);
 	praat_addAction1 (classEEG, 0, L"Hack -", 0, 0, 0);
@@ -714,6 +833,8 @@ void praat_EEG_init (void) {
 	praat_addAction1 (classERP, 0, L"Draw -", 0, 0, 0);
 		praat_addAction1 (classERP, 0, L"Draw...", 0, 1, DO_ERP_draw);
 		praat_addAction1 (classERP, 0, L"Draw scalp...", 0, 1, DO_ERP_drawScalp);
+		praat_addAction1 (classERP, 0, L"Draw scalp (colour)...", 0, 1, DO_ERP_drawScalp_colour);
+		praat_addAction1 (classERP, 0, L"Draw scalp (garnish)...", 0, 1, DO_ERP_drawScalp_garnish);
 	praat_addAction1 (classERP, 0, L"Tabulate -", 0, 0, 0);
 		praat_addAction1 (classERP, 0, L"Down to Table...", 0, 1, DO_ERP_downto_Table);
 	praat_addAction1 (classERP, 0, L"Query -", 0, 0, 0);
diff --git a/LPC/Cepstrogram.cpp b/LPC/Cepstrogram.cpp
index 52fa4a5..4507640 100644
--- a/LPC/Cepstrogram.cpp
+++ b/LPC/Cepstrogram.cpp
@@ -61,7 +61,7 @@ PowerCepstrogram PowerCepstrogram_create (double tmin, double tmax, long nt, dou
 	}
 }
 
-void PowerCepstrogram_paint (PowerCepstrogram me, Graphics g, double tmin, double tmax, double qmin, double qmax, double dBminimum, double dBmaximum, int garnish) {
+void PowerCepstrogram_paint (PowerCepstrogram me, Graphics g, double tmin, double tmax, double qmin, double qmax, double dBmaximum, int autoscaling, double dynamicRangedB, double dynamicCompression, int garnish) {
 	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }
 	if (qmax <= qmin) { qmin = my ymin; qmax = my ymax; }
 	long itmin, itmax, ifmin, ifmax;
@@ -79,9 +79,24 @@ void PowerCepstrogram_paint (PowerCepstrogram me, Graphics g, double tmin, doubl
 			thy z[i][j] = val;
 		}
 	}
-	if (dBmaximum <= dBminimum) {
+	double dBminimum = dBmaximum - dynamicRangedB;
+	if (autoscaling) {
 		dBminimum = min; dBmaximum = max;
 	}
+
+	for (long j = 1; j <= my nx; j++) {
+		double lmax = thy z[1][j];
+		for (long i = 2; i <= my ny; i++) {
+			if (thy z[i][j] > lmax) {
+				lmax = thy z[i][j];
+			}
+		}
+		double factor = dynamicCompression * (max - lmax);
+		for (long i = 1; i <= my ny; i++) {
+			thy z[i][j] += factor;
+		}
+	}
+	
 	Graphics_setInner (g);
 	Graphics_setWindow (g, tmin, tmax, qmin, qmax);
 	Graphics_image (g, thy z,
diff --git a/LPC/Cepstrogram.h b/LPC/Cepstrogram.h
index 59ac866..e198212 100644
--- a/LPC/Cepstrogram.h
+++ b/LPC/Cepstrogram.h
@@ -54,7 +54,7 @@ Cepstrogram Cepstrogram_create (double tmin, double tmax, long nt, double dt, do
 PowerCepstrogram PowerCepstrogram_create (double tmin, double tmax, long nt, double dt, double t1,
 	double qmin, double qmax, long nq, double dq, double q1);
 
-void PowerCepstrogram_paint (PowerCepstrogram me, Graphics g, double tmin, double tmax, double qmin, double qmax, double dBminimum, double dBmaximum, int garnish);
+void PowerCepstrogram_paint (PowerCepstrogram me, Graphics g, double tmin, double tmax, double qmin, double qmax, double dBmaximum, int autoscaling, double dynamicRangedB, double dynamicCompression, int garnish);
 
 PowerCepstrogram PowerCepstrogram_smooth (PowerCepstrogram me, double timeAveragingWindow, double quefrencyAveragingWindow);
 
diff --git a/LPC/Cepstrum.cpp b/LPC/Cepstrum.cpp
index c4840e4..81b4469 100644
--- a/LPC/Cepstrum.cpp
+++ b/LPC/Cepstrum.cpp
@@ -1,6 +1,6 @@
 /* Cepstrum.cpp
  *
- * Copyright (C) 1994-2013 David Weenink
+ * Copyright (C) 1994-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -315,7 +315,7 @@ void PowerCepstrum_fitTiltLine (PowerCepstrum me, double qmin, double qmax, doub
 }
 
 // Hillenbrand subtracts dB values and if the result is negative it is made zero
-void PowerCepstrum_subtractTiltLine_inline (PowerCepstrum me, double slope, double intercept, int lineType) {
+void PowerCepstrum_subtractTiltLine_inline2 (PowerCepstrum me, double slope, double intercept, int lineType) {
 	for (long j = 1; j <= my nx; j++) {
 		double q = my x1 + (j - 1) * my dx;
 		q = j == 1 ? 0.5 * my dx : q; // approximation
@@ -327,6 +327,22 @@ void PowerCepstrum_subtractTiltLine_inline (PowerCepstrum me, double slope, doub
 	}
 }
 
+// clip with tilt line
+void PowerCepstrum_subtractTiltLine_inline (PowerCepstrum me, double slope, double intercept, int lineType) {
+	for (long j = 1; j <= my nx; j++) {
+		double q = my x1 + (j - 1) * my dx;
+		q = j == 1 ? 0.5 * my dx : q; // approximation
+		double xq = lineType == 2 ? log(q) : q;
+		double db_background = slope * xq + intercept;
+		double db_cepstrum = my v_getValueAtSample (j, 1, 0);
+		double diff = db_cepstrum - db_background;
+		if (diff < 0) {
+			diff = 0;
+		}
+		my z[1][j] = exp (diff * NUMln10 / 10) - 1e-30;
+	}
+}
+
 
 void PowerCepstrum_subtractTilt_inline (PowerCepstrum me, double qstartFit, double qendFit, int lineType, int fitMethod) {
 	double slope, intercept;
diff --git a/LPC/LPC.cpp b/LPC/LPC.cpp
index ff7e166..6e939db 100644
--- a/LPC/LPC.cpp
+++ b/LPC/LPC.cpp
@@ -1,6 +1,6 @@
 /* LPC.cpp
  *
- * Copyright (C) 1994-2012 David Weenink
+ * Copyright (C) 1994-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -114,7 +114,7 @@ void LPC_drawGain (LPC me, Graphics g, double tmin, double tmax, double gmin, do
 	Graphics_setWindow (g, tmin, tmax, gmin, gmax);
 	for (long iframe = itmin; iframe <= itmax; iframe++) {
 		double x = Sampled_indexToX (me, iframe);
-		Graphics_fillCircle_mm (g, x, gain[iframe], 1.0);
+		Graphics_speckle (g, x, gain[iframe]);
 	}
 	Graphics_unsetInner (g);
 	if (garnish) {
diff --git a/LPC/Sound_and_LPC_robust.cpp b/LPC/Sound_and_LPC_robust.cpp
index f94f24a..19215ec 100644
--- a/LPC/Sound_and_LPC_robust.cpp
+++ b/LPC/Sound_and_LPC_robust.cpp
@@ -245,12 +245,12 @@ LPC LPC_and_Sound_to_LPC_robust (LPC thee, Sound me, double analysisWidth, doubl
 	}
 }
 
-Formant Sound_to_Formant_robust (Sound me, double dt_in, int numberOfPoles, double maximumFrequency,
+Formant Sound_to_Formant_robust (Sound me, double dt_in, double numberOfFormants, double maximumFrequency,
 	double halfdt_window, double preEmphasisFrequency, double safetyMargin, double k, int itermax, double tol, int wantlocation)
 {
 	double dt = dt_in > 0.0 ? dt_in : halfdt_window / 4.0;
 	double nyquist = 0.5 / my dx;
-	long predictionOrder = 2 * numberOfPoles;
+	int predictionOrder = 2 * numberOfFormants;
 	try {
 		autoSound sound = NULL;
 		if (maximumFrequency <= 0.0 || fabs (maximumFrequency / nyquist - 1) < 1.0e-12) {
diff --git a/LPC/Sound_and_LPC_robust.h b/LPC/Sound_and_LPC_robust.h
index 7183b49..5d86f9e 100644
--- a/LPC/Sound_and_LPC_robust.h
+++ b/LPC/Sound_and_LPC_robust.h
@@ -39,7 +39,7 @@ void LPC_Frames_and_Sound_huber (LPC_Frame me, Sound thee, LPC_Frame him, struct
 LPC LPC_and_Sound_to_LPC_robust (LPC thee, Sound me, double analysisWidth,
 	double preEmphasisFrequency, double k, int itermax, double tol, int wantlocation);
 
-Formant Sound_to_Formant_robust (Sound me, double dt_in, int numberOfPoles, double maximumFrequency,
+Formant Sound_to_Formant_robust (Sound me, double dt_in, double numberOfFormants, double maximumFrequency,
 	double halfdt_window, double preemphasisFrequency, double safetyMargin, double k, int itermax, double tol, int wantlocation);
 
 #endif /* _Sound_and_LPC_robust_h_ */
diff --git a/LPC/manual_LPC.cpp b/LPC/manual_LPC.cpp
index 7e847f2..f88143e 100644
--- a/LPC/manual_LPC.cpp
+++ b/LPC/manual_LPC.cpp
@@ -1,6 +1,6 @@
 /* manual_LPC.c
  *
- * Copyright (C) 1994-2013 David Weenink
+ * Copyright (C) 1994-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,8 +25,8 @@
 #include "ManPagesM.h"
 #include "MFCC.h"
 
-void manual_LPC_init (ManPages me);
-void manual_LPC_init (ManPages me)
+void manual_LPC (ManPages me);
+void manual_LPC (ManPages me)
 {
 
 MAN_BEGIN (L"CC: Paint...", L"djmw", 20040407)
@@ -65,6 +65,29 @@ NORMAL (L"where %z__%ji_ is the matrix element in row %j and column %i and "
 	"%c__%ij_ is the %j-th cepstral coefficient in frame %i.")
 MAN_END
 
+MAN_BEGIN (L"Formants: Extract smoothest part...", L"djmw", 20140313)
+INTRO (L"Extracts the part from one of the selected formants which shows the smoothest formant tracks in a given interval.")
+ENTRY (L"Settings")
+SCRIPT (5, Manual_SETTINGS_WINDOW_HEIGHT (5), L""
+	Manual_DRAW_SETTINGS_WINDOW (L"Formants: Extract smoothest part", 5)
+	Manual_DRAW_SETTINGS_WINDOW_RANGE (L"Time range (s)", L"0.0", L"0.0")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE (L"Fitter formant range", L"1", L"3")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD (L"Order of polynomials", L"3")
+	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN (L"Use bandwidths to model formant tracks", 1)
+	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN (L"Bandwidths for smoothing test", 0)
+)
+TAG (L"##Time range (s)#")
+DEFINITION (L"determines the position of the intervals that have to be compared.")
+TAG (L"##Fitter formant range")
+DEFINITION (L"determines which formant tracks will be modelled with a polynomial function. The goodness of fit of these models will be used in the comparison.")
+TAG (L"##Order of polynomials")
+DEFINITION (L"determines the maximum order of the polynomials that are used in modeling each formant track. Order 0 means a model which is a constant function; this model needs only one parameter. Order 1 means a model that is a straight line function; this order needs two parameters. Order 2 means that an additional parabolic function is used in the modeling; order 2 needs therefore 3 parameters. In general an order %p model needs %p+1 parameters.")
+TAG (L"##Use bandwidths to model formant tracks")
+DEFINITION (L"Bandwidths give an indication about the sharpness of a spectral peak. Sharp peaks have small bandwidths and, vice versa, broad peaks have large bandwidths. The width of a peak can also be interpreted as a measure of certainty for its formant frequency value. Setting this option %%on%, the default setting, means that you force the modeling function to be closer to frequencies that are well defined, i.e. that have sharp peaks, than to the frequencies of broad peaks, if choice [...]
+TAG (L"##Bandwidths for smoothing test")
+DEFINITION (L"determines whether for the smoothnes determination the formant frequencies are still needed. Not using them anymore probably gives a better indication of the smoothness of a track.")
+MAN_END
+
 MAN_BEGIN (L"PowerCepstrogram", L"djmw", 20130616)
 INTRO (L"One of the @@types of objects@ in P\\s{RAAT}.")
 ENTRY (L"Description")
@@ -74,20 +97,56 @@ MAN_END
 MAN_BEGIN (L"PowerCepstrogram: To Table (peak prominence)...", L"djmw", 20130616)
 INTRO (L"A command to create a table with @@PowerCepstrum: Get peak prominence...|cepstral peak prominence@ values.")
 ENTRY (L"Settings")
-SCRIPT (7, Manual_SETTINGS_WINDOW_HEIGHT (7), L""
+SCRIPT (5, Manual_SETTINGS_WINDOW_HEIGHT (7), L""
 	Manual_DRAW_SETTINGS_WINDOW ("PowerCepstrogram: To Table (peak prominence)", 7)
 	Manual_DRAW_SETTINGS_WINDOW_RANGE("Peak search pitch range (Hz)", L"60.0", L"300.0")
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (L"Interpolation", L"None", 0)
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (L"", L"Parabolic", 0)
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (L"", L"Cubic", 1)
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (L"", L"Sinc70", 0)
-	Manual_DRAW_SETTINGS_WINDOW_RANGE("Tilt line quefrency range (s)",L"0.001", L"0.0 (=end)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Tilt line quefrency range (s)", L"0.001", L"0.0 (=end)")
 	Manual_DRAW_SETTINGS_WINDOW_OPTIONMENU(L"Fit method", L"Robust")
 )
 NORMAL (L"The meaning of these settings is explained @@PowerCepstrum: Get peak prominence...|here at .")
 MAN_END
 
-MAN_BEGIN (L"PowerCepstrogram: Smooth...", L"djmw", 20130410)
+MAN_BEGIN(L"PowerCepstrogram: Paint...", L"djmw", 20131001)
+INTRO (L"A command to draw the selected @@PowerCepstrogram@ object(s) in shades of grey.")
+ENTRY (L"Settings")
+SCRIPT (5, Manual_SETTINGS_WINDOW_HEIGHT (7), L""
+	Manual_DRAW_SETTINGS_WINDOW (L"PowerCepstrogram: Paint", 7)
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Time range (s)", L"0.0", L"0.0")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Quefrency range (s)", L"0.0", L"0.0")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD("Maximum (dB)",L"80.0")
+	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Autoscaling",0)
+	Manual_DRAW_SETTINGS_WINDOW_FIELD("Dynamic range (dB)",L"30.0")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD("Dynamic compression (0-1)",L"0.0")
+	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Garnish",1)
+)
+TAG (L"##Time range (s)")
+DEFINITION (L"the time domain along the %%x% axis.")
+TAG (L"##Quefrency range (s)")
+DEFINITION (L"the quefency domain along the %%y% axis.")
+TAG (L"##Maximum (dB)")
+DEFINITION (L"cells that have cepstral values greater or equal than this value are drawn in black.")
+TAG (L"##Autoscaling")
+DEFINITION (L"If %%on%, overrules the effects of the previous option and the following three options. I.e. the global maximum and the "
+	"global minimum cepstral values determine the maximum blackness and the minimal blackness. Values in-between have apropriate "
+	"values of grey.")
+TAG (L"##Dynamic range (dB)")
+DEFINITION (L"All values more than %%Dynamic range% below the maximum will be drawn in white. Values in-between have apropriate "
+	"values of grey.")
+TAG (L"##Dynamic compression (0-1)")
+DEFINITION (L"determines how much stronger weak frames should be made before drawing. Normally this parameter is between 0 and 1. "
+	"If it is 0, no dynamic compression will take place. If it is 1, all time frames (vertical bands) will contain cepstral values "
+	"that are drawn in black. If the global %%maximum% is set at 80 dB, %%autoscaling% is off, %%dynamic range% is 30 dB and "
+	"%%dynamic compression% is 0.4 then for a frame with a local maximum of 40 dB all values will be lifted by 0.4*(80-40)=16 dB, "
+	"so that its maximum will be seen at 56 dB (thus making the corresponding cell visible).")
+TAG (L"##Garnish")
+DEFINITION (L"Draws a box around the cepstrogram and labels the axes.")
+MAN_END
+
+MAN_BEGIN (L"PowerCepstrogram: Smooth...", L"djmw", 20140117)
 INTRO (L"Smoothes the selected @PowerCepstrogram by averaging cepstra. The smoothed PowerCepstrogram is the result of two separate steps. "
 	"In the first step, cepsta are averaged across time. In the second step, cepstra are averaged across quefrency.")
 ENTRY (L"Settings")
@@ -107,8 +166,8 @@ ENTRY (L"Note")
 NORMAL (L"The following commands should reproduce the smoothing described in the @@Hillenbrand & Houde (1996)@ article, where they use a 20 ms "
 	"(10 frame) time smoothing and a 1 ms (10 bin) quefrency smoothing. ")
 CODE (L"selectObject (\"Sound xxx\")")
-CODE (L"do (\"To PowerCepstrogram...\", 0.041, 0.002, 5000.0)")
-CODE (L"do (\"Smooth...\", 0.02, 0.001)")
+CODE (L"To PowerCepstrogram: 0.041, 0.002, 5000.0")
+CODE (L"Smooth: 0.02, 0.001")
 MAN_END
 
 MAN_BEGIN (L"Cepstrum", L"djmw", 20130616)
diff --git a/LPC/praat_LPC_init.cpp b/LPC/praat_LPC_init.cpp
index 11de9d7..0d609c2 100644
--- a/LPC/praat_LPC_init.cpp
+++ b/LPC/praat_LPC_init.cpp
@@ -29,7 +29,6 @@
 */
 
 #include <math.h>
-#include "praat.h"
 #include "Cepstrumc.h"
 #include "Cepstrogram.h"
 #include "Cepstrum_and_Spectrum.h"
@@ -47,6 +46,7 @@
 #include "LPC_to_Spectrum.h"
 #include "NUM2.h"
 #include "MelFilter_and_MFCC.h"
+#include "praatP.h"
 #include "Sound_and_LPC.h"
 #include "Sound_and_LPC_robust.h"
 #include "Sound_and_Cepstrum.h"
@@ -362,8 +362,7 @@ DIRECT (PowerCepstrogram_help)
 	Melder_help (L"PowerCepstrogram");
 END
 
-
-FORM (PowerCepstrogram_paint, L"PowerCepstrogram: Paint", 0)
+FORM (old_PowerCepstrogram_paint, L"PowerCepstrogram: Paint", 0)
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0")
 	REAL (L"left Quefrency range (s)", L"0.0")
@@ -378,7 +377,30 @@ DO
 		iam (PowerCepstrogram);
 		PowerCepstrogram_paint (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
 			GET_REAL (L"left Quefrency range"), GET_REAL (L"right Quefrency range"),
-  			GET_REAL (L"Minimum"), GET_REAL (L"Maximum"), GET_INTEGER (L"Garnish"));
+			GET_REAL (L"Maximum"), false, GET_REAL (L"Maximum") - GET_REAL (L"Minimum"),
+			0.0, GET_INTEGER (L"Garnish"));
+        }
+END
+
+FORM (PowerCepstrogram_paint, L"PowerCepstrogram: Paint", L"PowerCepstrogram: Paint...")
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	REAL (L"left Quefrency range (s)", L"0.0")
+	REAL (L"right Quefrency range (s)", L"0.0")
+	REAL (L"Maximum (dB)", L"80.0")
+	BOOLEAN (L"Autoscaling", 0);
+	REAL (L"Dynamic range (dB)", L"30.0");
+	REAL (L"Dynamic compression (0-1)", L"0.0");
+	BOOLEAN (L"Garnish", 1);
+	OK
+DO_ALTERNATIVE (old_PowerCepstrogram_paint)
+	autoPraatPicture picture;
+	LOOP {
+		iam (PowerCepstrogram);
+		PowerCepstrogram_paint (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+		GET_REAL (L"left Quefrency range"), GET_REAL (L"right Quefrency range"),
+		GET_REAL (L"Maximum"), GET_INTEGER (L"Autoscaling"), GET_REAL (L"Dynamic range"),
+		GET_REAL (L"Dynamic compression"), GET_INTEGER (L"Garnish"));
 	}
 END
 
@@ -547,7 +569,7 @@ DO
 	}
 END
 
-FORM (PowerCepstrogram_to_Table_cpp, L"PowerCepstrogram: To Table (peak prominence)", L"PowerCepstrogram: To Table (peak prominence...")
+FORM (PowerCepstrogram_to_Table_cpp, L"PowerCepstrogram: To Table (peak prominence)", L"PowerCepstrogram: To Table (peak prominence)...")
 	REAL (L"left Peak search pitch range (Hz)", L"60.0")
 	REAL (L"right Peak search pitch range (Hz)", L"330.0")
 	POSITIVE (L"Tolerance (0-1)", L"0.05")
@@ -674,7 +696,7 @@ DO
 	}
 END
 
-/******************** Formant & Spectrogram ********************************************/
+/******************** Formant & Spectrogram ************************************/
 
 FORM (Formant_and_Spectrogram_to_IntensityTier, L"Formant & Spectrogram: To IntensityTier", L"Formant & Spectrogram: To IntensityTier...")
 	NATURAL (L"Formant number", L"1")
@@ -687,7 +709,6 @@ DO
 	praat_new (him.transfer(), my name, L"_", Melder_integer (GET_INTEGER (L"Formant number")));
 END
 
-
 /********************LFCC ********************************************/
 
 DIRECT (LFCC_help)
@@ -910,7 +931,7 @@ END
 	
 FORM (Sound_to_Formant_robust, L"Sound: To Formant (robust)", L"Sound: To Formant (robust)...")
 	REAL (L"Time step (s)", L"0.0 (= auto)")
-	POSITIVE (L"Max. number of formants", L"5")
+	POSITIVE (L"Max. number of formants", L"5.0")
 	REAL (L"Maximum formant (Hz)", L"5500 (= adult female)")
 	POSITIVE (L"Window length (s)", L"0.025")
 	POSITIVE (L"Pre-emphasis from (Hz)", L"50")
@@ -1245,6 +1266,8 @@ void praat_uvafon_LPC_init (void) {
 	praat_addAction1 (classFormant, 0, L"Formula...", L"Formula (bandwidths)...", 1, DO_Formant_formula);
 	praat_addAction2 (classFormant, 1, classSpectrogram, 1, L"To IntensityTier...", 0, 0, DO_Formant_and_Spectrogram_to_IntensityTier);
 
+	
+	
 	praat_addAction1 (classLFCC, 0, L"LFCC help", 0, 0, DO_LFCC_help);
 	praat_CC_init (classLFCC);
 	praat_addAction1 (classLFCC, 0, L"To LPC...", 0, 0, DO_LFCC_to_LPC);
@@ -1301,8 +1324,10 @@ void praat_uvafon_LPC_init (void) {
 	praat_addAction1 (classVocalTractTier, 0, L"To LPC...", 0, 0, DO_VocalTractTier_to_LPC);
 	praat_addAction1 (classVocalTractTier, 0, L"To VocalTract...", 0, 0, DO_VocalTractTier_to_VocalTract);
 	praat_addAction2 (classVocalTractTier, 1, classVocalTract, 1, L"Add VocalTract...", 0, 0, DO_VocalTractTier_addVocalTract);
-	INCLUDE_MANPAGES (manual_LPC_init)
+	INCLUDE_MANPAGES (manual_LPC)
+	INCLUDE_MANPAGES (manual_DataModeler)
 
+	INCLUDE_LIBRARY (praat_DataModeler_init)
 }
 
 /* End of file praat_LPC_init.c */
diff --git a/artsynth/Art_Speaker.cpp b/artsynth/Art_Speaker.cpp
index a9086b0..d9d2c37 100644
--- a/artsynth/Art_Speaker.cpp
+++ b/artsynth/Art_Speaker.cpp
@@ -1,6 +1,6 @@
 /* Art_Speaker.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -418,7 +418,7 @@ void Art_Speaker_drawMesh (Art art, Speaker speaker, Graphics graphics) {
 		Graphics_line (graphics, xmm [i], ymm [i], xmm [i + 1], ymm [i + 1]);
 
 	for (i = 1; i <= Art_Speaker_meshCount + 1; i ++)
-		Graphics_fillCircle_mm (graphics, xmm [i], ymm [i], 1.0);
+		Graphics_speckle (graphics, xmm [i], ymm [i]);
 	Graphics_setTextAlignment (graphics, Graphics_LEFT, Graphics_HALF);
 	Graphics_text (graphics, 0.0, 0.0, L"O");   /* Origin. */
 	Graphics_resetViewport (graphics, previous);
diff --git a/artsynth/Delta.cpp b/artsynth/Delta.cpp
index 7a5dc91..bb9cc0d 100644
--- a/artsynth/Delta.cpp
+++ b/artsynth/Delta.cpp
@@ -1,6 +1,6 @@
 /* Delta.cpp
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,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
@@ -22,16 +22,16 @@
 Thing_implement (Delta, Thing, 0);
 
 void structDelta :: v_destroy () {
-	NUMvector_free (this -> tube, 1);
+	NUMvector_free (our tube, 1);
 	Delta_Parent :: v_destroy ();
 }
 
 void structDelta :: init (int a_numberOfTubes) {
 	Melder_assert (a_numberOfTubes >= 1);
-	this -> numberOfTubes = a_numberOfTubes;
-	this -> tube = NUMvector <struct structDelta_Tube> (1, a_numberOfTubes);
+	our numberOfTubes = a_numberOfTubes;
+	our tube = NUMvector <struct structDelta_Tube> (1, a_numberOfTubes);
 	for (int itube = 1; itube <= a_numberOfTubes; itube ++) {
-		Delta_Tube t = this -> tube + itube;
+		Delta_Tube t = our tube + itube;
 		t -> parallel = 1;
 	}
 }
diff --git a/contrib/ola/KNN.cpp b/contrib/ola/KNN.cpp
index e0c1240..7ab7df7 100644
--- a/contrib/ola/KNN.cpp
+++ b/contrib/ola/KNN.cpp
@@ -1591,7 +1591,7 @@ Permutation KNN_SA_ToPermutation
     KNN_SA_t * istruct = KNN_SA_t_create(my input); 
     Permutation result = Permutation_create(my nInstances);
 
-    gsl_siman_params_t params = {tries, iterations, step_size, boltzmann_c, temp_start, damping_f, temp_stop};
+    gsl_siman_params_t params = { (int) tries, (int) iterations, step_size, boltzmann_c, temp_start, damping_f, temp_stop};
 
     gsl_rng_env_setup();
     T = gsl_rng_default;
diff --git a/dwsys/Eigen.cpp b/dwsys/Eigen.cpp
index 6cb9bec..c3296cb 100644
--- a/dwsys/Eigen.cpp
+++ b/dwsys/Eigen.cpp
@@ -1,6 +1,6 @@
 /* Eigen.c
  *
- * Copyright (C) 1993-2012 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -260,7 +260,7 @@ void Eigen_initFromSymmetricMatrix (I, double **a, long n) {
 	(void) NUMlapack_dsyev (&jobz, &uplo, &n, &my eigenvectors[1][1], &n,
 	                        &my eigenvalues[1], wt, &lwork, &info);
 	if (info != 0) {
-		Melder_throw ("dsyev fails");
+		Melder_throw ("dsyev initialization fails");
 	}
 
 	lwork = wt[0];
diff --git a/dwsys/Graphics_extensions.cpp b/dwsys/Graphics_extensions.cpp
index ea14348..054cced 100644
--- a/dwsys/Graphics_extensions.cpp
+++ b/dwsys/Graphics_extensions.cpp
@@ -1,6 +1,6 @@
 /* Graphics_extensions.c
  *
- * Copyright (C) 2012 David Weenink
+ * Copyright (C) 2012 -2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -39,7 +39,7 @@ void Graphics_boxAndWhiskerPlot (Graphics g, double data[], long ndata, double x
 		return;
 	}
 	/*
-		Sort the data (increasing: data[1] <= ... <= data[ndata]).
+		Sort the data (ascending: data[1] <= ... <= data[ndata]).
 		Get the median (q50) and the upper and lower quartile points
 		(q25 and q75).
 		Now q25 and q75 are the lower and upper hinges, respectively.
@@ -174,7 +174,7 @@ void Graphics_boxAndWhiskerPlot (Graphics g, double data[], long ndata, double x
 	}
 }
 
-void Graphics_quantileQuantilePlot (Graphics g, long numberOfQuantiles, double *xdata, long xnumberOfData, double *ydata, long ynumberOfData, double xmin, double xmax, double ymin, double ymax, int labelSize, const wchar_t *plotLabel) {
+void Graphics_quantileQuantilePlot (Graphics g, long numberOfQuantiles, double xdata[], long xnumberOfData, double ydata[], long ynumberOfData, double xmin, double xmax, double ymin, double ymax, int labelSize, const wchar_t *plotLabel) {
 	int fontSize = Graphics_inqFontSize (g);
 
 	Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
@@ -248,4 +248,22 @@ void Graphics_matrixAsSquares (Graphics g, double **matrix, long numberOfRows, l
 	}
 }
 
+void Graphics_lagPlot (Graphics g, double data[], long numberOfData, double xmin, double xmax, long lag, int labelSize, const wchar_t *plotLabel) {
+	if (lag < 0 || lag >= numberOfData) {
+		return;
+	}
+	int fontSize = Graphics_inqFontSize (g);
+	Graphics_setFontSize (g, labelSize);
+	Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
+	// plot x[i] vertically and x[i-lag] horizontally
+	for (long i = 1; i <= numberOfData - lag; i++) {
+		double x = data[i + lag], y = data[i];
+		if (x >= xmin && x <= xmax && y >= xmin && y <= xmax) {
+			Graphics_text (g, x, y, plotLabel);
+		}
+	}
+	Graphics_setLineType (g, Graphics_DRAWN);
+	Graphics_setFontSize (g, fontSize);
+}
+
 /* End of file Graphics_extensions.c */
diff --git a/dwsys/Graphics_extensions.h b/dwsys/Graphics_extensions.h
index 964cfaa..cd5d1da 100644
--- a/dwsys/Graphics_extensions.h
+++ b/dwsys/Graphics_extensions.h
@@ -2,7 +2,7 @@
 #define _Graphics_extensions_h_
 /* Graphics_extensions.h
  *
- * Copyright (C) 2012 David Weenink
+ * Copyright (C) 2012-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -33,8 +33,10 @@
 
 void Graphics_boxAndWhiskerPlot (Graphics g, double data[], long ndata, double x, double r, double w, double ymin, double ymax);
 
-void Graphics_quantileQuantilePlot (Graphics g, long numberOfQuantiles, double *xdata, long xnumberOfData, double *ydata, long ynumberOfData, double xmin, double xmax, double ymin, double ymax, int labelSize, const wchar_t *plotLabel);
+void Graphics_quantileQuantilePlot (Graphics g, long numberOfQuantiles, double xdata[], long xnumberOfData, double ydata[], long ynumberOfData, double xmin, double xmax, double ymin, double ymax, int labelSize, const wchar_t *plotLabel);
 
 void Graphics_matrixAsSquares (Graphics g, double **matrix, long numberOfRows, long numberOfColumns, double zmin, double zmax, double cellSizeFactor, int randomFillOrder);
 
+void Graphics_lagPlot (Graphics g, double x[], long numberOfData, double xmin, double xmax, long lag, int labelSize, const wchar_t *plotLabel);
+
 #endif /* _Graphics_extensions_h_ */
diff --git a/dwsys/NUM2.cpp b/dwsys/NUM2.cpp
index 4701ea7..9fe1121 100644
--- a/dwsys/NUM2.cpp
+++ b/dwsys/NUM2.cpp
@@ -1,6 +1,6 @@
 /* NUM2.cpp
  *
- * Copyright (C) 1993-2012 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -61,6 +61,7 @@
  djmw 20101209 removed NUMwcscmp is Melder_wcscmp now
  djmw 20110304 Thing_new
  djmw 20111110 use autostringvector
+ djmw 20140318 NUMvector_avevar now returns variance instead of sigma^2
 */
 
 #include <vector>
@@ -336,7 +337,7 @@ void NUMvector_avevar (double *a, long n, double *average, double *variance) {
 			var += s * s;
 		}
 
-		var = (var - eps * eps / n) / (n - 1);
+		var = (var - eps * eps / n);
 	} else {
 		var = NUMundefined;
 	}
@@ -370,7 +371,7 @@ void NUMcolumn_avevar (double **a, long nr, long nc, long icol, double *average,
 			var += s * s;
 		}
 
-		var = (var - eps * eps / nr) / (nr - 1);
+		var = (var - eps * eps / nr);
 	} else {
 		var = NUMundefined;
 	}
@@ -381,7 +382,6 @@ void NUMcolumn_avevar (double **a, long nr, long nc, long icol, double *average,
 void NUMcolumn2_avevar (double **a, long nr, long nc, long icol1, long icol2,
                         double *average1, double *variance1, double *average2, double *variance2,
                         double *covariance) {
-	long ndf = nr - 1;
 	double eps1 = 0, eps2 = 0, mean1 = 0, mean2 = 0;
 	double var1 = 0, var2 = 0, covar = 0;
 
@@ -417,9 +417,8 @@ void NUMcolumn2_avevar (double **a, long nr, long nc, long icol1, long icol2,
 			covar += s1 * s2;
 		}
 
-		var1 = (var1 - eps1 * eps1 / nr) / ndf;
-		var2 = (var2 - eps2 * eps2 / nr) / ndf;
-		covar /= ndf;
+		var1 = (var1 - eps1 * eps1 / nr);
+		var2 = (var2 - eps2 * eps2 / nr);;
 	} else {
 		var1 = NUMundefined;
 		var2 = NUMundefined;
diff --git a/dwsys/NUM2.h b/dwsys/NUM2.h
index fa00292..ffc8cad 100644
--- a/dwsys/NUM2.h
+++ b/dwsys/NUM2.h
@@ -2,7 +2,7 @@
 #define _NUM2_h_
 /* NUM2.h
  *
- * Copyright (C) 1997-2012 David Weenink
+ * Copyright (C) 1997-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -316,7 +316,6 @@ void NUMmonotoneRegression (const double x[], long n, double xs[]);
     array b[1..n]. A characteristic of heapsort is that it does not conserve
     the order of equals: e.g., the array 3,1,1,2 will be sorted as 1,1,2,3.
     It may occur that a_sorted[1] = a_presorted[2] and a_sorted[2] = a_presorted[1]
-    no g
 */
 template<class T1, class T2>
 void NUMsort2 (long n, T1 *a, T2 *b) {
diff --git a/dwsys/SVD.cpp b/dwsys/SVD.cpp
index 90256c6..aaf4d40 100644
--- a/dwsys/SVD.cpp
+++ b/dwsys/SVD.cpp
@@ -1,6 +1,6 @@
 /* SVD.cpp
  *
- * Copyright (C) 1994-2011 David Weenink
+ * Copyright (C) 1994-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -244,27 +244,17 @@ void SVD_compute (SVD me) {
 
 // V D^2 V'or V D^-2 V
 void SVD_getSquared (SVD me, double **m, bool inverse) {
-	if (inverse) {
-		for (long i = 1; i <= my numberOfColumns; i++) {
-			for (long j = 1; j <= my numberOfColumns; j++) {
-				double val = 0;
-				for (long k = 1; k <= my numberOfColumns; k++) {
-					if (my d[k] > 0) {
-						val += my v[i][k] * my v[j][k] / (my d[k] * my d[k]);
-					}
-				}
-				m[i][j] = val;
-			}
-		}
-	} else {
-		for (long i = 1; i <= my numberOfColumns; i++) {
-			for (long j = 1; j <= my numberOfColumns; j++) {
-				double val = 0;
-				for (long k = 1; k <= my numberOfColumns; k++) {
-					val += my d[k] * my d[k] * my v[i][k] * my v[j][k];
+	for (long i = 1; i <= my numberOfColumns; i++) {
+		for (long j = 1; j <= my numberOfColumns; j++) {
+			double val = 0;
+			for (long k = 1; k <= my numberOfColumns; k++) {
+				if (my d[k] > 0) {
+					double dsq = my d[k] * my d[k];
+					double factor = inverse ? 1 / dsq : dsq;
+					val += my v[i][k] * my v[j][k] * factor;
 				}
-				m[i][j] = val;
 			}
+			m[i][j] = val;
 		}
 	}
 }
diff --git a/dwtest/KlattGrid_test.praat b/dwtest/KlattGrid_test.praat
index a2352c4..0d7c071 100644
--- a/dwtest/KlattGrid_test.praat
+++ b/dwtest/KlattGrid_test.praat
@@ -1,320 +1,314 @@
 # KlattGrid_test.praat
-# djmw 20081208, 20090420
+# djmw 20081208, 20090420, 20140113
 
 
-printline ===========    KlattGrid test start
+printline ===========		KlattGrid test start
 
 call test_get_add_remove_extract_replace_old_interface
 
 call test_get_add_remove_replace
 
-printline ===========    KlattGrid test end
+printline ===========		KlattGrid test end
 
 procedure test_get_add_remove_replace
-  numberOfOralFormants = 6
-  numberOfNasalFormants = 1
-  numberOfTrachealFormants = 1
-  numberOfFricationFormants = 5
-  numberOfNasalAntiFormants = 1
-  numberOfTrachealAntiFormants = 1
-  numberOfDeltaFormants = 1
-  tmin = 0
-  tmax = 1
-
-  kg = Create KlattGrid... kg tmin tmax numberOfOralFormants numberOfNasalFormants numberOfNasalAntiFormants 
-    ... numberOfFricationFormants numberOfTrachealFormants numberOfTrachealAntiFormants numberOfDeltaFormants
-
-  printline ===========    KlattGrid test_get_add_remove_extract_replace start
-  for .itime to 20
-    call setget_1 "pitch" 130
-    call setget_1 "voicing amplitude" 90
-    call setget_1 "flutter" 0.5
-    call setget_1 "power1" 2
-    call setget_1 "power2" 3
-    call setget_1 "open phase" 0.5
-    call setget_1 "collision phase" 0.035
-    call setget_1 "double pulsing" 0.4
-    call setget_1 "spectral tilt" 20
-    call setget_1 "aspiration amplitude" 90
-    call setget_1 "breathiness amplitude" 90
-
-    call setget_1 "frication bypass" 20
-    call setget_1 "frication amplitude" 50
-  
-    call setget_formants Oral
-    call setget_formants Nasal
-    call setget_formants Tracheal
-    call setget_formants Delta
-    call setget_formants Frication
-    call setget_antiformants Nasal
-    call setget_antiformants Tracheal
-
-  endfor
-
-  call test_deltaFormants
-
-  select kg
-  Remove
-
-  printline ===========    KlattGrid test_get_add_remove_extract_replace succesful
+	numberOfOralFormants = 6
+	numberOfNasalFormants = 1
+	numberOfTrachealFormants = 1
+	numberOfFricationFormants = 5
+	numberOfNasalAntiFormants = 1
+	numberOfTrachealAntiFormants = 1
+	numberOfDeltaFormants = 1
+	tmin = 0
+	tmax = 1
+
+	kg = Create KlattGrid... kg tmin tmax numberOfOralFormants numberOfNasalFormants numberOfNasalAntiFormants 
+		... numberOfFricationFormants numberOfTrachealFormants numberOfTrachealAntiFormants numberOfDeltaFormants
+
+	printline ===========		KlattGrid test_get_add_remove_extract_replace start
+	for .itime to 20
+		call setget_1 "pitch" 130
+		call setget_1 "voicing amplitude" 90
+		call setget_1 "flutter" 0.5
+		call setget_1 "power1" 2
+		call setget_1 "power2" 3
+		call setget_1 "open phase" 0.5
+		call setget_1 "collision phase" 0.035
+		call setget_1 "double pulsing" 0.4
+		call setget_1 "spectral tilt" 20
+		call setget_1 "aspiration amplitude" 90
+		call setget_1 "breathiness amplitude" 90
+
+		call setget_1 "frication bypass" 20
+		call setget_1 "frication amplitude" 50
+	
+		call setget_formants Oral
+		call setget_formants Nasal
+		call setget_formants Tracheal
+		call setget_formants Delta
+		call setget_formants Frication
+		call setget_antiformants Nasal
+		call setget_antiformants Tracheal
+
+	endfor
+
+	call test_deltaFormants
+
+	removeObject (kg)
+
+	printline ===========		KlattGrid test_get_add_remove_extract_replace succesful
 
 endproc
 
 procedure test_get_add_remove_extract_replace_old_interface
-  numberOfOralFormants = 6
-  numberOfNormalFormants = numberOfOralFormants
-  numberOfNasalFormants = 1
-  numberOfTrachealFormants = 1
-  numberOfFricationFormants = 5
-  numberOfNasalAntiFormants = 1
-  numberOfTrachealAntiFormants = 1
-  numberOfDeltaFormants = 1
-  tmin = 0
-  tmax = 1
-
-  printline ===========    KlattGrid test_get_add_remove_extract_replace_old_interface start
-
-  kg = Create KlattGrid... kg tmin tmax numberOfOralFormants numberOfNasalFormants numberOfNasalAntiFormants 
-    ... numberOfFricationFormants numberOfTrachealFormants numberOfTrachealAntiFormants numberOfDeltaFormants
-
-  for .itime to 20
-    call setget_1 "pitch" 130
-    call setget_1 "voicing amplitude" 90
-    call setget_1 "flutter" 0.5
-    call setget_1 "power1" 2
-    call setget_1 "power2" 3
-    call setget_1 "open phase" 0.5
-    call setget_1 "collision phase" 0.035
-    call setget_1 "double pulsing" 0.4
-    call setget_1 "spectral tilt" 20
-    call setget_1 "aspiration amplitude" 90
-    call setget_1 "breathiness amplitude" 90
-
-    call setget_1 "frication bypass" 20
-    call setget_1 "frication amplitude" 50
-  
-    call setget_formants_old Normal
-    call setget_formants_old Nasal
-    call setget_formants_old Tracheal
-    call setget_formants_old Frication
-    call setget_antiformants_old Nasal
-    call setget_antiformants_old Tracheal
-
-    call setget_deltaformants
-
-  endfor
-
-  call test_deltaFormants_old
-
-  select kg
-  Remove
-  printline ===========    KlattGrid test_get_add_remove_extract_replace_old_interface succesful
+	numberOfOralFormants = 6
+	numberOfNormalFormants = numberOfOralFormants
+	numberOfNasalFormants = 1
+	numberOfTrachealFormants = 1
+	numberOfFricationFormants = 5
+	numberOfNasalAntiFormants = 1
+	numberOfTrachealAntiFormants = 1
+	numberOfDeltaFormants = 1
+	tmin = 0
+	tmax = 1
+
+	printline ===========		KlattGrid test_get_add_remove_extract_replace_old_interface start
+
+	kg = Create KlattGrid... kg tmin tmax numberOfOralFormants numberOfNasalFormants numberOfNasalAntiFormants 
+		... numberOfFricationFormants numberOfTrachealFormants numberOfTrachealAntiFormants numberOfDeltaFormants
+
+	for .itime to 20
+		call setget_1 "pitch" 130
+		call setget_1 "voicing amplitude" 90
+		call setget_1 "flutter" 0.5
+		call setget_1 "power1" 2
+		call setget_1 "power2" 3
+		call setget_1 "open phase" 0.5
+		call setget_1 "collision phase" 0.035
+		call setget_1 "double pulsing" 0.4
+		call setget_1 "spectral tilt" 20
+		call setget_1 "aspiration amplitude" 90
+		call setget_1 "breathiness amplitude" 90
+
+		call setget_1 "frication bypass" 20
+		call setget_1 "frication amplitude" 50
+	
+		call setget_formants_old Normal
+		call setget_formants_old Nasal
+		call setget_formants_old Tracheal
+		call setget_formants_old Frication
+		call setget_antiformants_old Nasal
+		call setget_antiformants_old Tracheal
+
+		call setget_deltaformants
+
+	endfor
+
+	call test_deltaFormants_old
+
+	removeObject (kg)
+	printline ===========		KlattGrid test_get_add_remove_extract_replace_old_interface succesful
 endproc
 
 procedure setget_1 .var$ .value
-  select kg
-  .time = randomUniform (tmin,tmax)
-  Add '.var$' point... .time .value
-  .val = Get '.var$' at time... .time
-  .vt = Extract '.var$' tier
-  select kg
-  Remove '.var$' points between... tmin tmax
-  .val1 = Get '.var$' at time... .time
-  assert .val1 = undefined; Add '.var$' at time... '.time' '.value'
-  
-  plus .vt
-  Replace '.var$' tier
-  select kg
-  .val1 = Get '.var$' at time... .time
-  assert .val1 =.val; Add '.var$' at time... '.time' '.value'
-  select .vt
-  Remove
+	selectObject (kg)
+	.time = randomUniform (tmin,tmax)
+	Add '.var$' point... .time .value
+	.val = Get '.var$' at time... .time
+	.vt = Extract '.var$' tier
+	selectObject (kg)
+	Remove '.var$' points between... tmin tmax
+	.val1 = Get '.var$' at time... .time
+	assert .val1 = undefined; Add '.var$' at time... '.time' '.value'
+	
+	plusObject (.vt)
+	Replace '.var$' tier
+	selectObject (kg)
+	.val1 = Get '.var$' at time... .time
+	assert .val1 =.val; Add '.var$' at time... '.time' '.value'
+	removeObject (.vt)
 endproc
 
 procedure setget_2p .var$ .ifor .time .value
-  select kg
-  Add delta '.var$' point... .ifor .time .value
-  .val = Get delta '.var$' at time... .ifor .time
-  assert .val = .value; Add delta '.var$' point... .ifor .time .value
-  .fg = Extract delta formant grid
-  select kg
-  Remove delta '.var$' points between... .ifor tmin tmax
-  .val1 =  Get delta '.var$' at time... .ifor .time
-  assert .val1 = undefined; Get delta '.var$' at time... .ifor .time
-  plus .fg
-  Replace delta formant grid
-  select kg
-  .val1 =  Get delta '.var$' at time... .ifor .time
-  assert .val1 =.val; Get delta '.var$' at time... .ifor .time
-  select .fg
-  Remove
+	selectObject (kg)
+	Add delta '.var$' point... .ifor .time .value
+	.val = Get delta '.var$' at time... .ifor .time
+	assert .val = .value; Add delta '.var$' point... .ifor .time .value
+	.fg = Extract delta formant grid
+	selectObject (kg)
+	Remove delta '.var$' points between... .ifor tmin tmax
+	.val1 = Get delta '.var$' at time... .ifor .time
+	assert .val1 = undefined; Get delta '.var$' at time... .ifor .time
+	plusObject (.fg)
+	Replace delta formant grid
+	selectObject (kg)
+	.val1 = Get delta '.var$' at time... .ifor .time
+	assert .val1 =.val; Get delta '.var$' at time... .ifor .time
+	removeObject (.fg)
 endproc
 
 procedure setget_3_old .var$ .choice$ .ifor .time .value
-  select kg
-  Add '.var$' point... "'.choice$'" .ifor .time .value
-  .val = Get '.var$' at time... "'.choice$'" .ifor .time
-  assert .val = .value; Add '.var$' point... "'.choice$'" .ifor .time .value
-  .fg = Extract formant grid... '.choice$'
-  select kg
-  Remove '.var$' points between... "'.choice$'" .ifor tmin tmax
-  .val1 =  Get '.var$' at time... "'.choice$'" .ifor .time
-  assert .val1 = undefined; Get '.var$' at time... "'.choice$'" .ifor .time
-  plus .fg
-  Replace formant grid... '.choice$'
-  select kg
-  .val1 =  Get '.var$' at time... "'.choice$'" .ifor .time
-  assert .val1 =.val; Get '.var$' at time... "'.choice$'" .ifor .time
-  select .fg
-  Remove
+	selectObject (kg)
+	Add '.var$' point... "'.choice$'" .ifor .time .value
+	.val = Get '.var$' at time... "'.choice$'" .ifor .time
+	assert .val = .value; Add '.var$' point... "'.choice$'" .ifor .time .value
+	.fg = Extract formant grid... '.choice$'
+	selectObject (kg)
+	Remove '.var$' points between... "'.choice$'" .ifor tmin tmax
+	.val1 = Get '.var$' at time... "'.choice$'" .ifor .time
+	assert .val1 = undefined; Get '.var$' at time... "'.choice$'" .ifor .time
+	plusObject (.fg)
+	Replace formant grid... '.choice$'
+	selectObject (kg)
+	.val1 = Get '.var$' at time... "'.choice$'" .ifor .time
+	assert .val1 =.val; Get '.var$' at time... "'.choice$'" .ifor .time
+	removeObject (.fg)
+
 endproc
 
 procedure setget_3a_old .var$ .choice$ .ifor .time .value
-  select kg
-  Add '.var$' point... "'.choice$'" .ifor .time .value
-  .val = Get '.var$' at time... "'.choice$'" .ifor .time
-  assert .val = .value; Add '.var$' point... "'.choice$'" .ifor .time .value
-  .tier = Extract amplitude tier... "'.choice$'" .ifor
-  select kg
-  Remove '.var$' points between... "'.choice$'" .ifor tmin tmax
-  .val1 =  Get '.var$' at time... "'.choice$'" .ifor .time
-  assert .val1 = undefined; Get '.var$' at time... "'.choice$'" .ifor .time
-  plus .tier
-  Replace amplitude tier... "'.choice$'" .ifor
-  select kg
-  .val1 =  Get '.var$' at time... "'.choice$'" .ifor .time
-  assert .val1 =.val; Get '.var$' at time... "'.choice$'" .ifor .time
-  select .tier
-  Remove
+	selectObject (kg)
+	Add '.var$' point... "'.choice$'" .ifor .time .value
+	.val = Get '.var$' at time... "'.choice$'" .ifor .time
+	assert .val = .value; Add '.var$' point... "'.choice$'" .ifor .time .value
+	.tier = Extract amplitude tier... "'.choice$'" .ifor
+	selectObject (kg)
+	Remove '.var$' points between... "'.choice$'" .ifor tmin tmax
+	.val1 = Get '.var$' at time... "'.choice$'" .ifor .time
+	assert .val1 = undefined; Get '.var$' at time... "'.choice$'" .ifor .time
+	plusObject (.tier)
+	Replace amplitude tier... "'.choice$'" .ifor
+	selectObject (kg)
+	.val1 = Get '.var$' at time... "'.choice$'" .ifor .time
+	assert .val1 =.val; Get '.var$' at time... "'.choice$'" .ifor .time
+	removeObject (.tier)
 endproc
 
 procedure setget_formants_old .type$
-  for .i to numberOf'.type$'Formants
-    .time = randomUniform (tmin, tmax)
-    .f = (2*.i-1) * randomUniform (450, 550)
-    .b = .f /10
-    .a = randomUniform (70, 90)
-    call setget_3_old "formant" "'.type$' formant" .i .time .f
-    call setget_3_old "bandwidth" "'.type$' formant" .i .time .b
-    call setget_3a_old "amplitude" "'.type$' formant" .i .time .a
-  endfor
-  select kg
-  Formula (frequencies)... "'.type$' formant" self + 100
-  Formula (bandwidths)... "'.type$' formant" self * 2
+	for .i to numberOf'.type$'Formants
+		.time = randomUniform (tmin, tmax)
+		.f = (2*.i-1) * randomUniform (450, 550)
+		.b = .f /10
+		.a = randomUniform (70, 90)
+		call setget_3_old "formant" "'.type$' formant" .i .time .f
+		call setget_3_old "bandwidth" "'.type$' formant" .i .time .b
+		call setget_3a_old "amplitude" "'.type$' formant" .i .time .a
+	endfor
+	selectObject (kg)
+	Formula (frequencies)... "'.type$' formant" self + 100
+	Formula (bandwidths)... "'.type$' formant" self * 2
 endproc
 
 
 procedure setget_3 .type$ .formant$ .var$ .ifor .time .value
-  select kg
-  what$ = "'.type$' '.formant$' '.var$'"
-  Remove 'what$' points... .ifor tmin tmax
-  Add 'what$' point... .ifor .time .value
-  .val = Get 'what$' at time... .ifor .time
-  assert .val = .value;  '.val' '.value' (Get 'what$' at time... '.ifor' '.time')
-  .fg = Extract '.type$' '.formant$' grid
-  select kg
-  Remove 'what$' points... .ifor tmin tmax
-  .val1 =  Get 'what$' at time... .ifor .time
-  assert .val1 = undefined;  '.val1' (Get 'what$' at time... '.ifor' '.time')
-  plus .fg
-  Replace '.type$' '.formant$' grid
-  select kg
-  .val1 =  Get 'what$' at time... .ifor .time
-  if .var$ = "amplitude"
-    .val = undefined
-  endif
-  assert .val1 =.val; '.val1' '.val' (Get 'what$' at time... '.ifor' '.time')
-  select .fg
-  Remove
+	selectObject (kg)
+	what$ = "'.type$' '.formant$' '.var$'"
+	Remove 'what$' points... .ifor tmin tmax
+	Add 'what$' point... .ifor .time .value
+	.val = Get 'what$' at time... .ifor .time
+	assert .val = .value;	'.val' '.value' (Get 'what$' at time... '.ifor' '.time')
+	.fg = Extract '.type$' '.formant$' grid
+	selectObject (kg)
+	Remove 'what$' points... .ifor tmin tmax
+	.val1 = Get 'what$' at time... .ifor .time
+	assert .val1 = undefined;	'.val1' (Get 'what$' at time... '.ifor' '.time')
+	plusObject (.fg)
+	Replace '.type$' '.formant$' grid
+	selectObject (kg)
+	.val1 = Get 'what$' at time... .ifor .time
+	if .var$ = "amplitude"
+		.val = undefined
+	endif
+	assert .val1 =.val; '.val1' '.val' (Get 'what$' at time... '.ifor' '.time')
+	removeObject (.fg)
 endproc
 
 procedure setget_formants .tYPE$
-  .type$ = replace_regex$ (.tYPE$, "(^.)","\L\1", 1)
-  for .i to numberOf'.tYPE$'Formants
-    .time = randomUniform (tmin, tmax)
-    .f = (2*.i-1) * randomUniform (450, 550)
-    .b = .f /10
-    .a = randomUniform (70, 90)
-    call setget_3 '.type$' "formant" "frequency" .i .time .f
-    call setget_3 '.type$' "formant" "bandwidth" .i .time .b
-    if .tYPE$ <> "Delta"
-      call setget_3 '.type$' "formant" "amplitude" .i .time .a
-    endif
-  endfor
-  select kg
-  Formula ('.type$' formant frequencies)... self + 100
-  Formula ('.type$' formant bandwidths)...  self * 2
+	.type$ = replace_regex$ (.tYPE$, "(^.)","\L\1", 1)
+	for .i to numberOf'.tYPE$'Formants
+		.time = randomUniform (tmin, tmax)
+		.f = (2*.i-1) * randomUniform (450, 550)
+		.b = .f /10
+		.a = randomUniform (70, 90)
+		call setget_3 '.type$' "formant" "frequency" .i .time .f
+		call setget_3 '.type$' "formant" "bandwidth" .i .time .b
+		if .tYPE$ <> "Delta"
+			call setget_3 '.type$' "formant" "amplitude" .i .time .a
+		endif
+	endfor
+	selectObject (kg)
+	Formula ('.type$' formant frequencies)... self + 100
+	Formula ('.type$' formant bandwidths)... self * 2
 endproc
 
 procedure setget_antiformants .tYPE$
-  .type$ = replace_regex$ (.tYPE$, "(^.)","\L\1", 1)
-  for .i to numberOf'.tYPE$'AntiFormants
-    .time = randomUniform (tmin, tmax)
-    .f = (2*.i-1) * randomUniform (450, 550)
-    .b = .f /10
-    call setget_3 '.type$' "antiformant" "frequency" .i .time .f
-    call setget_3 '.type$' "antiformant" "bandwidth" .i .time .b
-  endfor
-  select kg
-  Formula ('.type$' antiformant frequencies)... self + 100
-  Formula ('.type$' antiformant bandwidths)...  self * 2
+	.type$ = replace_regex$ (.tYPE$, "(^.)","\L\1", 1)
+	for .i to numberOf'.tYPE$'AntiFormants
+		.time = randomUniform (tmin, tmax)
+		.f = (2*.i-1) * randomUniform (450, 550)
+		.b = .f /10
+		call setget_3 '.type$' "antiformant" "frequency" .i .time .f
+		call setget_3 '.type$' "antiformant" "bandwidth" .i .time .b
+	endfor
+	selectObject (kg)
+	Formula ('.type$' antiformant frequencies)... self + 100
+	Formula ('.type$' antiformant bandwidths)... self * 2
 endproc
 
 
 procedure setget_deltaformants
-  for .i to numberOfDeltaFormants
-    .time = randomUniform (tmin, tmax)
-    .f = randomUniform (50, 60)
-    .b = .f /10
-    call setget_2p "formant" .i .time .f
-    call setget_2p "bandwidth" .i .time .b
-  endfor
+	for .i to numberOfDeltaFormants
+		.time = randomUniform (tmin, tmax)
+		.f = randomUniform (50, 60)
+		.b = .f /10
+		call setget_2p "formant" .i .time .f
+		call setget_2p "bandwidth" .i .time .b
+	endfor
 endproc
 
 procedure setget_antiformants_old .type$
-  for .i to numberOf'.type$'AntiFormants
-    .time = randomUniform (tmin, tmax)
-    .f = (2*.i-1) * randomUniform (450, 550)
-    .b = .f /10
-    call setget_3_old "formant" "'.type$' antiformant" .i .time .f
-    call setget_3_old "bandwidth" "'.type$' antiformant" .i .time .b
-  endfor
+	for .i to numberOf'.type$'AntiFormants
+		.time = randomUniform (tmin, tmax)
+		.f = (2*.i-1) * randomUniform (450, 550)
+		.b = .f /10
+		call setget_3_old "formant" "'.type$' antiformant" .i .time .f
+		call setget_3_old "bandwidth" "'.type$' antiformant" .i .time .b
+	endfor
 endproc
 
 procedure test_deltaFormants_old
-  .kg = Create KlattGrid... kg 0 1 6 1 1 6 1 1 1
-  Add pitch point... 0.5 100
-  Add voicing amplitude point... 0.5 90
-  Add bandwidth point... "Normal formant" 1 0.5 50
-  Add delta formant point... 1 0.5 500
-  Remove formant points between... "Normal formant" 1 0 2
-  Add formant point... "Normal formant" 1 0 400
-  Add formant point... "Normal formant" 1 1 600
-  Add formant point... "Normal formant" 1 0.003553 400
-  Add formant point... "Normal formant" 1 0.002 300
-  Add formant point... "Normal formant" 1 0.0112 430
-  .sound = To Sound
-  Remove
-  select .kg
-  Extract formant grid (open phases)... 0.1
+	.kg = Create KlattGrid... kg 0 1 6 1 1 6 1 1 1
+	Add pitch point... 0.5 100
+	Add voicing amplitude point... 0.5 90
+	Add bandwidth point... "Normal formant" 1 0.5 50
+	Add delta formant point... 1 0.5 500
+	Remove formant points between... "Normal formant" 1 0 2
+	Add formant point... "Normal formant" 1 0 400
+	Add formant point... "Normal formant" 1 1 600
+	Add formant point... "Normal formant" 1 0.003553 400
+	Add formant point... "Normal formant" 1 0.002 300
+	Add formant point... "Normal formant" 1 0.0112 430
+	.sound = To Sound
+	selectObject (.kg)
+	.fg = Extract formant grid (open phases)... 0.1
+	removeObject (.sound, .kg, .fg)
 endproc
 
 procedure test_deltaFormants
-  .kg = Create KlattGrid... kg 0 1 6 1 1 6 1 1 1
-  Add pitch point... 0.5 100
-  Add voicing amplitude point... 0.5 90
-  Add oral formant bandwidth point... 1 0.5 50
-  Add delta formant frequency point... 1 0.5 500
-  Remove oral formant frequency points... 1 0 2
-  Add oral formant frequency point... 1 0 400
-  Add oral formant frequency point... 1 1 600
-  Add oral formant frequency point... 1 0.003553 400
-  Add oral formant frequency point... 1 0.002 300
-  Add oral formant frequency point... 1 0.0112 430
-  .sound = To Sound
-  Remove
-  select .kg
-  Extract oral formant grid (open phases)... 0.1
+	.kg = Create KlattGrid... kg 0 1 6 1 1 6 1 1 1
+	Add pitch point... 0.5 100
+	Add voicing amplitude point... 0.5 90
+	Add oral formant bandwidth point... 1 0.5 50
+	Add delta formant frequency point... 1 0.5 500
+	Remove oral formant frequency points... 1 0 2
+	Add oral formant frequency point... 1 0 400
+	Add oral formant frequency point... 1 1 600
+	Add oral formant frequency point... 1 0.003553 400
+	Add oral formant frequency point... 1 0.002 300
+	Add oral formant frequency point... 1 0.0112 430
+	.sound = To Sound
+	selectObject (.kg)
+	.fg = Extract oral formant grid (open phases)... 0.1
+	removeObject (.sound, .fg, .kg)
 endproc
 
 
diff --git a/dwtest/test_Table_extractMahalanobisWhere.praat b/dwtest/test_Table_extractMahalanobisWhere.praat
new file mode 100644
index 0000000..8ff92ea
--- /dev/null
+++ b/dwtest/test_Table_extractMahalanobisWhere.praat
@@ -0,0 +1,31 @@
+# test_Table_extractMahalanobisWhere.praat
+# djmw 20140509
+
+appendInfoLine: "Table_extractMahalanobisWhere test"
+
+t = Create Table with column names: "t", 1000, "f x y"
+Formula (column range): "f", "f", "randomInteger (1,1)"
+Formula (column range): "x", "y", "randomGauss (self[1],0.1)"
+tm = Extract rows where (mahalanobis): "x y", "greater than", 3, "f", "1"
+tm1 = Extract rows where: "self$[""f""]=""1"""
+nm1 = Get number of rows
+selectObject: t
+t1 = Extract rows where: "self$[""f""]=""1"""
+t1m1 = Extract rows where (mahalanobis): "x y", "greater than", 3, "", "1"
+n1m1 = Get number of rows
+assert nm1 = n1m1
+# only for interactive use
+; @draw
+procedure draw
+	Erase all
+	selectObject: t
+	Select outer viewport: 0, 6, 0, 6
+	Scatter plot: "x", 0, 4, "y", 0, 4, "f", 10, "yes"
+	Colour: "Red"
+	Draw ellipses: "x", 0, 4, "y", 0, 4, "f", 3, 12, "no"
+	Colour: "Black"
+endproc
+
+removeObject: t, tm, tm1, t1, t1m1
+
+appendInfoLine: "Table_extractMahalanobisWhere test OK"
diff --git a/dwtools/DTW.cpp b/dwtools/DTW.cpp
index c92e709..4252c6f 100644
--- a/dwtools/DTW.cpp
+++ b/dwtools/DTW.cpp
@@ -1500,7 +1500,7 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, Matri
             } else if (psi[iy][ix] == DTW_START) {
                 break;
             }
-            if (pathIndex < 1 || iy < 1) break;
+            if (pathIndex < 2 || iy < 1) break;
             //Melder_assert (pathIndex > 1 && iy > 0);
             my path[--pathIndex].x = ix;
             my path[pathIndex].y = iy;
diff --git a/dwtools/DataModeler.cpp b/dwtools/DataModeler.cpp
new file mode 100644
index 0000000..11f65fc
--- /dev/null
+++ b/dwtools/DataModeler.cpp
@@ -0,0 +1,1884 @@
+/* DataModeler.cpp
+ *
+ * Copyright (C) 2014 David Weenink
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ djmw 20140217
+*/
+
+#include "DataModeler.h"
+#include "NUM2.h"
+#include "NUMmachar.h"
+#include "SVD.h"
+#include "Strings_extensions.h"
+#include "Sound_and_LPC_robust.h"
+#include "Table_extensions.h"
+
+#include "oo_DESTROY.h"
+#include "DataModeler_def.h"
+#include "oo_COPY.h"
+#include "DataModeler_def.h"
+#include "oo_EQUAL.h"
+#include "DataModeler_def.h"
+#include "oo_CAN_WRITE_AS_ENCODING.h"
+#include "DataModeler_def.h"
+#include "oo_WRITE_TEXT.h"
+#include "DataModeler_def.h"
+#include "oo_WRITE_BINARY.h"
+#include "DataModeler_def.h"
+#include "oo_READ_TEXT.h"
+#include "DataModeler_def.h"
+#include "oo_READ_BINARY.h"
+#include "DataModeler_def.h"
+#include "oo_DESCRIPTION.h"
+#include "DataModeler_def.h"
+
+extern machar_Table NUMfpp;
+
+Thing_implement (DataModeler, Function, 0);
+
+void structDataModeler :: v_info () {
+	MelderInfo_writeLine (L"   Time domain:");
+	MelderInfo_writeLine (L"      Start time: ", Melder_double (xmin), L" seconds");
+	MelderInfo_writeLine (L"      End time: ", Melder_double (xmax), L" seconds");
+	MelderInfo_writeLine (L"      Total duration: ", Melder_double (xmax - xmin), L" seconds");
+	double ndf, rSquared = DataModeler_getCoefficientOfDetermination (this, NULL, NULL);
+	double probability, chisq = DataModeler_getChiSquaredQ (this, useSigmaY, &probability, &ndf);
+	MelderInfo_writeLine (L"   Fit:");
+	MelderInfo_writeLine (L"      Number of data points: ", Melder_integer (numberOfDataPoints));
+	MelderInfo_writeLine (L"      Number of parameters: ", Melder_integer (numberOfParameters));
+	MelderInfo_writeLine (L"      Each data point has ", useSigmaY == DataModeler_DATA_WEIGH_EQUAL ? L" the same weight (estimated)." :
+		useSigmaY == DataModeler_DATA_WEIGH_SIGMA ? L"a different weight (sigmaY)." : 
+		useSigmaY == DataModeler_DATA_WEIGH_RELATIVE ? L"a different relative weight (Y_value/sigmaY)." :
+		L"a different weight (SQRT(sigmaY)).");
+	MelderInfo_writeLine (L"      Chi squared: ", Melder_double (chisq));
+	MelderInfo_writeLine (L"      Number of degrees of freedom: ", Melder_double (ndf));
+	MelderInfo_writeLine (L"      Probability: ", Melder_double (probability));
+	MelderInfo_writeLine (L"      R-squared: ", Melder_double (rSquared));
+	for (long ipar = 1; ipar <= numberOfParameters; ipar++) {
+		double sigma = parameterStatus[ipar] == DataModeler_PARAMETER_FIXED ? 0 : sqrt (parameterCovariances -> data[ipar][ipar]);
+		MelderInfo_writeLine (L"      p[", Melder_integer (ipar), L"] = ", Melder_double (parameter[ipar]), L"; sigma = ", Melder_double (sigma));
+	}
+}
+
+static double polynome_evaluate (DataModeler me, double x, double p[])
+{
+	double xpi = 1, result = p[1];
+	for (long i = 2; i <= my numberOfParameters; i++) {
+		xpi *= x;
+		result += p[i] * xpi;
+	}
+	return result;
+}
+
+static void polynome_evaluateBasisFunctions (DataModeler me, double xin, double term[])
+{
+	term[1] = 1;
+	// From domain [xmin, xmax] to domain [-1, 1]
+	double x = (2 * xin - my xmin - my xmax) / (my xmax - my xmin);
+	for (long i = 2; i <= my numberOfParameters; i++) {
+		term[i] = term[i-1] * x;
+	}
+}
+
+static double legendre_evaluate (DataModeler me, double xin, double p[])
+{
+	// From domain [xmin, xmax] to domain [-1, 1]
+	double x = (2 * xin - my xmin - my xmax) / (my xmax - my xmin);
+	double pti, ptim1, ptim2 = 1, result = p[1];
+	if (my numberOfParameters > 1) {
+		double twox = 2 * x, f2 = x, d = 1.0;
+		result += p[2] * (ptim1 = x);
+		for (long i = 3; i <= my numberOfParameters; i++) {
+			double f1 = d++;
+			f2 += twox;
+			result += p[i] * (pti = (f2 * ptim1 - f1 * ptim2) / d);
+			ptim2 = ptim1; ptim1 = pti;
+		}
+	}
+	return result;
+}
+
+static void legendre_evaluateBasisFunctions (DataModeler me, double xin, double term[])
+{
+	term[1] = 1;
+	/* transform x from domain [xmin, xmax] to domain [-1, 1] */
+	double x = ( 2 * xin - my xmin - my xmax) / (my xmax - my xmin);
+	if (my numberOfParameters > 1) {
+		double twox = 2 * x, f2 = term[2] = x, d = 1.0;
+		for (long i = 3; i <= my numberOfParameters; i++) {
+			double f1 = d++;
+			f2 += twox;
+			term[i] = (f2 * term[i-1] - f1 * term[i-2]) / d;
+		}
+	}
+}
+
+double DataModeler_getDataPointWeight (DataModeler me, long iPoint, int useSigmaY ) {
+	double weight = 1;
+	if (iPoint > 0 && iPoint <= my numberOfDataPoints && my dataPointStatus[iPoint] != DataModeler_DATA_INVALID) {
+		if (useSigmaY == DataModeler_DATA_WEIGH_SIGMA) {
+			weight = my sigmaY[iPoint];
+		} else if (useSigmaY == DataModeler_DATA_WEIGH_RELATIVE) {
+			double q = my y[iPoint] / my sigmaY[iPoint];
+			weight = 500 / q; //
+		} else if (useSigmaY == DataModeler_DATA_WEIGH_SQRT) {
+			weight = 7.071 * sqrt (my sigmaY[iPoint]); // .bw = 50 gives 50
+		}
+	}
+	return weight;
+}
+
+double DataModeler_getModelValueAtX (DataModeler me, double x) {
+	double f = NUMundefined;
+	if (x >= my xmin && x <= my xmax) {
+		f = my f_evaluate (me, x, my parameter);
+	}
+	return f;
+}
+
+void DataModeler_getExtremaY (DataModeler me, double *ymin, double *ymax) {
+	double min = 1e38, max = -min;
+	for (long i = 1; i <= my numberOfDataPoints; i++) {
+		if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+			if (my y[i] < min) {
+				min = my y[i];
+			}
+			if (my y[i] > max) {
+				max = my y[i];
+			}
+		}
+	}
+	if (ymin != NULL) {
+		*ymin = min;
+	}
+	if (ymax != NULL) {
+		*ymax = max;
+	}
+}
+
+double DataModeler_getDataPointValue (DataModeler me, long index) {
+	double value = NUMundefined;
+	if (index > 0 && index <= my numberOfDataPoints && my dataPointStatus[index] != DataModeler_DATA_INVALID) {
+		value = my y[index];
+	}
+	return value;
+}
+
+void DataModeler_setDataPointValue (DataModeler me, long index, double value) {
+	if (index > 0 && index <= my numberOfDataPoints) {
+		my y[index] = value;
+	}
+}
+
+void DataModeler_setDataPointSigma (DataModeler me, long index, double sigma) {
+	if (index > 0 && index <= my numberOfDataPoints) {
+		my sigmaY[index] = sigma;
+	}
+}
+
+double DataModeler_getDataPointSigma (DataModeler me, long index) {
+	double sigma = NUMundefined;
+	if (index > 0 && index <= my numberOfDataPoints) {
+		sigma = my sigmaY[index];
+	}
+	return sigma;
+}
+
+int DataModeler_getDataPointStatus (DataModeler me, long index) {
+	int value = DataModeler_DATA_INVALID;
+	if (index > 0 && index <= my numberOfDataPoints) {
+		value = my dataPointStatus[index];
+	}
+	return value;
+}
+
+void DataModeler_setDataPointStatus (DataModeler me, long index, int status) {
+	if (index > 0 && index <= my numberOfDataPoints) {
+		if (status == DataModeler_DATA_VALID && ! NUMdefined (my y[index])) {
+			Melder_throw ("Your data value is undefined. First set the value and then its status.");
+		}
+		my dataPointStatus[index] = status;
+	}
+}
+
+void DataModeler_setDataPointValueAndStatus (DataModeler me, long index, double value, int dataStatus) {
+	if (index > 0 && index <= my numberOfDataPoints) {
+		my y[index] = value;
+		my dataPointStatus[index] = dataStatus;
+	}
+}
+
+void DataModeler_setParameterValueFixed (DataModeler me, long index, double value) {
+	if (index > 0 && index <= my numberOfParameters) {
+		my parameter[index] = value;
+		my parameterStatus[index] = DataModeler_PARAMETER_FIXED;
+	}
+}
+
+double DataModeler_getParameterValue (DataModeler me, long index) {
+	double value = NUMundefined;
+	if (index > 0 && index <= my numberOfParameters) {
+		value = my parameter[index];
+	}
+	return value;
+}
+
+int DataModeler_getParameterStatus (DataModeler me, long index) {
+	int status = DataModeler_PARAMETER_UNDEFINED;
+	if (index > 0 && index <= my numberOfParameters) {
+		status = my parameterStatus[index];
+	}
+	return status;
+}
+
+double DataModeler_getParameterStandardDeviation (DataModeler me, long index) {
+	double stdev = NUMundefined;
+	if (index > 0 && index <= my numberOfParameters) {
+		stdev = sqrt (my parameterCovariances -> data[index][index]);
+	}
+	return stdev;
+}
+
+double DataModeler_getVarianceOfParameters (DataModeler me, long fromIndex, long toIndex, long *numberOfFreeParameters) {
+	double variance = NUMundefined;
+	if (toIndex < fromIndex || (toIndex == 0 && fromIndex == 0)) {
+		fromIndex = 1; toIndex = my numberOfParameters;
+	}
+	long numberOfParameters = 0;
+	if (fromIndex <= toIndex && fromIndex > 0 && toIndex <= my numberOfParameters) {
+		variance = 0;
+		for (long index = fromIndex; index <= toIndex; index++) {
+			if (my parameterStatus[index] != DataModeler_PARAMETER_FIXED) {
+				variance += my parameterCovariances -> data[index][index];
+				numberOfParameters++;
+			}
+		}
+	}
+	if (numberOfFreeParameters != NULL) {
+		*numberOfFreeParameters = numberOfParameters;
+	}
+	return variance;
+}
+
+void DataModeler_setParametersFree (DataModeler me, long fromIndex, long toIndex) {
+	if (toIndex < fromIndex || (toIndex == 0 && fromIndex == 0)) {
+		fromIndex = 1; toIndex = my numberOfParameters;
+	}
+	if (fromIndex <= toIndex && fromIndex > 0 && toIndex <= my numberOfParameters) {
+		for (long index = fromIndex; index <= toIndex; index++) {
+			my parameterStatus[index] = DataModeler_PARAMETER_FREE;
+		}
+	}
+}
+
+void DataModeler_setParameterValuesToZero (DataModeler me, double numberOfSigmas) {
+	long numberOfChangedParameters = 0;
+	for (long i = my numberOfParameters; i > 0; i--) {
+		if (my parameterStatus[i] != DataModeler_PARAMETER_FIXED) {
+			double value = my parameter[i];
+			double sigmas = numberOfSigmas * DataModeler_getParameterStandardDeviation (me, i);
+			if ((value - sigmas) * (value + sigmas) < 0) {
+				DataModeler_setParameterValueFixed (me, i, 0);
+				numberOfChangedParameters++;
+			}
+		}
+	}
+}
+
+static long DataModeler_getNumberOfFreeParameters (DataModeler me) {
+	long numberOfFreeParameters = 0;
+	for (long i = 1; i <= my numberOfParameters; i++) {
+		if (my parameterStatus[i] == DataModeler_PARAMETER_FREE) numberOfFreeParameters++;
+	}
+	return numberOfFreeParameters;
+}
+
+long DataModeler_getNumberOfFixedParameters (DataModeler me) {
+	return my numberOfParameters - DataModeler_getNumberOfFreeParameters (me);
+}
+
+static long DataModeler_getNumberOfValidDataPoints (DataModeler me) {
+	long numberOfValidDataPoints = 0;
+	for (long i = 1; i <= my numberOfDataPoints; i++) {
+		if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+			numberOfValidDataPoints++;
+		}
+	}
+	return numberOfValidDataPoints;
+}
+
+long DataModeler_getNumberOfInvalidDataPoints (DataModeler me) {
+	return my numberOfDataPoints - DataModeler_getNumberOfValidDataPoints  (me);
+}
+
+void DataModeler_setTolerance (DataModeler me, double tolerance) {
+	my tolerance = tolerance > 0 ? tolerance : my numberOfDataPoints * NUMfpp -> eps;
+}
+
+double DataModeler_getDegreesOfFreedom (DataModeler me) {
+	long numberOfDataPoints = 0;
+	for (long i = 1; i <= my numberOfDataPoints; i++) {
+		if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+			numberOfDataPoints++;
+		}
+	}
+	double ndf = numberOfDataPoints - DataModeler_getNumberOfFreeParameters (me);
+	return ndf;
+}
+
+double *DataModeler_getZScores (DataModeler me, int useSigmaY) {
+	try {
+		double estimatedSigmaY;
+		if (useSigmaY == DataModeler_DATA_WEIGH_EQUAL) {
+			estimatedSigmaY = DataModeler_estimateSigmaY (me);
+			if (! NUMdefined (estimatedSigmaY)) {
+				Melder_throw ("Not enough data points to calculate sigma.");
+			}
+		}
+		autoNUMvector<double> zscores (1, my numberOfDataPoints);
+		for (long i = 1; i <= my numberOfDataPoints; i++) {
+			double value = NUMundefined;
+			if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+				double estimate = my f_evaluate (me, my x[i], my parameter);
+				double sigma = useSigmaY == DataModeler_DATA_WEIGH_EQUAL ? estimatedSigmaY : DataModeler_getDataPointWeight (me, i, useSigmaY);
+				value = (my y[i] - estimate) / sigma;
+			}
+			zscores[i] = value;
+		}
+		return zscores.transfer();
+	} catch (MelderError) {
+		Melder_throw ("No z-scores calculated.");
+	}
+}
+
+double DataModeler_getChiSquaredQ (DataModeler me, int useSigmaY, double *probability, double *ndf)
+{
+	double chisq = 0;
+	autoNUMvector<double> zscores (DataModeler_getZScores (me, useSigmaY), 1);
+	for (long idata = 1; idata <= my numberOfDataPoints; idata++)
+	{
+		if (NUMdefined (zscores[idata])) {
+			chisq += zscores[idata] * zscores[idata];
+		}
+	}
+	double dof =  DataModeler_getDegreesOfFreedom (me);
+	dof = useSigmaY == DataModeler_DATA_WEIGH_EQUAL ? dof - 1 : dof; // we loose one dof if sigma is estimated from the data
+	if (probability != NULL) {
+		*probability = NUMchiSquareQ (chisq, dof);
+	}
+	if (ndf != NULL) {
+		*ndf = dof;
+	}
+	return chisq;
+}
+
+double DataModeler_getWeightedMean (DataModeler me) {
+	double ysum = 0, wsum = 0;
+	for (long i = 1; i <= my numberOfDataPoints; i++) {
+		if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+			double s = DataModeler_getDataPointWeight (me, i, my useSigmaY);
+			double weight =  1 / (s * s);
+			ysum += my y[i] * weight;
+			wsum += weight;
+		}
+	}
+	return ysum / wsum;
+}
+
+double DataModeler_getCoefficientOfDetermination (DataModeler me, double *ssreg, double *sstot) {
+
+	/* We cannot use the standard expressions for ss_tot, and ss_reg because our data are weighted by 1 / sigma[i].
+	 * We need the weighted mean and we need to weigh all sums-of-squares accordingly;
+	 * if all sigma[i] terms are equal, the formulas reduce to the standard ones.
+	 * Ref: A. Buse (1973): Goodness of Fit in Generalized Least Squares Estimatio, The American Statician, vol 27, 106-108
+	 */
+
+	double ymean = DataModeler_getWeightedMean (me);
+	double ss_tot = 0, ss_reg = 0;
+	for (long i = 1; i <= my numberOfDataPoints; i++) {
+		if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+			double s = DataModeler_getDataPointWeight (me, i, my useSigmaY);
+			double diff = (my y[i] - ymean) / s;
+			ss_tot += diff * diff; // total sum of squares
+			double estimate = my f_evaluate (me, my x[i], my parameter);
+			diff = (estimate - my y[i]) / s;
+			ss_reg += diff * diff; // regression sum of squares
+		}
+	}
+	double rSquared = ss_tot > 0 ? 1 - ss_reg / ss_tot : 1;
+	if (ssreg != NULL) {
+		*ssreg = ss_tot - ss_reg;
+	}
+	if (sstot != NULL) {
+		*sstot = ss_tot;
+	}
+	return rSquared;
+}
+
+void DataModeler_drawBasisFunction_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax,
+ 	long iterm, bool scale, long numberOfPoints) {
+	if (xmax <= xmin) {
+		xmin = my xmin; xmax = my xmax;
+	}
+	autoNUMvector<double> x (1, numberOfPoints);
+	autoNUMvector<double> y (1, numberOfPoints);
+	autoNUMvector<double> term (1, my numberOfParameters);
+	for (long i = 1; i <= numberOfPoints; i++) {
+		x[i] = xmin + (i - 0.5) * (xmax - xmin) / numberOfPoints;
+		my f_evaluateBasisFunctions (me, x[i], term.peek());
+		y[i] = term[iterm];
+		y[i] = scale ? y[i] * my parameter[iterm] : y[i];
+	}
+	if (ymax <= ymin) {
+		ymin = 1e38;ymax = -ymin;
+		for (long i = 1; i <= numberOfPoints; i++) {
+			ymax = y[i] > ymax ? y[i] : ymax;
+			ymin = y[i] < ymin ? y[i] : ymin;
+		}
+	}
+	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
+	for (long i = 2; i <= numberOfPoints; i++) {
+		Graphics_line (g, x[i-1], y[i-1], x[i], y[i]);
+	}
+}
+
+void DataModeler_drawOutliersMarked_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, double numberOfSigmas, int useSigmaY, wchar_t *mark, int marksFontSize, double horizontalOffset_mm) {
+	if (xmax <= xmin) { 
+		xmin = my xmin; xmax = my xmax;
+	}
+	long ixmin = 2;
+	while (my x[ixmin] < xmin && ixmin < my numberOfDataPoints) { 
+		ixmin++;
+	}
+	
+	ixmin--;
+	long ixmax = my numberOfDataPoints - 1;
+	while (my x[ixmax] > xmax && ixmax > 1) {
+		ixmax--;
+	}
+	ixmax++;
+	if (ixmin >= ixmax) {
+		return; // nothing to draw
+	}
+	
+	autoNUMvector<double> zscores (DataModeler_getZScores (me, useSigmaY), 1);
+	double horizontalOffset_wc = Graphics_dxMMtoWC (g, horizontalOffset_mm);
+	
+	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
+	Graphics_setFontSize (g, marksFontSize);
+	Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
+	int currentFontSize = Graphics_inqFontSize (g);
+	for (long idata = 1; idata <= my numberOfDataPoints; idata++) {
+		if (my dataPointStatus[idata] != DataModeler_DATA_INVALID) {
+			double x = my x[idata], y = my y[idata];
+			if (x >= xmin && x <= xmax && y >= ymin && y <= ymax) {
+				if (fabs (zscores[idata]) > numberOfSigmas) {
+					Graphics_text (g, x + horizontalOffset_wc, y, mark);
+				}
+			}
+		}
+	}
+	Graphics_setFontSize (g, currentFontSize);
+}
+
+void DataModeler_draw_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, int errorbars, int connectPoints, double barWidth_mm, double horizontalOffset_mm, int drawDots)
+{
+	if (xmax <= xmin) { 
+		xmin = my xmin; xmax = my xmax; 
+	}
+	
+	long ixmin = 2;
+	while (my x[ixmin] < xmin && ixmin < my numberOfDataPoints) { 
+		ixmin++;
+	}
+	ixmin--;
+	
+	long ixmax = my numberOfDataPoints - 1;
+	while (my x[ixmax] > xmax && ixmax > 1) {
+		ixmax--;
+	}
+	ixmax++;
+	
+	if (ixmin >= ixmax) {
+		return; // nothing to draw
+	}
+	numberOfParameters = numberOfParameters > my numberOfParameters ? my numberOfParameters : numberOfParameters;
+	autoNUMvector<double> parameter (1, my numberOfParameters);
+	NUMvector_copyElements (my parameter, parameter.peek(), 1, numberOfParameters);
+	
+	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
+	double horizontalOffset_wc = Graphics_dxMMtoWC (g, horizontalOffset_mm);
+	double barWidth_wc = barWidth_mm <= 0 ? 0 : Graphics_dxMMtoWC (g, barWidth_mm);
+	bool x1defined = false, x2defined = false;
+	for (long idata = ixmin; idata <= ixmax; idata++) {
+		if (my dataPointStatus[idata] != DataModeler_DATA_INVALID) {
+			double x1, y1, x2, y2, x = my x[idata], y = my y[idata];
+			if (! x1defined) {
+				x1 = x;
+				y1 = estimated ? my f_evaluate (me, x, parameter.peek()) : y;
+				y1 = y1 < ymin ? ymin : y1 > ymax ? ymax : y1;
+				x1defined = true;
+			} else {
+				x2 = x;
+				y2 = estimated ? my f_evaluate (me, x, parameter.peek()) : y;
+				y2 = y2 < ymin ? ymin : y2 > ymax ? ymax : y2;
+				x2defined = true;
+			}
+			if (x1defined && drawDots) {
+				Graphics_speckle (g, x + horizontalOffset_wc, y);
+			}
+			if (x1defined && x2defined) {
+				if (connectPoints) {
+					Graphics_line (g, x1 + horizontalOffset_wc, y1, x2 + horizontalOffset_wc, y2);
+				}
+				x1 = x;
+				y1 = y2;
+			}
+			if (x1defined && errorbars != 0) {
+				double sigma = my sigmaY[idata]; // DataModeler_getDataPointWeight ?
+				double ym = y1;
+				double yt = ym + 0.5 * sigma, yb = ym - 0.5 * sigma;
+				if (estimated) {
+					yt = (y - y1) > 0 ? y : y1;
+					yb = (y - y1) > 0 ? y1 : y;
+				}
+				int topOutside = yt > ymax, bottomOutside = yb < ymin;
+				yt = topOutside ? ymax : yt;
+				yb = bottomOutside ? ymin : yb;
+				Graphics_line (g, x1 + horizontalOffset_wc, yb, x1 + horizontalOffset_wc, yt);
+				if (barWidth_wc > 0 && ! estimated) {
+					double xl = x1 - 0.5 * barWidth_wc + horizontalOffset_wc;
+					double xr = xl + barWidth_wc;
+					if (! topOutside) {
+						Graphics_line (g, xl, yt, xr, yt);
+					}
+					if (! bottomOutside) {
+						Graphics_line (g, xl, yb, xr, yb);
+					}
+				}
+			}
+		}
+	}
+}
+
+void DataModeler_drawTrack_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, double horizontalOffset_mm)
+{
+	int errorbars = 0, connectPoints = 1; double barWidth_mm = 0;
+	DataModeler_draw_inside (me, g, xmin, xmax, ymin, ymax, estimated, numberOfParameters, errorbars, connectPoints, barWidth_mm, horizontalOffset_mm, 0);
+}
+
+void DataModeler_drawTrack (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, double horizontalOffset_mm, int garnish) {
+	if (ymax <= ymin) {
+		DataModeler_getExtremaY (me, &ymin, &ymax);
+	}
+	Graphics_setInner (g);
+	DataModeler_drawTrack_inside (me, g, xmin, xmax, ymin, ymax, estimated, numberOfParameters, horizontalOffset_mm);
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_drawInnerBox (g);
+		Graphics_marksBottom (g, 2, 1, 1, 0);
+		Graphics_marksLeft (g, 2, 1, 1, 0);
+	}
+}
+
+void DataModeler_speckle_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, int errorbars, double barWidth_mm, double horizontalOffset_mm)
+{
+	int connectPoints = 0;
+	DataModeler_draw_inside (me, g, xmin, xmax, ymin, ymax, estimated, numberOfParameters, errorbars, connectPoints, barWidth_mm, horizontalOffset_mm, 1);
+}
+
+void DataModeler_speckle (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, int errorbars, double barWidth_mm, double horizontalOffset_mm, int garnish) {
+	if (ymax <= ymin) {
+		DataModeler_getExtremaY (me, &ymin, &ymax);
+	}
+	Graphics_setInner (g);
+	DataModeler_speckle_inside (me, g, xmin, xmax, ymin, ymax, estimated, numberOfParameters, errorbars, barWidth_mm, horizontalOffset_mm);
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_drawInnerBox (g);
+		Graphics_marksBottom (g, 2, 1, 1, 0);
+		Graphics_marksLeft (g, 2, 1, 1, 0);
+	}
+}
+
+Table DataModeler_to_Table_zscores (DataModeler me, int useSigmaY) {
+	try {
+		autoTable ztable = Table_createWithColumnNames (my numberOfDataPoints, L"x z");
+		autoNUMvector<double> zscores (DataModeler_getZScores (me, useSigmaY), 1);
+		for (long i = 1; i <= my numberOfDataPoints; i++) {
+			Table_setNumericValue (ztable.peek(), i, 1, my x[i]);
+			Table_setNumericValue (ztable.peek(), i, 2, zscores[i]);
+		}
+		return ztable.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Table not created.");
+	}	
+}
+
+void DataModeler_normalProbabilityPlot (DataModeler me, Graphics g, int useSigmaY, long numberOfQuantiles, double numberOfSigmas, int labelSize, const wchar_t *label, int garnish) {
+	try {
+		autoTable thee = DataModeler_to_Table_zscores (me, useSigmaY);
+		Table_normalProbabilityPlot (thee.peek(), g, 2, numberOfQuantiles, numberOfSigmas, labelSize, label, garnish);
+	} catch (MelderError) {
+		// ignore
+	}
+}
+
+void DataModeler_setBasisFunctions (DataModeler me, int type) {
+	if (type == DataModeler_TYPE_LEGENDRE) {
+		my f_evaluate = legendre_evaluate;
+		my f_evaluateBasisFunctions = legendre_evaluateBasisFunctions;
+	} else {
+		my f_evaluate = polynome_evaluate;
+		my f_evaluateBasisFunctions = polynome_evaluateBasisFunctions;
+	}
+	my type = type;
+}
+
+void  DataModeler_init (DataModeler me, double xmin, double xmax, long numberOfDataPoints, long numberOfParameters, int type) {
+	my xmin = xmin; my xmax = xmax;
+	DataModeler_setBasisFunctions (me, type);
+	my numberOfDataPoints = numberOfDataPoints;
+	my x = NUMvector<double> (1, numberOfDataPoints);
+	my y = NUMvector<double> (1, numberOfDataPoints);
+	my sigmaY = NUMvector<double> (1, numberOfDataPoints);
+	my dataPointStatus = NUMvector<int> (1, numberOfDataPoints);
+	my numberOfParameters = numberOfParameters;
+	if (numberOfParameters <= 0) {
+		Melder_throw ("The number of parameters must be greater than zero.");
+	}
+	if (numberOfParameters > numberOfDataPoints) {
+		Melder_throw ("The number of parameters must be smaller than the number of data points");
+	}
+	my parameter = NUMvector<double> (1, numberOfParameters);
+	my parameterStatus = NUMvector<int> (1, numberOfParameters);
+	my parameterNames = Strings_createFixedLength (numberOfParameters);
+	my parameterCovariances = Covariance_create (numberOfParameters);
+}
+
+DataModeler DataModeler_create (double xmin, double xmax, long numberOfDataPoints, long numberOfParameters, int type) {
+	try {
+		autoDataModeler me = Thing_new (DataModeler);
+		DataModeler_init (me.peek(), xmin, xmax, numberOfDataPoints, numberOfParameters, type);
+		my xmin = xmin; my xmax = xmax;
+		return me.transfer();
+	} catch (MelderError) {
+		Melder_throw ("DataModeler not created.");
+	}
+}
+
+void DataModeler_fit (DataModeler me)
+{
+	try {
+		// Count the number of parameters to be fitted
+
+		long numberOfParameters = DataModeler_getNumberOfFreeParameters (me);
+		if (numberOfParameters == 0) return;
+		long numberOfDataPoints = DataModeler_getNumberOfValidDataPoints (me);
+		autoNUMvector<double> b (1, numberOfDataPoints);
+		autoNUMvector<double> term (1, my numberOfParameters);
+		autoNUMvector<double> parameter (1, my numberOfParameters);
+		autoNUMmatrix<double> design (1, numberOfDataPoints, 1, numberOfParameters);
+
+		// For function evaluation with only the FIXED parameters
+
+		for (long ipar = 1; ipar <= my numberOfParameters; ipar++) {
+			parameter[ipar] = my parameterStatus[ipar] == DataModeler_PARAMETER_FIXED ? my parameter[ipar] : 0;
+		}
+
+		// estimate sigma if we weigh all datapoint equally. 
+		// This is necessary to get the parameter covariances right
+		double sigmaY = my useSigmaY == DataModeler_DATA_WEIGH_EQUAL ? DataModeler_estimateSigmaY (me) : NUMundefined;
+		long idata = 0;
+		// Accumulate coefficients of the design matrix
+		for (long i = 1; i <= my numberOfDataPoints; i++) {
+			if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+				// function evaluation with only the FIXED parameters
+				double xi = my x[i], yi = my y[i];
+				double yFixed = my f_evaluate (me, xi, parameter.peek());
+				double si = my useSigmaY != DataModeler_DATA_WEIGH_EQUAL ? DataModeler_getDataPointWeight (me, i, my useSigmaY) : sigmaY;
+
+				// individual terms of the function
+
+				my f_evaluateBasisFunctions (me, xi, term.peek());
+				long ipar = 0; ++idata;
+				for (long j = 1; j <= my numberOfParameters; j++) {
+					if (my parameterStatus[j] != DataModeler_PARAMETER_FIXED) {
+						design[idata][++ipar] = term[j] / si;
+					}
+				}
+
+				// only 'residual variance' must be explained by the model
+
+				b[idata] = (yi - yFixed) / si;
+			}
+		}
+		
+		// Singular value decomposition and evaluation of the singular values
+
+		autoSVD thee = SVD_create_d (design.peek(), numberOfDataPoints, numberOfParameters);
+		if (! NUMfpp) {
+			NUMmachar ();
+		}
+		SVD_zeroSmallSingularValues (thee.peek(), my tolerance > 0 ? my tolerance : numberOfDataPoints * NUMfpp -> eps);
+		SVD_solve (thee.peek(), b.peek(), parameter.peek()); // re-use parameter
+
+		// Put the calculated parameters at the correct position in 'my p'
+		Covariance cov = my parameterCovariances;
+		long ipar = 0;
+		for (long j = 1; j <= my numberOfParameters; j++) {
+			if (my parameterStatus[j] != DataModeler_PARAMETER_FIXED) {
+				my parameter[j] = parameter[++ipar];
+			}
+			cov -> centroid[j] = my parameter[j];
+		}
+		cov -> numberOfObservations = numberOfDataPoints;
+		// estimate covariances between parameters
+		if (numberOfParameters < my numberOfParameters) {
+			autoNUMmatrix<double> covtmp (1, numberOfParameters, 1, numberOfParameters);
+			SVD_getSquared (thee.peek(), covtmp.peek(), true);
+			// Set fixed parameters variances and covariances to zero.
+			for (long i = 1; i <= my numberOfParameters; i++) {
+				for (long j = i; j <= my numberOfParameters; j++) {
+					cov -> data[i][j] = cov -> data[j][i] = 0;
+				}
+			}
+			long ipar = 0, jpar;
+			for (long i = 1; i <= my numberOfParameters; i++) {
+				if (my parameterStatus[i] != DataModeler_PARAMETER_FIXED) {
+					jpar = 0; ipar++;
+					for (long j = 1; j <= my numberOfParameters; j++) {
+						if (my parameterStatus[j] != DataModeler_PARAMETER_FIXED) {
+							jpar++;
+							cov -> data[i][j] = covtmp[ipar][jpar];
+						}
+					}
+				}
+			}
+		} else {
+			SVD_getSquared (thee.peek(), cov -> data, true);
+		}
+	} catch (MelderError) {
+		Melder_throw ("DataModeler no fit.");
+	}
+}
+
+void DataModeler_setDataWeighing (DataModeler me, int useSigmaY) {
+	if (my useSigmaY != useSigmaY) {
+		my useSigmaY = useSigmaY;
+		DataModeler_fit (me); // because sigma has changed!
+	}
+}
+
+Covariance DataModeler_to_Covariance_parameters (DataModeler me) {
+	try {
+		autoCovariance cov = (Covariance) Data_copy (my parameterCovariances);
+		return cov.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Covariance not created.");
+	}
+}
+
+DataModeler Table_to_DataModeler (Table me, double xmin, double xmax, long xcolumn, long ycolumn, long scolumn, long numberOfParameters, int type) {
+	try {
+		Table_checkSpecifiedColumnNumberWithinRange (me, xcolumn);
+		Table_checkSpecifiedColumnNumberWithinRange (me, ycolumn);
+		int useSigmaY = scolumn > 0;
+		if (useSigmaY) {
+			Table_checkSpecifiedColumnNumberWithinRange (me, scolumn);
+		}
+		long numberOfRows = my rows -> size, numberOfData = 0;
+		autoNUMvector<double> x (1, numberOfRows), y (1, numberOfRows), sy (1, numberOfRows);
+		for (long i = 1; i <= numberOfRows; i++) {
+			double val = Table_getNumericValue_Assert (me, i, xcolumn);
+			if (NUMdefined (val)) {
+				numberOfData++; x[numberOfData] = val;
+				if (numberOfData > 1) {
+					if (val < x[numberOfData - 1]) {
+						Melder_throw ("Data with x-values must be sorted.");
+					} else if (val == x[numberOfData - 1]) {
+						Melder_throw ("All x-values must be different.");
+					}
+				}
+				y[numberOfData] = Table_getNumericValue_Assert (me, i, ycolumn);
+				sy[numberOfData] = useSigmaY ? Table_getNumericValue_Assert (me, i, scolumn) : 1;
+			}
+		}
+		if (xmax <= xmin) {
+			NUMvector_extrema<double> (x.peek(), 1, numberOfData, &xmin, &xmax);
+		}
+		if (xmin >= xmax) {
+			Melder_throw ("Range of x-values too small.");
+		}
+		long numberOfDataPoints = 0, validData = 0;
+		for (long i = 1; i <= numberOfData; i++) {
+			if (x[i] >= xmin && x[i] <= xmax) {
+				numberOfDataPoints++;
+			}
+		}
+		autoDataModeler thee = DataModeler_create (xmin, xmax, numberOfDataPoints, numberOfParameters, type);
+		numberOfDataPoints = 0;
+		for (long i = 1; i <= numberOfData; i++) {
+			if (x[i] >= xmin && x[i] <= xmax) {
+				thy x[++numberOfDataPoints] = x[i];
+				thy dataPointStatus[numberOfDataPoints] = DataModeler_DATA_INVALID;
+				if (NUMdefined (y[i])) {
+					thy y[numberOfDataPoints] = y[i];
+					thy sigmaY[numberOfDataPoints] = sy[i];
+					thy dataPointStatus[numberOfDataPoints] = DataModeler_DATA_VALID;
+					validData++;
+				}
+			}
+		}
+		thy useSigmaY = useSigmaY;
+		thy numberOfDataPoints = numberOfDataPoints;
+		thy tolerance = 1e-5;
+		if (validData < numberOfParameters) {
+			Melder_throw ("The number of parameters must not exceed the number of data points.");
+		}
+		DataModeler_setDataWeighing (thee.peek(), DataModeler_DATA_WEIGH_SIGMA);
+		DataModeler_fit (thee.peek());
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Datamodeler not created from Table.");
+	}
+}
+
+Thing_implement (FormantModeler, Function, 0);
+
+void structFormantModeler :: v_info () {
+
+	MelderInfo_writeLine (L"Time domain:");
+	MelderInfo_writeLine (L"   Start time: ", Melder_double (xmin), L" seconds");
+	MelderInfo_writeLine (L"   End time: ", Melder_double (xmax), L" seconds");
+	MelderInfo_writeLine (L"   Total duration: ", Melder_double (xmax - xmin), L" seconds");
+	for (long iformant = 1; iformant <= datamodelers -> size; iformant++) {
+		DataModeler ffi = (DataModeler) datamodelers -> item[iformant];
+		MelderInfo_writeLine (L"Formant ", Melder_integer (iformant));
+		ffi -> v_info();
+	}
+}
+
+double DataModeler_getResidualSumOfSquares (DataModeler me, long *numberOfDataPoints) {
+	long n = 0;
+	double rss = 0;
+	for (long i = 1; i <= my numberOfDataPoints; i++) {
+		if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+				++n;
+				double dif = my y[i] - my f_evaluate (me, my x[i], my parameter);
+				rss += dif * dif;
+		}
+	}
+	if (numberOfDataPoints) {
+		*numberOfDataPoints = n;
+	}
+	return rss;
+}
+
+double DataModeler_estimateSigmaY (DataModeler me) {
+	try {
+		long numberOfDataPoints = 0;
+		autoNUMvector<double> y (1, my numberOfDataPoints);
+		for (long i = 1; i <= my numberOfDataPoints; i++) {
+			if (my dataPointStatus[i] != DataModeler_DATA_INVALID) {
+				y[++numberOfDataPoints] = my y[i];
+			}
+		}
+		double variance;
+		NUMvector_avevar (y.peek(), numberOfDataPoints, NULL, &variance);
+		double sigma = NUMdefined (variance) ? sqrt (variance / (numberOfDataPoints - 1)) : NUMundefined;
+		return sigma;
+	} catch (MelderError) {
+		Melder_throw ("Cannot estimate sigma.");
+	}
+}
+
+double FormantModeler_getStandardDeviation (FormantModeler me, long iformant) {
+	double sigma = NUMundefined;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		sigma = DataModeler_estimateSigmaY (ff);
+	}
+	return sigma;
+}
+
+double FormantModeler_getDataPointValue (FormantModeler me, long iformant, long index) {
+	double value = NUMundefined;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		value = DataModeler_getDataPointValue (ff, index);
+	}
+	return value;
+}
+
+void FormantModeler_setDataPointValue (FormantModeler me, long iformant, long index, double value) {
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+ 		DataModeler_setDataPointValue (ff, index, value);
+	}
+}
+
+double FormantModeler_getDataPointSigma (FormantModeler me, long iformant, long index) {
+	double sigma = NUMundefined;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		sigma = DataModeler_getDataPointSigma (ff, index);
+	}
+	return sigma;
+}
+
+void FormantModeler_setDataPointSigma (FormantModeler me, long iformant, long index, double sigma) {
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+ 		DataModeler_setDataPointSigma (ff, index, sigma);
+	}
+}
+
+int FormantModeler_getDataPointStatus (FormantModeler me, long iformant, long index) {
+	int value = DataModeler_DATA_INVALID;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		value = DataModeler_getDataPointStatus (ff, index);
+	}
+	return value;
+}
+
+void FormantModeler_setDataPointStatus (FormantModeler me, long iformant, long index, int status)
+{
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		DataModeler_setDataPointStatus (ff, index, status);
+	}
+}
+
+void FormantModeler_setDataPointValueAndStatus (FormantModeler me, long iformant, long index, double value, int dataStatus)
+{
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		DataModeler_setDataPointValueAndStatus (ff, index, value, dataStatus);
+	}
+}
+
+void FormantModeler_setParameterValueFixed (FormantModeler me, long iformant, long index, double value) {
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+		DataModeler_setParameterValueFixed (ffi, index, value);
+	}
+}
+
+void FormantModeler_setParametersFree (FormantModeler me, long fromFormant, long toFormant, long fromIndex, long toIndex) {
+	long numberOfFormants = my datamodelers -> size;
+	if (toFormant < fromFormant || (fromFormant == toFormant && fromFormant == 0)) {
+		fromFormant = 1; toFormant= numberOfFormants;
+	}
+	if (! (toFormant >= 1 && toFormant <= numberOfFormants && fromFormant >= 1 && fromFormant <= numberOfFormants &&
+		fromFormant <= toFormant)) {
+		Melder_throw ("Formant number(s) must be in the interval [1, ", Melder_integer (numberOfFormants), "].");
+	}
+	for (long iformant = fromFormant; iformant <= toFormant; iformant++) {
+		DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+		DataModeler_setParametersFree (ffi, fromIndex, toIndex);
+	}
+}
+
+void FormantModeler_setDataWeighing (FormantModeler me, long fromFormant, long toFormant, int useSigmaY) {
+	long numberOfFormants = my datamodelers -> size;
+	if (toFormant < fromFormant || (fromFormant == toFormant && fromFormant == 0)) {
+		fromFormant = 1; toFormant= numberOfFormants;
+	}
+	if (! (toFormant >= 1 && toFormant <= numberOfFormants && fromFormant >= 1 && fromFormant <= numberOfFormants &&
+		fromFormant <= toFormant)) {
+		Melder_throw ("Formant number(s) must be in the interval [1, ", Melder_integer (numberOfFormants), "].");
+	}
+	for (long iformant = fromFormant; iformant <= toFormant; iformant++) {
+		DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+		DataModeler_setDataWeighing (ffi, useSigmaY);
+	}
+}
+
+void FormantModeler_fit (FormantModeler me) {
+	for (long iformant = 1; iformant <= my datamodelers -> size; iformant++) {
+		DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+		DataModeler_fit (ffi);
+	}
+}
+
+void FormantModeler_drawBasisFunction (FormantModeler me, Graphics g, double tmin, double tmax, double fmin, double fmax,
+ 	long iformant, long iterm, bool scaled, long numberOfPoints, int garnish) {
+	if (tmax <= tmin) {
+		tmin = my xmin; tmax = my xmax; 
+	}
+	if (iformant < 1 || iformant > my datamodelers -> size) {
+		return;
+	}
+	Graphics_setInner (g);
+	DataModeler ffi =  (DataModeler) my datamodelers -> item[iformant];
+	DataModeler_drawBasisFunction_inside (ffi, g, tmin, tmax, fmin, fmax, iterm, scaled, numberOfPoints);
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_inqWindow (g, &tmin, &tmax, &fmin, &fmax);
+		Graphics_drawInnerBox (g);
+		Graphics_textBottom (g, 1, L"Time (s)");
+		Graphics_textLeft (g, 1, (scaled ? L"Frequency (Hz)" : L"Amplitude"));
+		Graphics_marksBottom (g, 2, 1, 1, 0);
+		Graphics_markLeft (g, fmin, 1, 1, 0, L"");
+		Graphics_markLeft (g, fmax, 1, 1, 0, L"");
+	}
+}
+
+void FormantModeler_drawOutliersMarked (FormantModeler me, Graphics g, double tmin, double tmax, double fmax, long fromTrack, long toTrack, double numberOfSigmas, int useSigmaY, wchar_t *mark, int marksFontSize, double horizontalOffset_mm, int garnish) {
+	if (tmax <= tmin) { 
+		tmin = my xmin; tmax = my xmax; 
+	}
+	long maxTrack = my datamodelers -> size;
+	if (toTrack == 0 && fromTrack == 0) {
+		fromTrack = 1; toTrack = maxTrack;
+	}
+	if (fromTrack > maxTrack) return;
+	if (toTrack > maxTrack) {
+		toTrack = maxTrack;
+	}
+	Graphics_setInner (g);
+	int currectFontSize = Graphics_inqFontSize (g);
+	for (long iformant = fromTrack; iformant <= toTrack; iformant++) {
+		DataModeler ffi =  (DataModeler) my datamodelers -> item[iformant];
+		double xOffset_mm = (iformant % 2 == 1) ? horizontalOffset_mm : -horizontalOffset_mm;
+		DataModeler_drawOutliersMarked_inside (ffi, g, tmin, tmax, 0, fmax, numberOfSigmas, useSigmaY, mark, marksFontSize, xOffset_mm);
+	}
+	Graphics_setFontSize (g, currectFontSize);
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_drawInnerBox (g);
+		Graphics_textBottom (g, 1, L"Time (s)");
+		Graphics_textLeft (g, 1, L"Formant frequency (Hz)");
+		Graphics_marksBottom (g, 2, 1, 1, 0);
+		Graphics_marksLeftEvery (g, 1.0, 1000.0, 1, 1, 1);
+	}
+}
+
+void FormantModeler_normalProbabilityPlot (FormantModeler me, Graphics g, long iformant, int useSigmaY, long numberOfQuantiles, double numberOfSigmas, int labelSize, const wchar_t *label, int garnish) {
+	if (iformant > 0 || iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers ->item[iformant];
+		DataModeler_normalProbabilityPlot (ff, g, useSigmaY, numberOfQuantiles, numberOfSigmas, labelSize, label, garnish);
+	}
+}
+
+void FormantModeler_drawTracks_inside (FormantModeler me, Graphics g, double xmin, double xmax, double fmax,
+	long fromTrack, long toTrack, int estimated, long numberOfParameters, double horizontalOffset_mm) {
+	for (long iformant = fromTrack; iformant <= toTrack; iformant++) {
+		DataModeler ffi =  (DataModeler) my datamodelers -> item[iformant];
+		double xOffset_mm = (iformant % 2 == 1) ? horizontalOffset_mm : -horizontalOffset_mm;
+		DataModeler_drawTrack_inside (ffi, g, xmin, xmax, 0, fmax, estimated, numberOfParameters, xOffset_mm);
+	}
+}
+
+void FormantModeler_drawTracks (FormantModeler me, Graphics g, double tmin, double tmax, double fmax,
+	long fromTrack, long toTrack, int estimated, long numberOfParameters, double horizontalOffset_mm, int garnish) {
+	if (tmax <= tmin) { 
+		tmin = my xmin; tmax = my xmax; 
+	}
+	long maxTrack = my datamodelers -> size;
+	if (toTrack == 0 && fromTrack == 0) {
+		fromTrack = 1; toTrack = maxTrack;
+	}
+	if (fromTrack > maxTrack) return;
+	if (toTrack > maxTrack) {
+		toTrack = maxTrack;
+	}
+	Graphics_setInner (g);
+	FormantModeler_drawTracks_inside (me, g, tmin, tmax, fmax, fromTrack, toTrack, estimated, numberOfParameters, horizontalOffset_mm);
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_drawInnerBox (g);
+		Graphics_textBottom (g, 1, L"Time (s)");
+		Graphics_textLeft (g, 1, L"Formant frequency (Hz)");
+		Graphics_marksBottom (g, 2, 1, 1, 0);
+		Graphics_marksLeftEvery (g, 1.0, 1000.0, 1, 1, 1);
+	}
+}
+
+void FormantModeler_speckle_inside (FormantModeler me, Graphics g, double xmin, double xmax, double fmax,
+	long fromTrack, long toTrack, int estimated, long numberOfParameters, int errorBars, double barWidth_mm, double horizontalOffset_mm) {
+	for (long iformant = fromTrack; iformant <= toTrack; iformant++) {
+		DataModeler ffi =  (DataModeler) my datamodelers -> item[iformant];
+		double xOffset_mm = (iformant % 2 == 1) ? horizontalOffset_mm : -horizontalOffset_mm;
+		DataModeler_speckle_inside (ffi, g, xmin, xmax, 0, fmax, estimated, numberOfParameters, errorBars, barWidth_mm, xOffset_mm);
+	}
+}
+
+void FormantModeler_speckle (FormantModeler me, Graphics g, double tmin, double tmax, double fmax,
+	long fromTrack, long toTrack, int estimated, long numberOfParameters, int errorBars, double barWidth_mm, double horizontalOffset_mm, int garnish) {
+	if (tmax <= tmin) { 
+		tmin = my xmin; tmax = my xmax; 
+	}
+	long maxTrack = my datamodelers -> size;
+	if (toTrack == 0 && fromTrack == 0) {
+		fromTrack = 1; toTrack = maxTrack;
+	}
+	if (fromTrack > maxTrack) return;
+	if (toTrack > maxTrack) {
+		toTrack = maxTrack;
+	}
+	Graphics_setInner (g);
+	FormantModeler_speckle_inside (me, g, tmin, tmax, fmax, fromTrack, toTrack, estimated, numberOfParameters,  errorBars, barWidth_mm, horizontalOffset_mm);
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_drawInnerBox (g);
+		Graphics_textBottom (g, 1, L"Time (s)");
+		Graphics_textLeft (g, 1, L"Formant frequency (Hz)");
+		Graphics_marksBottom (g, 2, 1, 1, 0);
+		Graphics_marksLeftEvery (g, 1.0, 1000.0, 1, 1, 1);
+	}
+}
+
+FormantModeler FormantModeler_create (double tmin, double tmax, long numberOfFormants, long numberOfDataPoints, long numberOfParameters) {
+	try {
+		autoFormantModeler me = Thing_new (FormantModeler);
+		my xmin = tmin; my xmax = tmax;
+		my datamodelers = Ordered_create ();
+		for (long itrack = 1; itrack <= numberOfFormants; itrack++) {
+			autoDataModeler ff = DataModeler_create (tmin, tmax, numberOfDataPoints, numberOfParameters,  DataModeler_TYPE_LEGENDRE);
+			Collection_addItem (my datamodelers, ff.transfer());
+		}
+		return me.transfer();
+	} catch (MelderError) {
+		Melder_throw ("FormantModeler not created.");
+	}
+}
+
+double FormantModeler_getModelValueAtTime (FormantModeler me, long iformant, double time) {
+	double f = NUMundefined;
+	if (iformant >= 1 && iformant <= my datamodelers -> size) {
+		DataModeler thee =  (DataModeler) my datamodelers -> item[iformant];
+		f =  DataModeler_getModelValueAtX (thee, time);
+	}
+	return f;
+}
+
+double FormantModeler_getWeightedMean (FormantModeler me, long iformant) {
+	double f = NUMundefined;
+	if (iformant >= 1 && iformant <= my datamodelers -> size) {
+		DataModeler thee =  (DataModeler) my datamodelers -> item[iformant];
+		f =  DataModeler_getWeightedMean (thee);
+	}
+	return f;
+	
+}
+
+long FormantModeler_getMaximumNumberOfParameters (FormantModeler me) {
+	long maxnum = 1;
+	for (long i = 1; i <= my datamodelers -> size; i++) {
+		DataModeler ffi = (DataModeler) my datamodelers -> item[i];
+		if (ffi -> numberOfParameters > maxnum) {
+			maxnum = ffi -> numberOfParameters;
+		}
+	}
+	return maxnum;
+}
+
+long FormantModeler_getNumberOfTracks (FormantModeler me) {
+	return my datamodelers -> size;
+}
+
+long FormantModeler_getNumberOfParameters (FormantModeler me, long iformant) {
+	long numberOfParameters = 0;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		numberOfParameters = ff -> numberOfParameters;
+	}
+	return numberOfParameters;
+}
+
+long FormantModeler_getNumberOfFixedParameters (FormantModeler me, long iformant) {
+	long numberOfParameters = 0;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		numberOfParameters = ff -> numberOfParameters;
+		numberOfParameters -= DataModeler_getNumberOfFreeParameters (ff);
+	}
+	return numberOfParameters;
+}
+
+long FormantModeler_getNumberOfDataPoints (FormantModeler me, long iformant) {
+	long numberOfDataPoints = 0;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		numberOfDataPoints = ff -> numberOfDataPoints;
+	}
+	return numberOfDataPoints;
+}
+
+
+long FormantModeler_getNumberOfInvalidDataPoints (FormantModeler me, long iformant) {
+	long numberOfInvalidDataPoints = 0;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		numberOfInvalidDataPoints = DataModeler_getNumberOfInvalidDataPoints (ff);
+	}
+	return numberOfInvalidDataPoints;
+}
+
+double FormantModeler_getParameterValue (FormantModeler me, long iformant, long iparameter) {
+	double value = NUMundefined;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		value = DataModeler_getParameterValue (ff, iparameter);
+	}
+	return value;
+}
+
+int FormantModeler_getParameterStatus (FormantModeler me, long iformant, long index) {
+	int status = DataModeler_PARAMETER_UNDEFINED;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		status = DataModeler_getParameterStatus (ff, index);
+	}
+	return status;
+}
+
+double FormantModeler_getParameterStandardDeviation ( FormantModeler me, long iformant, long index) {
+	double stdev = NUMundefined;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		stdev = DataModeler_getParameterStandardDeviation (ff, index);
+	}
+	return stdev;
+}
+
+double FormantModeler_getDegreesOfFreedom (FormantModeler me, long iformant) {
+	double dof = 0;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		dof = DataModeler_getDegreesOfFreedom (ff);
+	}
+	return dof;
+}
+
+double FormantModeler_getVarianceOfParameters (FormantModeler me, long fromFormant, long toFormant, long fromIndex, long toIndex, long *numberOfFreeParameters) {
+	double variance = NUMundefined;
+	long numberOfFormants = my datamodelers -> size, numberOfParameters = 0, nofp;
+	if (toFormant < fromFormant || (toFormant == 0 && fromFormant == 0)) {
+		fromFormant = 1; toFormant = numberOfFormants;
+	}
+	if (fromFormant <= toFormant && fromFormant > 0 && toFormant <= numberOfFormants) {
+		variance = 0;
+		for (long iformant = fromFormant; iformant <= toFormant; iformant++) {
+			DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+			variance += DataModeler_getVarianceOfParameters (ff, fromIndex, toIndex, &nofp);
+			numberOfParameters += nofp;
+		}
+	}
+	if (numberOfFreeParameters != NULL) {
+		*numberOfFreeParameters = numberOfParameters;
+	}
+	return variance;
+}
+
+long FormantModeler_getNumberOfDataPoints (FormantModeler me) {
+	DataModeler thee = (DataModeler) my datamodelers -> item[1];
+	return thy numberOfDataPoints;
+}
+
+Table FormantModeler_to_Table_zscores (FormantModeler me, int useSigmaY) {
+	try {
+		long icolt = 1, numberOfFormants = my datamodelers -> size;
+		long numberOfDataPoints = FormantModeler_getNumberOfDataPoints (me);
+		autoMelderString columnLabel;
+		autoTable ztable = Table_createWithoutColumnNames (numberOfDataPoints, numberOfFormants + 1);
+		Table_setColumnLabel (ztable.peek(), icolt, L"time");
+		for (long iformant = 1; iformant <= numberOfFormants; iformant++) {
+			long icolz = iformant + 1;
+			MelderString_append (&columnLabel, L"z", Melder_integer(iformant));
+			Table_setColumnLabel (ztable.peek(), icolz, columnLabel.string);
+			MelderString_empty (&columnLabel);
+			DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+			if (iformant == 1) {
+				for (long i = 1; i <= numberOfDataPoints; i++) { // only once all tracks have same x-values
+				Table_setNumericValue (ztable.peek(), i, icolt, ffi -> x[i]);
+				}
+			}
+			autoNUMvector<double> zscores (DataModeler_getZScores (ffi, useSigmaY), 1);
+			for (long i = 1; i <= numberOfDataPoints; i++) {
+				Table_setNumericValue (ztable.peek(), i, icolz, zscores[i]);
+			}
+		}
+		return ztable.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Table not created.");
+	}	
+}
+
+DataModeler FormantModeler_extractDataModeler (FormantModeler me, long iformant) {
+	try {
+		if (! (iformant > 0 && iformant <= my datamodelers -> size)) {
+			Melder_throw ("");
+		}
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		autoDataModeler thee = (DataModeler) Data_copy (ff);
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("DataModeler not created.");
+	}	
+}
+
+Covariance FormantModeler_to_Covariance_parameters (FormantModeler me, long iformant) {
+	try {
+		if (iformant < 1 || iformant > my datamodelers -> size) {
+			Melder_throw (L"The formant should be greater than zero and smaller than or equal to ", 
+				  Melder_integer (my datamodelers -> size));
+		}
+		DataModeler thee = (DataModeler) my datamodelers -> item[iformant];
+		autoCovariance cov = (Covariance) Data_copy (thy parameterCovariances);
+		return cov.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Covariance not created.");
+	}
+	
+}
+
+void FormantModeler_setTolerance (FormantModeler me, double tolerance) {
+	for (long iformant = 1; iformant <= my datamodelers -> size; iformant++) {
+		DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+		DataModeler_setTolerance (ffi, tolerance);
+	}
+}
+
+FormantModeler Formant_to_FormantModeler (Formant me, double tmin, double tmax, long numberOfFormants, long numberOfParametersPerTrack, int bandwidthEstimatesSigma) {
+	try {
+		long ifmin, ifmax, posInCollection = 0;
+		if (tmax <= tmin) {
+			tmin = my xmin; tmax = my xmax;
+		}
+		long numberOfDataPoints = Sampled_getWindowSamples (me, tmin, tmax, &ifmin, &ifmax);
+		if (numberOfDataPoints < numberOfParametersPerTrack) {
+			Melder_throw ("Not enought data points, extend the selection.");
+		}
+		autoFormantModeler thee = FormantModeler_create (tmin, tmax, numberOfFormants, numberOfDataPoints, numberOfParametersPerTrack);
+		for (long iformant = 1; iformant <= numberOfFormants; iformant++) {
+			posInCollection++;
+			DataModeler ffi = (DataModeler) thy datamodelers -> item[posInCollection];
+			long idata = 0, validData = 0;
+			for (long iframe = ifmin; iframe <= ifmax; iframe++) {
+				Formant_Frame curFrame = & my d_frames[iframe];
+				ffi -> x[++idata] = Sampled_indexToX (me, iframe);
+				ffi -> dataPointStatus[idata] = DataModeler_DATA_INVALID;
+				if (iformant <= curFrame -> nFormants) {
+					double frequency = curFrame -> formant[iformant].frequency;
+					if (NUMdefined (frequency)) {
+						double bw = curFrame -> formant[iformant].bandwidth;
+						ffi -> y[idata] = curFrame -> formant[iformant].frequency;
+						ffi -> sigmaY[idata] = bw;
+						ffi -> dataPointStatus[idata] = DataModeler_DATA_VALID;
+						validData++;
+					}
+				}
+			}
+			ffi -> useSigmaY = bandwidthEstimatesSigma;
+			ffi -> numberOfDataPoints = idata;
+			ffi -> tolerance = 1e-5;
+			if (validData < numberOfParametersPerTrack) { // remove don't throw exception
+				Collection_removeItem (thy datamodelers, posInCollection);
+				posInCollection--;
+			}
+		}
+		if (posInCollection == 0) {
+			Melder_throw ("Not enought data points in all the formants!");
+		}
+		FormantModeler_fit (thee.peek());
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("FormantModeler not created.");
+	}
+}
+
+Formant FormantModeler_to_Formant (FormantModeler me, int useEstimates, int estimateUndefineds) {
+	try {
+		long numberOfFormants = my datamodelers -> size;
+		DataModeler ff = (DataModeler) my datamodelers -> item[1];
+		long numberOfFrames = ff -> numberOfDataPoints;
+		double t1 = ff -> x[1], dt = ff -> x[2] -ff -> x[1];
+		autoFormant thee = Formant_create (my xmin, my xmax, numberOfFrames, dt, t1, numberOfFormants);
+		autoNUMvector<double> sigma (1, numberOfFormants);
+		if (useEstimates || estimateUndefineds) {
+			for (long iformant = 1; iformant <= numberOfFormants; iformant++) {
+				sigma[iformant] = FormantModeler_getStandardDeviation (me, iformant);
+			}
+		}
+		for (long iframe = 1; iframe <= numberOfFrames; iframe++) {
+			Formant_Frame thyFrame = & thy d_frames [iframe];
+			thyFrame -> intensity = 1; //???
+			thyFrame -> formant = NUMvector <structFormant_Formant> (1, numberOfFormants);
+			
+			for (long iformant = 1; iformant <= numberOfFormants; iformant++) {
+				DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+				double f = NUMundefined, b = f;
+				if (ffi -> dataPointStatus[iframe] != DataModeler_DATA_INVALID) {
+					f = useEstimates ? DataModeler_getModelValueAtX (ffi, ffi -> x[iframe]) : ffi -> y[iframe];
+					b = ff -> sigmaY[iframe]; // copy original value
+				} else {
+					if (estimateUndefineds) {
+						f = FormantModeler_getModelValueAtTime (me, iformant, ffi -> x[iframe]);
+						b = sigma[iformant];
+					}
+				}
+				thyFrame -> formant[iformant].frequency = f;
+				thyFrame -> formant[iformant].bandwidth = b;
+			}
+		}
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Cannot create Formant from FormantModeler.");
+	}
+}
+
+double FormantModeler_getChiSquaredQ (FormantModeler me, long fromFormant, long toFormant, int useSigmaY, double *probability, double *ndf) {
+	double chisq = NUMundefined, ndfTotal = 0;
+	if (fromFormant == 0 && toFormant == 0) {
+		fromFormant = 1; toFormant = my datamodelers -> size;
+	}
+	if (fromFormant >= 1 && toFormant <= my datamodelers -> size) {
+		chisq = 0;
+		long numberOfDefined = 0;
+		for (long iformant= fromFormant; iformant <= toFormant; iformant++) {
+			DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+			double p, df, chisqi = DataModeler_getChiSquaredQ (ffi, useSigmaY, &p, &df);
+			if (NUMdefined (chisqi)) {
+				chisq += df * chisqi;
+				ndfTotal += df; numberOfDefined++;
+			}
+		}
+		if (numberOfDefined > 0) {
+			chisq *= numberOfDefined / ndfTotal;
+			if (ndf != NULL) { *ndf = ndfTotal; }
+			if (probability != NULL) { *probability = NUMchiSquareQ (chisq, ndfTotal); }
+		}
+	}
+	return chisq;
+}
+
+double FormantModeler_getCoefficientOfDetermination (FormantModeler me, long fromFormant, long toFormant) {
+	double rSquared = NUMundefined;
+	if (fromFormant == 0 && toFormant == 0) {
+		fromFormant = 1; toFormant = my datamodelers -> size;
+	}
+	if (fromFormant >= 1 && toFormant <= my datamodelers -> size) {
+		double ssreg = 0, sstot = 0, ssregi, sstoti;
+		for (long iformant= fromFormant; iformant <= toFormant; iformant++) {
+			DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+			DataModeler_getCoefficientOfDetermination (ffi, &ssregi, &sstoti);
+			sstot += sstoti; ssreg += ssregi;
+		}
+		rSquared = sstot > 0 ? ssreg / sstot : 1;
+	}
+	return rSquared;
+}
+
+double FormantModeler_getResidualSumOfSquares (FormantModeler me, long iformant, long *numberOfDataPoints) {
+	double rss = NUMundefined;
+	if (iformant > 0 && iformant <= my datamodelers -> size) {
+		DataModeler ff = (DataModeler) my datamodelers -> item[iformant];
+		rss = DataModeler_getResidualSumOfSquares (ff, numberOfDataPoints);
+	}
+	return rss;
+}
+
+void FormantModeler_setParameterValuesToZero (FormantModeler me, long fromFormant, long toFormant, double numberOfSigmas) {
+	if (fromFormant == 0 && toFormant == 0) {
+		fromFormant = 1; toFormant = my datamodelers -> size;
+	}
+	if (fromFormant >= 1 && toFormant <= my datamodelers -> size) {
+		for (long iformant= fromFormant; iformant <= toFormant; iformant++) {
+			DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+			DataModeler_setParameterValuesToZero (ffi, numberOfSigmas);
+		}
+	}
+}
+
+FormantModeler FormantModeler_processOutliers (FormantModeler me, double numberOfSigmas, int useSigmaY) {
+	try {
+		long numberOfFormants = my datamodelers -> size;
+		if (numberOfFormants < 3) {
+			Melder_throw ("We need at least three formants to process outliers.");
+		}
+		long numberOfDataPoints = FormantModeler_getNumberOfDataPoints (me);
+		autoNUMvector<double> x (1, numberOfDataPoints); // also store x-values
+		autoNUMmatrix<double> z (1, numberOfFormants, 1, numberOfDataPoints);
+		// maybe some of the formants had NUMundefind's.
+
+		// 1. calculate z-scores for each formant and sort them in descending order
+		DataModeler ff = (DataModeler) my datamodelers -> item[1];
+		NUMvector_copyElements<double> (ff -> x, x.peek(), 1, numberOfDataPoints);
+		for (long iformant = 1; iformant <= numberOfFormants; iformant++) {
+			DataModeler ffi = (DataModeler) my datamodelers -> item[iformant];
+			autoNUMvector<double> zscores (DataModeler_getZScores (ffi, useSigmaY), 1);
+			NUMvector_copyElements<double> (zscores.peek(), z[iformant], 1, numberOfDataPoints);
+		}
+		// 2. Do the manipulation in a copy
+		autoFormantModeler thee = (FormantModeler) Data_copy (me);
+		for (long i = 1; i <= numberOfDataPoints; i++) {
+			// First the easy one: first formant missing: F1' = F2; F2' = F3
+			if (NUMdefined (z[1][i]) && NUMdefined (z[1][i]) && NUMdefined (z[3][i])) {
+				if (z[1][i] > numberOfSigmas && z[2][i] > numberOfSigmas && z[3][i] > numberOfSigmas) {
+					// all deviations have the same sign:
+					// probably F1 is missing
+					// try if f2 <- F1 and f3 <- F2 reduces chisq
+					double f2 = FormantModeler_getDataPointValue (me, 1, i); // F1
+					double f3 = FormantModeler_getDataPointValue (me, 2, i); // F2
+					FormantModeler_setDataPointStatus (thee.peek(), 1, i, DataModeler_DATA_INVALID);
+					FormantModeler_setDataPointValueAndStatus (thee.peek(), 2, i, f2, FormantModeler_DATA_FROM_LOWER);
+					FormantModeler_setDataPointValueAndStatus (thee.peek(), 3, i, f3, FormantModeler_DATA_FROM_LOWER);
+				}
+			}
+		}
+		FormantModeler_fit (thee.peek());
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Cannot calculate track discontinuities");
+	}
+}
+
+double FormantModeler_getSmoothnessValue (FormantModeler me, long fromFormant, long toFormant, long numberOfParametersPerTrack, double power) {
+	double smoothness = NUMundefined;
+	if (toFormant >= fromFormant) {
+		fromFormant = 1; toFormant = my datamodelers -> size;
+	}
+	if (fromFormant > 0 && fromFormant <= toFormant && toFormant <= my datamodelers -> size) {
+		long nofp;
+		double ndof, var = FormantModeler_getVarianceOfParameters (me, fromFormant, toFormant, 1, numberOfParametersPerTrack, &nofp);
+		double chisq = FormantModeler_getChiSquaredQ (me, fromFormant, toFormant, TRUE, NULL, &ndof);
+		if (NUMdefined (var) && NUMdefined (chisq) && nofp > 0) {
+			smoothness = sqrt (pow (var / nofp, power) * chisq / ndof);
+		}
+	}
+	return smoothness;
+}
+
+double FormantModeler_getAverageDistanceBetweenTracks (FormantModeler me, long track1, long track2, int type) {
+	double diff = NUMundefined;
+	if (track1 == track2) {
+		return 0;
+	}
+	if (track1 <= my datamodelers -> size && track2 <= my datamodelers -> size) {
+		DataModeler fi = (DataModeler) my datamodelers -> item[track1];
+		DataModeler fj = (DataModeler) my datamodelers -> item[track2];
+		// fi and fj have equal number of data points
+		long numberOfDataPoints = 0; diff = 0;
+		for (long i = 1; i <= fi -> numberOfDataPoints; i++) {
+			if (type != 0) {
+				double fie = fi -> f_evaluate (fi, fi -> x[i], fi -> parameter);
+				double fje = fj -> f_evaluate (fj, fj -> x[i], fj -> parameter);
+				diff += fabs (fie - fje);
+				numberOfDataPoints++;
+			} else if (fi -> dataPointStatus[i] != DataModeler_DATA_INVALID && fj -> dataPointStatus[i] != DataModeler_DATA_INVALID) {
+				diff += fabs (fi -> y[i] - fj -> y[i]);
+				numberOfDataPoints++;
+			}
+		}
+		diff /= numberOfDataPoints;
+	}
+	return diff;
+}
+
+double FormantModeler_getFormantsConstraintsFactor (FormantModeler me, double minF1, double maxF1, double minF2, double maxF2, double minF3) {
+	double f1 = FormantModeler_getParameterValue (me, 1, 1); // datamodelers -> item[1] -> parameter[1]
+	double minF1Factor = f1 > minF1 ? 1 : sqrt (minF1 - f1 + 1);
+	double maxF1Factor = f1 < maxF1 ? 1 : sqrt (f1 - maxF1 + 1);
+	double f2 = FormantModeler_getParameterValue (me, 2, 1); // datamodelers -> item[2] -> parameter[1]
+	double minF2Factor = f2 > minF2 ? 1 : sqrt (minF2 - f2 + 1);
+	double maxF2Factor = f2 < maxF2 ? 1 : sqrt (f2 - maxF2 + 1);
+	double f3 = FormantModeler_getParameterValue (me, 3, 1); // datamodelers -> item[3] -> parameter[1]
+	double minF3Factor = f3 > minF3 ? 1 : sqrt (minF3 - f3 + 1);
+	return minF1Factor * maxF1Factor * minF2Factor * maxF2Factor * minF3Factor;
+}
+
+long Formants_getSmoothestInInterval (Collection me, double tmin, double tmax, long numberOfFormantTracks, long numberOfParametersPerTrack, int useBandWidthsForTrackEstimation, int useConstraints, double numberOfSigmas, double power, double minF1, double maxF1, double minF2, double maxF2, double minF3) {
+	try {
+		long numberOfFormantObjects = my size, minNumberOfFormants = 1000000;
+		if (numberOfFormantObjects == 1) {
+			return 1;
+		}
+		autoNUMvector<long> numberOfFormants (1, numberOfFormantObjects);
+		autoNUMvector<int> invalid (1, numberOfFormantObjects);
+		double tminf = 0, tmaxf = 0;
+		for (long iobject = 1; iobject <= numberOfFormantObjects; iobject++) {
+			// Check that all Formants have the same domain
+			Formant fi = (Formant) my item[iobject];
+			if (tminf == tmaxf) {
+				tminf = fi -> xmin; tmaxf = fi -> xmax;
+			} else if (fi -> xmin != tminf || fi -> xmax != tmaxf) {
+				Melder_throw ("All Formant objects must have the same starting and finishing times.");
+			}
+			// Find the one that has least formant tracks
+			numberOfFormants[iobject] = Formant_getMaxNumFormants (fi);
+			if (numberOfFormants[iobject] < minNumberOfFormants) {
+				minNumberOfFormants = numberOfFormants[iobject];
+			}
+		}
+		if (numberOfFormantTracks == 0) { // default
+			numberOfFormantTracks = minNumberOfFormants;
+		}
+		if (numberOfFormantTracks > minNumberOfFormants) {
+			// make formants with not enough tracks invalid for the competition
+			long numberOfInvalids = 0;
+			for (long iobject = 1; iobject <= numberOfFormantObjects; iobject++) {
+				if (numberOfFormants[iobject] < numberOfFormantTracks) {
+					invalid[iobject] = 1;
+					numberOfInvalids++;
+				}
+			}
+			if (numberOfInvalids == numberOfFormantObjects) {
+				Melder_throw ("None of the Formants has enough formant tracks. Lower your upper formant number.");
+			}
+		}
+		if (tmax <= tmin) { // default
+			tmin = tminf; tmax = tmaxf;
+		}
+		if (! (tmin >= tminf && tmax <= tmaxf)) {
+			Melder_throw ("The selected interval needs to be within the Formant object's domain.");
+		}
+		/* The chisq is not meaningfull as a the only test whether one model is better than the other because if we have two models 
+		 * 1 & 2 with the same data points (x1[i]=x2[i] and y1[i]= y2[i] but if sigma1[i] < sigma2[i] than chisq1 > chisq2.
+		 * This is not what we want.
+		 * We test therefore the variances of the parameters because if sigma1[i] < sigma2[i] than pvar1 < pvar2.
+		 */
+		double minChiVar = 1e38;
+		long index = 0;
+		for (long iobject = 1; iobject <= numberOfFormantObjects; iobject++) {
+			if (invalid[iobject] != 1) {
+				Formant fi = (Formant) my item[iobject];
+				autoFormantModeler fs = Formant_to_FormantModeler (fi, tmin, tmax, numberOfFormantTracks, numberOfParametersPerTrack, useBandWidthsForTrackEstimation);
+				FormantModeler_setParameterValuesToZero (fs.peek(), 1, numberOfFormantTracks, numberOfSigmas);
+				double cf = useConstraints ? FormantModeler_getFormantsConstraintsFactor (fs.peek(), minF1, maxF1, minF2, maxF2, minF3) : 1;
+				double chiVar = FormantModeler_getSmoothnessValue (fs.peek(), 1, numberOfFormantTracks, numberOfParametersPerTrack, power);
+				if (NUMdefined (chiVar) && cf * chiVar < minChiVar) {
+					minChiVar = cf * chiVar;
+					index = iobject;
+				}
+			}
+		}
+		return index;
+	} catch (MelderError) {
+		Melder_throw ("No Formant object could be selected.");
+	}
+}
+
+Formant Formant_extractPart (Formant me, double tmin, double tmax) {
+	try {
+		if (tmin >= tmax) {
+			tmin = my xmin; tmax = my xmax;
+		}
+		if (tmin >= my xmax || tmax <= my xmin) {
+			Melder_throw ("Your start and end time should be between ", Melder_double (my xmin), L" and ", Melder_double (my xmax), ".");
+		}
+		long thyindex = 1, ifmin, ifmax;
+		long numberOfFrames = Sampled_getWindowSamples (me, tmin, tmax, &ifmin, &ifmax);
+		double t1 = Sampled_indexToX (me, ifmin);
+		autoFormant thee = Formant_create (tmin, tmax, numberOfFrames, my dx, t1, my maxnFormants);
+		for (long iframe = ifmin; iframe <= ifmax; iframe++, thyindex++) {
+			Formant_Frame myFrame = & my d_frames [iframe];
+			Formant_Frame thyFrame = & thy d_frames [thyindex];
+			myFrame -> copy (thyFrame);
+		}
+		return thee.transfer();
+		
+	} catch (MelderError) {
+		Melder_throw ("Formant part could not be extracted.");
+	}
+}
+
+Formant Formants_extractSmoothestPart (Collection me, double tmin, double tmax, long numberOfFormantTracks, long numberOfParametersPerTrack, int useBandWidthsForTrackEstimation, double numberOfSigmas, double power) {
+	try {
+		long index = Formants_getSmoothestInInterval (me, tmin, tmax, numberOfFormantTracks, numberOfParametersPerTrack, useBandWidthsForTrackEstimation, numberOfSigmas, power, 0, 1, 1, 1, 1, 1); // last four are just fillers
+		Formant bestfit = (Formant) my item[index];
+		autoFormant thee = Formant_extractPart (bestfit, tmin, tmax);
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Smoothest Formant part could not be extracted.");
+	}
+}
+
+
+Formant Formants_extractSmoothestPart_withFormantsConstraints (Collection me, double tmin, double tmax, long numberOfFormantTracks, long numberOfParametersPerTrack, int useBandWidthsForTrackEstimation, double numberOfSigmas, double power, double minF1, double maxF1, double minF2, double maxF2, double minF3) {
+	try {
+		long index = Formants_getSmoothestInInterval (me, tmin, tmax, numberOfFormantTracks, numberOfParametersPerTrack, useBandWidthsForTrackEstimation, numberOfSigmas, power, 1, minF1, maxF1, minF2, maxF2, minF3);
+		Formant bestfit = (Formant) my item[index];
+		autoFormant thee = Formant_extractPart (bestfit, tmin, tmax);
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Smoothest Formant part could not be extracted.");
+	}
+}
+
+Thing_implement (PitchModeler, DataModeler, 0);
+
+PitchModeler Pitch_to_PitchModeler (Pitch me, double tmin, double tmax, long numberOfParameters) {
+	try {
+		long ifmin, ifmax;
+		if (tmax <= tmin) {
+			tmin = my xmin; tmax = my xmax;
+		}
+		long numberOfDataPoints = Sampled_getWindowSamples (me, tmin, tmax, &ifmin, &ifmax);
+		if (numberOfDataPoints < numberOfParameters) {
+			Melder_throw ("Not enought data points, extend the selection.");
+		}
+		autoPitchModeler thee = Thing_new (PitchModeler);
+		DataModeler_init (thee.peek(), tmin, tmax, numberOfDataPoints, numberOfParameters, DataModeler_TYPE_LEGENDRE);
+		long idata = 0, validData = 0;
+		for (long iframe = ifmin; iframe <= ifmax; iframe++) {
+			thy x[++idata] = Sampled_indexToX (me, iframe);
+			thy dataPointStatus[idata] = DataModeler_DATA_INVALID;
+			if (Pitch_isVoiced_i (me, iframe)) {
+				thy y[idata] = my frame [iframe]. candidate [1]. frequency;
+				thy dataPointStatus[idata] = DataModeler_DATA_VALID;
+				validData++;
+			}
+		}
+		thy numberOfDataPoints = idata;
+		if (validData < numberOfParameters) { // remove don't throw exception
+			Melder_throw ("Not enough valid data in interval.");
+		}
+		DataModeler_fit (thee.peek());
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("No PitchModeler could be created.");
+	}
+}
+
+void PitchModeler_draw (PitchModeler me, Graphics g, double tmin, double tmax, double fmin, double fmax, long numberOfParameters, int garnish) {
+	Graphics_setInner (g);
+	DataModeler_drawTrack_inside (me, g, tmin, tmax, fmin, fmax, 1, numberOfParameters, 0);
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_drawInnerBox (g);
+		Graphics_textBottom (g, 1, L"Time (s)");
+		Graphics_textLeft (g, 1, L"Frequency (Hz)");
+		Graphics_marksBottom (g, 2, 1, 1, 0);
+		Graphics_marksLeftEvery (g, 1.0, 100.0, 1, 1, 1);
+	}
+}
+
+Formant Sound_to_Formant_interval (Sound me, double startTime, double endTime, double windowLength, double timeStep, double minFreq, double maxFreq, long numberOfFrequencySteps, double preemphasisFrequency, long numberOfFormantTracks, long numberOfParametersPerTrack, int weighData, double numberOfSigmas, double power, bool useConstraints, double minF1, double maxF1, double minF2, double maxF2, double minF3, double *ceiling) {
+	try {
+		// parameter check
+		if (endTime <= startTime) {
+			startTime = my xmin; endTime = my xmax;
+		}
+		double nyquistFrequency = 0.5 / my dx;
+		if (maxFreq > nyquistFrequency) {
+			Melder_throw ("The upper value of the maximum frequency range is higher than the Nyquist frequency of the sound.");
+		}
+		double df = 0, ceiling_best, mincriterium = 1e28;
+		if (minFreq >= maxFreq) {
+			numberOfFrequencySteps = 1;
+		} else {
+			df = (maxFreq - minFreq) / (numberOfFrequencySteps - 1);
+		}
+		long i_best = 0;
+		
+		// extract part +- windowLength because of Gaussian windowing in the formant analysis
+		// +timeStep/2 to have the analysis points maximally spread in the new domain.
+		
+		autoSound part = Sound_extractPart (me, startTime - windowLength + timeStep / 2, endTime + windowLength + timeStep / 2, kSound_windowShape_RECTANGULAR, 1, 1);
+
+		// Resample to 2*maxFreq to reduce resampling load in Sound_to_Formant
+		
+		autoSound resampled = Sound_resample (part.peek(), 2 * maxFreq, 50);
+		autoOrdered formants = Ordered_create ();
+		Melder_progressOff ();
+		for (long i = 1; i <= numberOfFrequencySteps; i++) {
+			double currentCeiling = minFreq + (i - 1) * df;
+			autoFormant formant = Sound_to_Formant_burg (resampled.peek(), timeStep, 5, currentCeiling, windowLength, preemphasisFrequency);
+			autoFormantModeler fm = Formant_to_FormantModeler (formant.peek(), startTime, endTime, numberOfFormantTracks, numberOfParametersPerTrack, weighData);
+			FormantModeler_setParameterValuesToZero (fm.peek(), 1, numberOfFormantTracks, numberOfSigmas);
+			Collection_addItem (formants.peek(), formant.transfer());
+			double cf = useConstraints ? FormantModeler_getFormantsConstraintsFactor (fm.peek(), minF1, maxF1, minF2, maxF2, minF3) : 1;
+			double chiVar = FormantModeler_getSmoothnessValue (fm.peek(), 1, numberOfFormantTracks, numberOfParametersPerTrack, power);
+			double criterium = chiVar * cf;
+			if (NUMdefined (chiVar) && criterium < mincriterium) {
+				mincriterium = criterium;
+				ceiling_best= currentCeiling;
+				i_best = i;
+			}
+		}
+		autoFormant thee = Formant_extractPart ((Formant) formants -> item[i_best], startTime, endTime);
+		Melder_progressOn ();
+		if (ceiling) {
+			*ceiling = ceiling_best;
+		}
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("No Formant object created.");
+	}
+}
+
+Formant Sound_to_Formant_interval_robust (Sound me, double startTime, double endTime, double windowLength, double timeStep, double minFreq, double maxFreq, long numberOfFrequencySteps, double preemphasisFrequency, long numberOfFormantTracks, long numberOfParametersPerTrack, int weighData, double numberOfSigmas, double power, bool useConstraints, double minF1, double maxF1, double minF2, double maxF2, double minF3, double *ceiling) {
+	try {
+		// parameter check
+		if (endTime <= startTime) {
+			startTime = my xmin; endTime = my xmax;
+		}
+		double nyquistFrequency = 0.5 / my dx;
+		if (maxFreq > nyquistFrequency) {
+			Melder_throw ("The upper value of the maximum frequency range is higher than the Nyquist frequency of the sound.");
+		}
+		double df = 0, ceiling_best, mincriterium = 1e28;
+		if (minFreq >= maxFreq) {
+			numberOfFrequencySteps = 1;
+		} else {
+			df = (maxFreq - minFreq) / (numberOfFrequencySteps - 1);
+		}
+		long i_best = 0;
+		
+		// extract part +- windowLength because of Gaussian windowing in the formant analysis
+		// +timeStep/2 to have the analysis points maximally spread in the new domain.
+		
+		autoSound part = Sound_extractPart (me, startTime - windowLength + timeStep / 2, endTime + windowLength + timeStep / 2, kSound_windowShape_RECTANGULAR, 1, 1);
+
+		// Resample to 2*maxFreq to reduce resampling load in Sound_to_Formant
+		
+		autoSound resampled = Sound_resample (part.peek(), 2 * maxFreq, 50);
+		autoOrdered formants = Ordered_create ();
+		Melder_progressOff ();
+		for (long i = 1; i <= numberOfFrequencySteps; i++) {
+			double currentCeiling = minFreq + (i - 1) * df;
+			autoFormant formant = Sound_to_Formant_robust (resampled.peek(), timeStep, 5, currentCeiling, windowLength, preemphasisFrequency, 50, 1.5, 3, 0.0000001, 1);
+			autoFormantModeler fm = Formant_to_FormantModeler (formant.peek(), startTime, endTime, numberOfFormantTracks, numberOfParametersPerTrack, weighData);
+			FormantModeler_setParameterValuesToZero (fm.peek(), 1, numberOfFormantTracks, numberOfSigmas);
+			Collection_addItem (formants.peek(), formant.transfer());
+			double cf = useConstraints ? FormantModeler_getFormantsConstraintsFactor (fm.peek(), minF1, maxF1, minF2, maxF2, minF3) : 1;
+			double chiVar = FormantModeler_getSmoothnessValue (fm.peek(), 1, numberOfFormantTracks, numberOfParametersPerTrack, power);
+			double criterium = chiVar * cf;
+			if (NUMdefined (chiVar) && criterium < mincriterium) {
+				mincriterium = criterium;
+				ceiling_best= currentCeiling;
+				i_best = i;
+			}
+		}
+		autoFormant thee = Formant_extractPart ((Formant) formants -> item[i_best], startTime, endTime);
+		Melder_progressOn ();
+		if (ceiling) {
+			*ceiling = ceiling_best;
+		}
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw ("No Formant object created.");
+	}
+}
+
+/* End of file DataModeler.cpp */
diff --git a/dwtools/DataModeler.h b/dwtools/DataModeler.h
new file mode 100644
index 0000000..36a82ac
--- /dev/null
+++ b/dwtools/DataModeler.h
@@ -0,0 +1,209 @@
+#ifndef _DataModeler_h_
+#define _DataModeler_h_
+/* DataModeler.h
+ *
+ * Copyright (C) 2014 David Weenink
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ djmw 20140217
+*/
+
+#define DataModeler_TYPE_POLYNOMIAL 0
+#define DataModeler_TYPE_LEGENDRE 1
+
+#define DataModeler_PARAMETER_FREE 0
+#define DataModeler_PARAMETER_FIXED 1
+#define DataModeler_PARAMETER_UNDEFINED -1
+
+#define DataModeler_DATA_WEIGH_EQUAL 0
+#define DataModeler_DATA_WEIGH_SIGMA 1
+#define DataModeler_DATA_WEIGH_RELATIVE 2
+#define DataModeler_DATA_WEIGH_SQRT 3
+#define DataModeler_DATA_WEIGH_SQRT 3
+
+
+#define DataModeler_DATA_VALID 0
+#define DataModeler_DATA_INVALID -1
+#define DataModeler_DATA_FROM_FIT 1
+#define DataModeler_DATA_FROM_COPY1 2
+#define DataModeler_DATA_FROM_COPY2 3
+#define DataModeler_DATA_FROM_COPY3 4
+#define DataModeler_DATA_FROM_COPY4 5
+
+#define FormantModeler_DATA_FROM_LOWER 2
+#define FormantModeler_DATA_FROM_UPPER 3
+
+#include "Collection.h"
+#include "Pitch.h"
+#include "Sound_to_Formant.h"
+#include "SSCP.h"
+#include "Table.h"
+
+#include "DataModeler_def.h"
+oo_CLASS_CREATE (DataModeler, Function);
+oo_CLASS_CREATE (FormantModeler, Function);
+
+Thing_define (PitchModeler, DataModeler) {
+	// overridden methods:
+	//	virtual void v_info ();
+};
+
+void  DataModeler_init (DataModeler me, double xmin, double xmax, long numberOfDataPoints, long numberOfParameters, int type);
+
+DataModeler DataModeler_create (double xmin, double xmax, long numberOfDataPoints, long numberOfParameters, int type);
+
+void DataModeler_setBasisFunctions (DataModeler me, int type);
+
+void DataModeler_draw_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, int errorbars, int connectPoints, double barWidth_mm, double horizontalOffset_mm, int drawDots);
+
+void DataModeler_speckle_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, int errorbars, double barWidth_mm, double horizontalOffset_mm);
+
+void DataModeler_speckle (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, int errorbars, double barWidth_mm, double horizontalOffset_mm, int garnish);
+
+void DataModeler_drawTrack (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, double horizontalOffset_mm, int garnish);
+
+void DataModeler_drawTrack_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int estimated, long numberOfParameters, double horizontalOffset_mm);
+
+void DataModeler_drawOutliersMarked_inside (DataModeler me, Graphics g, double xmin, double xmax, double ymin, double ymax, int useSigmaY, double numberOfSigmas, wchar_t *mark, int marksFontSize, double horizontalOffset_mm);
+
+/* Get the y-value of the fitted function at x */
+
+void DataModeler_setTolerance (DataModeler me, double tolerance);
+
+void DataModeler_fit (DataModeler me);
+
+// sigmaY used in fit or not.
+void DataModeler_setDataWeighing (DataModeler me, int useSigmaY);
+long DataModeler_getNumberOfFixedParameters (DataModeler me);
+void DataModeler_setParameterValueFixed (DataModeler me, long index, double value);
+void DataModeler_setParametersFree (DataModeler me, long fromIndex, long toIndex);
+double DataModeler_getParameterValue (DataModeler me, long index);
+int DataModeler_getParameterStatus (DataModeler me, long index);
+double DataModeler_getParameterStandardDeviation (DataModeler me, long index);
+double DataModeler_getVarianceOfParameters (DataModeler me, long fromIndex, long toIndex, long *numberOfFreeParameters);
+void DataModeler_setParameterValuesToZero (DataModeler me, double numberOfSigmas);
+double DataModeler_estimateSigmaY (DataModeler me);
+
+void DataModeler_getExtremaY (DataModeler me, double *ymin, double *ymax);
+double DataModeler_getModelValueAtX (DataModeler me, double x);
+double DataModeler_getWeightedMean (DataModeler me);
+
+long DataModeler_getNumberOfInvalidDataPoints (DataModeler me);
+double DataModeler_getDataPointValue (DataModeler me, long index);
+void DataModeler_setDataPointValue (DataModeler me, long index, double value);
+int DataModeler_getDataPointStatus (DataModeler me, long index);
+void DataModeler_setDataPointStatus (DataModeler me, long index, int status);
+void DataModeler_setDataPointSigma (DataModeler me, long index, double sigma);
+double DataModeler_getDataPointSigma (DataModeler me, long index);
+double DataModeler_getResidualSumOfSquares (DataModeler me, long *numberOfDataPoints);
+
+double *DataModeler_getZScores (DataModeler me, int useSigmaY);
+double DataModeler_getDegreesOfFreedom (DataModeler me);
+double DataModeler_getChiSquaredQ (DataModeler me, int useSigmaY, double *probability, double *ndf);
+double DataModeler_getCoefficientOfDetermination (DataModeler me, double *ssreg, double *sstot);
+
+Formant Formant_extractPart (Formant me, double tmin, double tmax);
+
+Covariance DataModeler_to_Covariance_parameters (DataModeler me);
+Table DataModeler_to_Table_zscores (DataModeler me, int useSigmaY);
+
+FormantModeler FormantModeler_create (double tmin, double tmax, long numberOfFormants, long numberOfDataPoints, long numberOfParameters);
+void FormantModeler_fit (FormantModeler me);
+void FormantModeler_drawBasisFunction (FormantModeler me, Graphics g, double tmin, double tmax, double fmin, double fmax,
+ 	long iformant, long iterm, bool scaled, long numberOfPoints, int garnish);
+
+void FormantModeler_setDataWeighing (FormantModeler me, long fromFormant, long toFormant, int useSigmaY);
+
+void FormantModeler_setParameterValueFixed (FormantModeler me, long iformant, long index, double value);
+void FormantModeler_setParametersFree (FormantModeler me, long fromFormant, long toFormant, long fromIndex, long toIndex);
+void FormantModeler_setParameterValuesToZero (FormantModeler me, long fromFormant, long toFormant, double numberOfSigmas);
+
+void FormantModeler_setTolerance (FormantModeler me, double tolerance);
+
+void FormantModeler_speckle (FormantModeler me, Graphics g, double tmin, double tmax, double fmax,
+	long fromTrack, long toTrack, int estimated, long numberOfParameters, int errorBars, double barWidth_mm, double horizontalOffset_mm, int garnish);
+
+void FormantModeler_drawTracks (FormantModeler me, Graphics g, double tmin, double tmax, double fmax, long fromTrack, long toTrack, 
+	 int estimated, long numberOfParameters, double horizontalOffset_mm, int garnish);
+
+void FormantModeler_drawOutliersMarked (FormantModeler me, Graphics g, double tmin, double tmax, double fmax, long fromTrack, long toTrack, double numberOfSigmas, int useSigmaY, wchar_t *mark, int marksFontSize, double horizontalOffset_mm, int garnish);
+
+void FormantModeler_normalProbabilityPlot (FormantModeler me, Graphics g, long iformant, int useSigmaY, long numberOfQuantiles, double numberOfSigmas, int labelSize, const wchar_t *label, int garnish);
+
+Table FormantModeler_to_Table_zscores (FormantModeler me, int useSigmaY);
+
+Covariance FormantModeler_to_Covariance_parameters (FormantModeler me, long iformant);
+
+double FormantModeler_getChiSquaredQ (FormantModeler me, long fromFormant, long toFormant, int useSigmaY, double *probability, double *ndf);
+
+double FormantModeler_getCoefficientOfDetermination (FormantModeler me, long fromFormant, long toFormant);
+double FormantModeler_getStandardDeviation (FormantModeler me, long iformant);
+double FormantModeler_getResidualSumOfSquares (FormantModeler me, long iformant, long *numberOfDataPoints);
+double FormantModeler_getEstimatedValueAtTime (FormantModeler me, long iformant, double time);
+
+long FormantModeler_getNumberOfParameters (FormantModeler me, long iformant);
+long FormantModeler_getNumberOfFixedParameters (FormantModeler me, long iformant);
+
+double FormantModeler_getParameterStandardDeviation ( FormantModeler me, long iformant, long index);
+double FormantModeler_getVarianceOfParameters (FormantModeler me, long fromFormant, long toFormant, long fromIndex, long toIndex, long *numberOfFreeParameters);
+int FormantModeler_getParameterStatus (FormantModeler me, long iformant, long index);
+
+long FormantModeler_getNumberOfDataPoints (FormantModeler me, long iformant);
+long FormantModeler_getNumberOfInvalidDataPoints (FormantModeler me, long iformant);
+
+void FormantModeler_setDataPointStatus (FormantModeler me, long iformant, long index, int status);
+int FormantModeler_getDataPointStatus (FormantModeler me, long iformant, long index);
+double FormantModeler_getDataPointValue (FormantModeler me, long iformant, long index);
+void FormantModeler_setDataPointValue (FormantModeler me, long iformant, long index, double value);
+double FormantModeler_getDataPointSigma (FormantModeler me, long iformant, long index);
+void FormantModeler_setDataPointSigma (FormantModeler me, long iformant, long index, double sigma);
+
+double FormantModeler_getDegreesOfFreedom (FormantModeler me, long iformant);
+long FormantModeler_getNumberOfTracks (FormantModeler me);
+
+double FormantModeler_getModelValueAtTime (FormantModeler me, long iformant, double time);
+double FormantModeler_getWeightedMean (FormantModeler me, long iformant);
+
+double FormantModeler_getParameterValue (FormantModeler me, long iformant, long iparameter);
+
+FormantModeler Formant_to_FormantModeler (Formant me, double tmin, double tmax, long numberOfFormants, long numberOfParametersPerTrack, int bandwidthEstimatesSigma);
+Formant FormantModeler_to_Formant (FormantModeler me, int estimate, int estimateUndefined);
+
+FormantModeler FormantModeler_processOutliers (FormantModeler me, double numberOfSigmas, int useSigmaY);
+double FormantModeler_getSmoothnessValue (FormantModeler me, long fromFormant, long toFormant, long numberOfParametersPerTrack, double power);
+double FormantModeler_getAverageDistanceBetweenTracks (FormantModeler me, long track1, long track2, int type);
+
+long Formants_getSmoothestInInterval (Collection me, double tmin, double tmax, long numberOfFormantTracks, long numberOfParametersPerTrack, int useBandWidthsForTrackEstimation, int useConstraints, double numberOfSigmas, double power, double minF1, double maxF1, double minF2, double maxF2, double minF3);
+
+double FormantModeler_getFormantsConstraintsFactor (FormantModeler me, double minF1, double maxF1, double minF2, double maxF2, double minF3);
+
+Formant Formants_extractSmoothestPart (Collection me, double tmin, double tmax, long numberOfFormantTracks, long numberOfParametersPerTrack, int useBandWidthsForTrackEstimation, double numberOfSigmas, double power);
+Formant Formants_extractSmoothestPart_withFormantsConstraints (Collection me, double tmin, double tmax, long numberOfFormantTracks, long numberOfParametersPerTrack, int useBandWidthsForTrackEstimation, double numberOfSigmas, double power, double minF1, double maxF1, double minF2, double maxF2, double minF3);
+
+DataModeler FormantModeler_extractDataModeler (FormantModeler me, long iformant);
+
+PitchModeler Pitch_to_PitchModeler (Pitch me, double tmin, double tmax, long numberOfParameters);
+
+void PitchModeler_draw (PitchModeler me, Graphics g, double tmin, double tmax, double fmin, double fmax, long numberOfParameters, int garnish);
+
+DataModeler Table_to_DataModeler (Table me, double xmin, double xmax, long xcolumn, long ycolumn, long scolumn, long numberOfParameters, int type);
+
+Formant Sound_to_Formant_interval (Sound me, double startTime, double endTime, double windowLength, double timeStep, double minFreq, double maxFreq, long numberOfFrequencySteps, double preemphasisFrequency, long numberOfFormantTracks, long numberOfParametersPerTrack, int weighData, double numberOfSigmas, double power, bool useConstraints, double minF1, double maxF1, double minF2, double maxF2, double minF3, double *ceiling);
+
+Formant Sound_to_Formant_interval_robust (Sound me, double startTime, double endTime, double windowLength, double timeStep, double minFreq, double maxFreq, long numberOfFrequencySteps, double preemphasisFrequency, long numberOfFormantTracks, long numberOfParametersPerTrack, int weighData, double numberOfSigmas, double power, bool useConstraints, double minF1, double maxF1, double minF2, double maxF2, double minF3, double *ceiling);
+#endif /* _DataModeler_h_ */
diff --git a/dwtools/DataModeler_def.h b/dwtools/DataModeler_def.h
new file mode 100644
index 0000000..79f5a0d
--- /dev/null
+++ b/dwtools/DataModeler_def.h
@@ -0,0 +1,46 @@
+/* DataModeler_def.h */
+/* David Weenink, 20140216 */
+
+#define ooSTRUCT DataModeler
+oo_DEFINE_CLASS (DataModeler, Function)
+	oo_INT (type)	// polynomial, legendre ...
+	oo_LONG (numberOfDataPoints)
+	oo_LONG (numberOfParameters)
+	oo_DOUBLE_VECTOR (x, numberOfDataPoints)
+	oo_DOUBLE_VECTOR (y, numberOfDataPoints)
+	oo_DOUBLE_VECTOR (sigmaY, numberOfDataPoints)
+	oo_INT_VECTOR (dataPointStatus, numberOfDataPoints)
+	oo_DOUBLE_VECTOR (parameter, numberOfParameters)
+	oo_INT_VECTOR (parameterStatus, numberOfParameters)
+	oo_DOUBLE (tolerance)
+	oo_INT (useSigmaY)
+	oo_OBJECT (Strings, 0, parameterNames)
+	oo_OBJECT (Covariance, 0, parameterCovariances)
+	#if oo_DECLARING
+		// overridden methods:
+		double (*f_evaluate) (DataModeler me, double x, double p[]);
+		void (*f_evaluateBasisFunctions) (DataModeler me, double x, double term[]);
+		public:
+			virtual void v_info ();
+	#endif
+	#if oo_COPYING
+			DataModeler_setBasisFunctions (thee, thy type);
+	#endif
+	#if oo_READING
+		 DataModeler_setBasisFunctions (this, type);
+	#endif
+oo_END_CLASS (DataModeler)
+#undef ooSTRUCT
+
+#define ooSTRUCT FormantModeler
+oo_DEFINE_CLASS (FormantModeler, Function)
+	oo_COLLECTION (Ordered, datamodelers, DataModeler, 0)
+	#if oo_DECLARING
+		// overridden methods:
+		public:
+			virtual void v_info ();
+	#endif
+oo_END_CLASS (FormantModeler)
+#undef ooSTRUCT
+
+/* End of file DataModeler_def.h */	
diff --git a/dwtools/EEG_extensions.cpp b/dwtools/EEG_extensions.cpp
index 9428ec2..8215ab4 100644
--- a/dwtools/EEG_extensions.cpp
+++ b/dwtools/EEG_extensions.cpp
@@ -1,6 +1,6 @@
 /* EEG_extensions.cpp
  *
- * Copyright (C) 2012 David Weenink
+ * Copyright (C) 2012-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,7 @@
 
 
 #include "ICA.h"
-#include "EEG.h"
+#include "EEG_extensions.h"
 #include "NUM2.h"
 #include "Sound_and_PCA.h"
 #include "Sound_extensions.h"
@@ -29,13 +29,13 @@
 static EEG EEG_copyWithoutSound (EEG me) {
 	try {
  		autoEEG thee = EEG_create (my xmin, my xmax);
-		thy d_numberOfChannels = my d_numberOfChannels;
-		thy d_textgrid = (TextGrid) Data_copy (my d_textgrid);
-		autostringvector channelNames (1, my d_numberOfChannels);
-		for (long i = 1; i <= my d_numberOfChannels; i++) {
-			channelNames[i] = Melder_wcsdup (my d_channelNames[i]);
+		thy numberOfChannels = my numberOfChannels;
+		thy textgrid = (TextGrid) Data_copy (my textgrid);
+		autostringvector channelNames (1, my numberOfChannels);
+		for (long i = 1; i <= my numberOfChannels; i++) {
+			channelNames[i] = Melder_wcsdup (my channelNames[i]);
 		}
-		thy d_channelNames = channelNames.transfer();
+		thy channelNames = channelNames.transfer();
 		return thee.transfer();
 	} catch (MelderError) {
 		Melder_throw (me, ": not copied.");
@@ -46,8 +46,8 @@ static long *EEG_channelNames_to_channelNumbers (EEG me, wchar_t **channelNames,
 	try {
 		autoNUMvector<long> channelNumbers (1, numberOfChannelNames);
 		for (long i = 1; i <= numberOfChannelNames; i++) {
-			for (long j = 1; j <= my d_numberOfChannels; j++) {
-				if (Melder_wcsequ (channelNames[i], my d_channelNames[j])) {
+			for (long j = 1; j <= my numberOfChannels; j++) {
+				if (Melder_wcsequ (channelNames[i], my channelNames[j])) {
 					channelNumbers[i] = j;
 				}
 			}
@@ -66,7 +66,7 @@ static void EEG_setChannelNames_selected (EEG me, const wchar_t *precursor, long
 	const wchar_t *zero = L"0";
 	for (long i = 1; i <= numberOfChannels; i++) {
 		MelderString_append (&name, precursor);
-		if (my d_numberOfChannels > 100) {
+		if (my numberOfChannels > 100) {
 			if (i < 10) {
 				MelderString_append (&name, zero);
 			}
@@ -77,12 +77,12 @@ static void EEG_setChannelNames_selected (EEG me, const wchar_t *precursor, long
 			MelderString_append (&name, zero);
 		}
 		MelderString_append (&name, Melder_integer (i));
-		my f_setChannelName (channelNumbers[i], name.string);
+		EEG_setChannelName (me, channelNumbers[i], name.string);
 		MelderString_empty (&name);
 	}
 }
 
-CrossCorrelationTable EEG_to_CrossCorrelationTable (EEG me, double startTime, double endTime, double lagTime, const wchar_t *channelRanges)
+CrossCorrelationTable EEG_to_CrossCorrelationTable (EEG me, double startTime, double endTime, double lagStep, const wchar_t *channelRanges)
 {
 	try {
 		// autowindow
@@ -96,15 +96,15 @@ CrossCorrelationTable EEG_to_CrossCorrelationTable (EEG me, double startTime, do
 		if (endTime > my xmax) {
 			endTime = my xmax;
 		}
-		autoEEG thee = my f_extractPart (startTime, endTime, true);
+		autoEEG thee = EEG_extractPart (me, startTime, endTime, true);
 		long numberOfChannels;
-		autoNUMvector <long> channels (NUMstring_getElementsOfRanges (channelRanges, thy d_numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
-		autoSound soundPart = Sound_copyChannelRanges (thy d_sound, channelRanges);
-		autoCrossCorrelationTable him = Sound_to_CrossCorrelationTable (soundPart.peek(), startTime, endTime, lagTime);
+		autoNUMvector <long> channels (NUMstring_getElementsOfRanges (channelRanges, thy numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
+		autoSound soundPart = Sound_copyChannelRanges (thy sound, channelRanges);
+		autoCrossCorrelationTable him = Sound_to_CrossCorrelationTable (soundPart.peek(), startTime, endTime, lagStep);
 		// assign channel names
 		for (long i = 1; i <= numberOfChannels; i++) {
 			long ichannel = channels[i];
-			wchar_t *label = my d_channelNames[ichannel];
+			wchar_t *label = my channelNames[ichannel];
 			TableOfReal_setRowLabel (him.peek(), i, label);
 			TableOfReal_setColumnLabel (him.peek(), i, label);
 		}
@@ -117,8 +117,8 @@ CrossCorrelationTable EEG_to_CrossCorrelationTable (EEG me, double startTime, do
 Covariance EEG_to_Covariance (EEG me, double startTime, double endTime, const wchar_t *channelRanges)
 {
 	try {
-		double lagTime = 0.0;
-		autoCrossCorrelationTable thee = EEG_to_CrossCorrelationTable (me, startTime, endTime, lagTime, channelRanges);
+		double lagStep = 0.0;
+		autoCrossCorrelationTable thee = EEG_to_CrossCorrelationTable (me, startTime, endTime, lagStep, channelRanges);
         autoCovariance him = Thing_new (Covariance);
         thy structCrossCorrelationTable :: v_copy (him.peek());
 		return him.transfer();
@@ -127,7 +127,7 @@ Covariance EEG_to_Covariance (EEG me, double startTime, double endTime, const wc
 	}
 }
 
-CrossCorrelationTables EEG_to_CrossCorrelationTables (EEG me, double startTime, double endTime, double lagTime, long ncovars, const wchar_t *channelRanges) {
+CrossCorrelationTables EEG_to_CrossCorrelationTables (EEG me, double startTime, double endTime, double lagStep, long ncovars, const wchar_t *channelRanges) {
 	try {
 		// autowindow
 		if (startTime == endTime) {
@@ -140,11 +140,11 @@ CrossCorrelationTables EEG_to_CrossCorrelationTables (EEG me, double startTime,
 		if (endTime > my xmax) {
 			endTime = my xmax;
 		}
-		autoEEG thee = my f_extractPart (startTime, endTime, true);
+		autoEEG thee = EEG_extractPart (me, startTime, endTime, true);
 		long numberOfChannels;
-		autoNUMvector <long> channels (NUMstring_getElementsOfRanges (channelRanges, thy d_numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
-		autoSound soundPart = Sound_copyChannelRanges (thy d_sound, channelRanges);
-		autoCrossCorrelationTables him = Sound_to_CrossCorrelationTables (soundPart.peek(), startTime, endTime, lagTime, ncovars);
+		autoNUMvector <long> channels (NUMstring_getElementsOfRanges (channelRanges, thy numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
+		autoSound soundPart = Sound_copyChannelRanges (thy sound, channelRanges);
+		autoCrossCorrelationTables him = Sound_to_CrossCorrelationTables (soundPart.peek(), startTime, endTime, lagStep, ncovars);
 		return him.transfer();
 	} catch (MelderError) {
 		Melder_throw (me, ": no CrossCorrelationTables calculated.");
@@ -172,15 +172,15 @@ EEG EEG_and_PCA_to_EEG_whiten (EEG me, PCA thee, long numberOfComponents) {
 		if (numberOfComponents <= 0 || numberOfComponents > thy numberOfEigenvalues) {
 			numberOfComponents = thy numberOfEigenvalues;
 		}
-		numberOfComponents = numberOfComponents > my d_numberOfChannels ? my d_numberOfChannels : numberOfComponents;
+		numberOfComponents = numberOfComponents > my numberOfChannels ? my numberOfChannels : numberOfComponents;
 
 		autoNUMvector<long> channelNumbers (EEG_channelNames_to_channelNumbers (me, thy labels, thy dimension), 1);
 
 		autoEEG him = (EEG) Data_copy (me);
-		autoSound white = Sound_and_PCA_whitenSelectedChannels (my d_sound, thee, numberOfComponents, channelNumbers.peek(), thy dimension);
+		autoSound white = Sound_and_PCA_whitenSelectedChannels (my sound, thee, numberOfComponents, channelNumbers.peek(), thy dimension);
 		for (long i = 1; i <= thy dimension; i++) {
 			long ichannel = channelNumbers[i];
-			NUMvector_copyElements<double> (white -> z[i], his d_sound -> z[ichannel], 1, his d_sound -> nx);
+			NUMvector_copyElements<double> (white -> z[i], his sound -> z[ichannel], 1, his sound -> nx);
 		}
 		EEG_setChannelNames_selected (him.peek(), L"wh", channelNumbers.peek(), thy dimension);
 		return him.transfer();
@@ -194,14 +194,14 @@ EEG EEG_and_PCA_to_EEG_principalComponents (EEG me, PCA thee, long numberOfCompo
 		if (numberOfComponents <= 0 || numberOfComponents > thy numberOfEigenvalues) {
 			numberOfComponents = thy numberOfEigenvalues;
 		}
-		numberOfComponents = numberOfComponents > my d_numberOfChannels ? my d_numberOfChannels : numberOfComponents;
+		numberOfComponents = numberOfComponents > my numberOfChannels ? my numberOfChannels : numberOfComponents;
 
 		autoNUMvector<long> channelNumbers (EEG_channelNames_to_channelNumbers (me, thy labels, thy dimension), 1);
 		autoEEG him = (EEG) Data_copy (me);
-		autoSound pc = Sound_and_PCA_to_Sound_pc_selectedChannels (my d_sound, thee, numberOfComponents, channelNumbers.peek(), thy dimension);
+		autoSound pc = Sound_and_PCA_to_Sound_pc_selectedChannels (my sound, thee, numberOfComponents, channelNumbers.peek(), thy dimension);
 		for (long i = 1; i <= thy dimension; i++) {
 			long ichannel = channelNumbers[i];
-			NUMvector_copyElements<double> (pc -> z[i], his d_sound -> z[ichannel], 1, his d_sound -> nx);
+			NUMvector_copyElements<double> (pc -> z[i], his sound -> z[ichannel], 1, his sound -> nx);
 		}
 		EEG_setChannelNames_selected (him.peek(), L"pc", channelNumbers.peek(), thy dimension);
 		return him.transfer();
@@ -210,7 +210,7 @@ EEG EEG_and_PCA_to_EEG_principalComponents (EEG me, PCA thee, long numberOfCompo
 	}
 }
 
-EEG EEG_to_EEG_bss (EEG me, double startTime, double endTime, long ncovars, double lagTime, const wchar_t *channelRanges, int whiteningMethod, int diagonalizerMethod, long maxNumberOfIterations, double tol) {
+EEG EEG_to_EEG_bss (EEG me, double startTime, double endTime, long ncovars, double lagStep, const wchar_t *channelRanges, int whiteningMethod, int diagonalizerMethod, long maxNumberOfIterations, double tol) {
 	try {
 		// autowindow
 		if (startTime == endTime) {
@@ -224,18 +224,18 @@ EEG EEG_to_EEG_bss (EEG me, double startTime, double endTime, long ncovars, doub
 			endTime = my xmax;
 		}
 		long numberOfChannels;
-		autoNUMvector <long> channelNumbers (NUMstring_getElementsOfRanges (channelRanges, my d_numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
-		autoEEG thee = my f_extractPart (startTime, endTime, true);
+		autoNUMvector <long> channelNumbers (NUMstring_getElementsOfRanges (channelRanges, my numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
+		autoEEG thee = EEG_extractPart (me, startTime, endTime, true);
 		if (whiteningMethod != 0) {
 			bool fromCorrelation = whiteningMethod == 2;
 			autoPCA pca = EEG_to_PCA (thee.peek(), thy xmin, thy xmax, channelRanges, fromCorrelation);
 			autoEEG white = EEG_and_PCA_to_EEG_whiten (thee.peek(), pca.peek(), 0);
 			thee.reset (white.transfer());
 		}
-		autoMixingMatrix mm = Sound_to_MixingMatrix (thy d_sound, startTime, endTime, ncovars, lagTime, maxNumberOfIterations, tol, diagonalizerMethod);
+		autoMixingMatrix mm = Sound_to_MixingMatrix (thy sound, startTime, endTime, ncovars, lagStep, maxNumberOfIterations, tol, diagonalizerMethod);
 
 		autoEEG him = EEG_copyWithoutSound (me);
-		his d_sound = Sound_and_MixingMatrix_unmix (my d_sound, mm.peek());
+		his sound = Sound_and_MixingMatrix_unmix (my sound, mm.peek());
 		EEG_setChannelNames_selected (him.peek(), L"ic", channelNumbers.peek(), numberOfChannels);
 
 		// Calculate the cross-correlations between eye-channels and the ic's
@@ -251,15 +251,15 @@ EEG EEG_to_EEG_bss (EEG me, double startTime, double endTime, long ncovars, doub
 Sound EEG_to_Sound_modulated (EEG me, double baseFrequency, double channelBandwidth, const wchar_t *channelRanges) {
 	try {
 		long numberOfChannels;
-		autoNUMvector <long> channelNumbers (NUMstring_getElementsOfRanges (channelRanges, my d_numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
-		double maxFreq = baseFrequency + my d_numberOfChannels * channelBandwidth;
+		autoNUMvector <long> channelNumbers (NUMstring_getElementsOfRanges (channelRanges, my numberOfChannels, & numberOfChannels, NULL, L"channel", true), 1);
+		double maxFreq = baseFrequency + my numberOfChannels * channelBandwidth;
 		double samplingFrequency = 2 * maxFreq;
 		samplingFrequency = samplingFrequency < 44100 ? 44100 : samplingFrequency;
 		autoSound thee = Sound_createSimple (1, my xmax - my xmin, samplingFrequency);
 		for (long i = 1; i <= numberOfChannels; i++) {
 			long ichannel = channelNumbers[i];
 			double fbase = baseFrequency;// + (ichannel - 1) * channelBandwidth;
-			autoSound si = Sound_extractChannel (my d_sound, ichannel);
+			autoSound si = Sound_extractChannel (my sound, ichannel);
 			autoSpectrum spi = Sound_to_Spectrum (si.peek(), 1);
 			Spectrum_passHannBand (spi.peek(), 0.5, channelBandwidth - 0.5, 0.5);
 			autoSpectrum spi_shifted = Spectrum_shiftFrequencies (spi.peek(), fbase, samplingFrequency / 2, 30);
@@ -278,7 +278,7 @@ Sound EEG_to_Sound_modulated (EEG me, double baseFrequency, double channelBandwi
 
 Sound EEG_to_Sound_frequencyShifted (EEG me, long channel, double frequencyShift, double samplingFrequency, double maxAmp) {
 	try {
-		autoSound si = Sound_extractChannel (my d_sound, channel);
+		autoSound si = Sound_extractChannel (my sound, channel);
 		autoSpectrum spi = Sound_to_Spectrum (si.peek(), 1);
 		autoSpectrum spi_shifted = Spectrum_shiftFrequencies (spi.peek(), frequencyShift, samplingFrequency / 2, 30);
 		autoSound thee = Spectrum_to_Sound (spi_shifted.peek());
diff --git a/dwtools/EditDistanceTable.cpp b/dwtools/EditDistanceTable.cpp
index c450750..a264091 100644
--- a/dwtools/EditDistanceTable.cpp
+++ b/dwtools/EditDistanceTable.cpp
@@ -1,6 +1,6 @@
 /* EditDistanceTable.c
  *
- * Copyright (C) 2012 David Weenink
+ * Copyright (C) 2012, 2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -60,8 +60,8 @@ Thing_implement (WarpingPath, Data, 0);
 WarpingPath WarpingPath_create (long length) {
 	try {
 		autoWarpingPath me = Thing_new (WarpingPath);
-		my d_path = NUMvector<structPairOfInteger> (1, length);
-		my _capacity = my d_pathLength = length;
+		my path = NUMvector<structPairOfInteger> (1, length);
+		my _capacity = my pathLength = length;
 		return me.transfer();
 	} catch (MelderError) {
 		Melder_throw ("WarpPath not created.");
@@ -70,15 +70,15 @@ WarpingPath WarpingPath_create (long length) {
 
 static void WarpingPath_reset (WarpingPath me) {
 	for (long i = 1; i <= my _capacity; i++) {
-		my d_path[i].x = my d_path[i].y = 0;
+		my path[i].x = my path[i].y = 0;
 	}
-	my d_pathLength = my _capacity;
+	my pathLength = my _capacity;
 }
 
 static void WarpingPath_getPath (WarpingPath me, short **psi, long iy, long ix) { // psi[0..nrows-1][0..ncols-1]
-	long index = my d_pathLength;
-	my d_path[index].x = ix;
-	my d_path[index].y = iy;
+	long index = my pathLength;
+	my path[index].x = ix;
+	my path[index].y = iy;
 	while (!(ix == 0 && iy == 0)) {
 		if (psi[iy][ix] == WARPING_fromLeft) {
 			ix--;
@@ -87,22 +87,22 @@ static void WarpingPath_getPath (WarpingPath me, short **psi, long iy, long ix)
 		} else { // WARPING_fromDiag
 			ix--; iy--;
 		}
-		my d_path[--index].x = ix;
-		my d_path[index].y = iy;
+		my path[--index].x = ix;
+		my path[index].y = iy;
 	}
 	if (index > 1) {
 		long k = 1;
-		for (long i = index; i <= my d_pathLength; i++) {
-			my d_path[k++] = my d_path[i];
-			my d_path[i].x = my d_path[i].y = 0;
+		for (long i = index; i <= my pathLength; i++) {
+			my path[k++] = my path[i];
+			my path[i].x = my path[i].y = 0;
 		}
-		my d_pathLength = k - 1;
+		my pathLength = k - 1;
 	}
 }
 
 static void WarpingPath_shiftPathByOne (WarpingPath me) {
-	for (long i = 1; i <= my d_pathLength; i++) {
-		(my d_path[i].x)++; (my d_path[i].y)++;
+	for (long i = 1; i <= my pathLength; i++) {
+		(my path[i].x)++; (my path[i].y)++;
 	}
 }
 
@@ -111,12 +111,12 @@ long WarpingPath_getColumnsFromRowIndex (WarpingPath me, long iy, long *ix1, lon
 		return 0;
 	}
 	*ix1 = 0; *ix2 = 0;
-	for (long i = 1; i <= my d_pathLength; i++) {
-		if (my d_path[i].y < iy) {
+	for (long i = 1; i <= my pathLength; i++) {
+		if (my path[i].y < iy) {
 			continue;
-		} else if (my d_path[i].y == iy) {
-			if (*ix1 == 0) *ix1 = my d_path[i].x;
-			*ix2 = my d_path[i].x;
+		} else if (my path[i].y == iy) {
+			if (*ix1 == 0) *ix1 = my path[i].x;
+			*ix2 = my path[i].x;
 		} else {
 			break;
 		}
@@ -129,12 +129,12 @@ long WarpingPath_getRowsFromColumnIndex (WarpingPath me, long ix, long *iy1, lon
 		return 0;
 	}
 	*iy1 = 0; *iy2 = 0;
-	for (long i = 1; i <= my d_pathLength; i++) {
-		if (my d_path[i].x < ix) {
+	for (long i = 1; i <= my pathLength; i++) {
+		if (my path[i].x < ix) {
 			continue;
-		} else if (my d_path[i].x == ix) {
-			if (*iy1 == 0) *iy1 = my d_path[i].y;
-			*iy2 = my d_path[i].y;
+		} else if (my path[i].x == ix) {
+			if (*iy1 == 0) *iy1 = my path[i].y;
+			*iy2 = my path[i].y;
 		} else {
 			break;
 		}
@@ -342,8 +342,8 @@ EditDistanceTable EditDistanceTable_create (Strings target, Strings source) {
 		for (long i = 1; i <= numberOfTargetSymbols; i++) {
 			my rowLabels[i + 1] = Melder_wcsdup (target -> strings[i]);
 		}
-		my d_warpingPath = WarpingPath_create (numberOfTargetSymbols + numberOfSourceSymbols + 1);
-		my d_editCostsTable = EditCostsTable_createDefault ();
+		my warpingPath = WarpingPath_create (numberOfTargetSymbols + numberOfSourceSymbols + 1);
+		my editCostsTable = EditCostsTable_createDefault ();
 		EditDistanceTable_findPath (me.peek(), 0);
 		return me.transfer();
 	} catch (MelderError) {
@@ -353,9 +353,9 @@ EditDistanceTable EditDistanceTable_create (Strings target, Strings source) {
 
 void EditDistanceTable_setEditCosts (EditDistanceTable me, EditCostsTable thee) {
 	try {
-		forget (my d_editCostsTable);
+		forget (my editCostsTable);
 		autoEditCostsTable ect = (EditCostsTable) Data_copy (thee);
-		my d_editCostsTable = ect.transfer();
+		my editCostsTable = ect.transfer();
 	} catch (MelderError) {
 		Melder_throw (me, ": edit costs not set.");
 	}
@@ -437,8 +437,8 @@ void EditDistanceTable_draw (EditDistanceTable me, Graphics graphics, int iforma
 	double maxTextWidth = getMaxRowLabelWidth (me, graphics, rowmin, rowmax);
 	double y = 1 + 0.1 * lineSpacing;
 	autoNUMmatrix<bool> onPath (1, my numberOfRows, 1, my numberOfColumns);
-	for (long i = 1; i <= my d_warpingPath -> d_pathLength; i++) {
-		structPairOfInteger poi = my d_warpingPath -> d_path[i];
+	for (long i = 1; i <= my warpingPath -> pathLength; i++) {
+		structPairOfInteger poi = my warpingPath -> path[i];
 		onPath[poi.y] [poi.x] = true;
 	}
 
@@ -486,12 +486,12 @@ void EditDistanceTable_draw (EditDistanceTable me, Graphics graphics, int iforma
 
 void EditDistanceTable_drawEditOperations (EditDistanceTable me, Graphics graphics) {
 	const wchar_t *oinsertion = L"i", *insertion = L"*", *odeletion = L"d", *deletion = L"*", *osubstitution = L"s", *oequal = L"";
-	Graphics_setWindow (graphics, 0.5, my d_warpingPath -> d_pathLength - 0.5, 0, 1); // pathLength-1 symbols
+	Graphics_setWindow (graphics, 0.5, my warpingPath -> pathLength - 0.5, 0, 1); // pathLength-1 symbols
 	double lineSpacing = getLineSpacing (graphics);
 	double ytarget = 1 - lineSpacing, ysource = ytarget - 2 * lineSpacing, yoper = ysource - lineSpacing;
 	Graphics_setTextAlignment (graphics, Graphics_CENTRE, Graphics_BOTTOM);
-	for (long i = 2; i <= my d_warpingPath -> d_pathLength; i++) {
-		structPairOfInteger p = my d_warpingPath -> d_path[i], p1 = my d_warpingPath -> d_path[i - 1];
+	for (long i = 2; i <= my warpingPath -> pathLength; i++) {
+		structPairOfInteger p = my warpingPath -> path[i], p1 = my warpingPath -> path[i - 1];
 		double x = i - 1;
 		if (p.x == p1.x) { // insertion
 			Graphics_text (graphics, x, ytarget, my rowLabels[p.y]);
@@ -511,7 +511,7 @@ void EditDistanceTable_drawEditOperations (EditDistanceTable me, Graphics graphi
 }
 
 void EditDistanceTable_setDefaultCosts (EditDistanceTable me, double insertionCosts, double deletionCosts, double substitutionCosts) {
-	EditCostsTable_setDefaultCosts (my d_editCostsTable, insertionCosts, deletionCosts, substitutionCosts);
+	EditCostsTable_setDefaultCosts (my editCostsTable, insertionCosts, deletionCosts, substitutionCosts);
 	EditDistanceTable_findPath (me, 0);
 }
 
@@ -533,19 +533,19 @@ void EditDistanceTable_findPath (EditDistanceTable me, TableOfReal *directions)
 		autoNUMmatrix<double> delta (0, numberOfTargets, 0, numberOfSources);
 
 		for (long j = 1; j <= numberOfSources; j++) {
-			delta[0][j] = delta[0][j - 1] + EditCostsTable_getDeletionCost (my d_editCostsTable, my columnLabels[j+1]);
+			delta[0][j] = delta[0][j - 1] + EditCostsTable_getDeletionCost (my editCostsTable, my columnLabels[j+1]);
 			psi[0][j] = WARPING_fromLeft;
 		}
 		for (long i = 1; i <= numberOfTargets; i++) {
-			delta[i][0] = delta[i - 1][0] + EditCostsTable_getInsertionCost (my d_editCostsTable, my rowLabels[i+1]);
+			delta[i][0] = delta[i - 1][0] + EditCostsTable_getInsertionCost (my editCostsTable, my rowLabels[i+1]);
 			psi[i][0] = WARPING_fromBelow;
 		}
 		for (long j = 1; j <= numberOfSources; j++) {
 			for (long i = 1; i <= numberOfTargets; i++) {
 				// the substitution, deletion and insertion costs.
-				double left = delta[i][j - 1] + EditCostsTable_getInsertionCost (my d_editCostsTable, my rowLabels[i+1]);
-				double bottom = delta[i - 1][j] + EditCostsTable_getDeletionCost (my d_editCostsTable, my columnLabels[j+1]);
-				double mindist = delta[i - 1][j - 1] + EditCostsTable_getSubstitutionCost (my d_editCostsTable, my rowLabels[i+1], my columnLabels[j+1]); // diag
+				double left = delta[i][j - 1] + EditCostsTable_getInsertionCost (my editCostsTable, my rowLabels[i+1]);
+				double bottom = delta[i - 1][j] + EditCostsTable_getDeletionCost (my editCostsTable, my columnLabels[j+1]);
+				double mindist = delta[i - 1][j - 1] + EditCostsTable_getSubstitutionCost (my editCostsTable, my rowLabels[i+1], my columnLabels[j+1]); // diag
 				psi[i][j] = WARPING_fromDiag;
 				if (bottom < mindist) {
 					mindist = bottom;
@@ -561,11 +561,11 @@ void EditDistanceTable_findPath (EditDistanceTable me, TableOfReal *directions)
 		// find minimum distance in last column
 		long iy = numberOfTargets, ix = numberOfSources;
 
-		WarpingPath_reset (my d_warpingPath);
+		WarpingPath_reset (my warpingPath);
 
-		WarpingPath_getPath (my d_warpingPath, psi.peek(), iy, ix);
+		WarpingPath_getPath (my warpingPath, psi.peek(), iy, ix);
 
-		WarpingPath_shiftPathByOne (my d_warpingPath);
+		WarpingPath_shiftPathByOne (my warpingPath);
 
 		for (long i = 0; i <= numberOfTargets; i++) {
 			for (long j = 0; j <= numberOfSources; j++) {
diff --git a/dwtools/EditDistanceTable_def.h b/dwtools/EditDistanceTable_def.h
index aa4291c..139977e 100644
--- a/dwtools/EditDistanceTable_def.h
+++ b/dwtools/EditDistanceTable_def.h
@@ -1,6 +1,6 @@
 /* EditDistanceTable_def.h
  *
- * Copyright (C) 2012 David Weenink
+ * Copyright (C) 2012,2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,8 +31,8 @@ oo_END_STRUCT (PairOfInteger)
 #define ooSTRUCT WarpingPath
 oo_DEFINE_CLASS (WarpingPath, Data)
 	oo_LONG (_capacity)
-	oo_LONG (d_pathLength)
-	oo_STRUCT_VECTOR (PairOfInteger, d_path, d_pathLength)
+	oo_LONG (pathLength)
+	oo_STRUCT_VECTOR (PairOfInteger, path, pathLength)
 oo_END_CLASS (WarpingPath)
 #undef ooSTRUCT
 
@@ -50,9 +50,9 @@ oo_END_CLASS (EditCostsTable)
 
 #define ooSTRUCT EditDistanceTable
 	oo_DEFINE_CLASS (EditDistanceTable, TableOfReal)
-	oo_OBJECT (WarpingPath, 0, d_warpingPath)
+	oo_OBJECT (WarpingPath, 0, warpingPath)
 	#if oo_DECLARING
-		oo_OBJECT (EditCostsTable, 0, d_editCostsTable)
+		oo_OBJECT (EditCostsTable, 0, editCostsTable)
 		// overridden methods:
 			virtual void v_info ();
 	#endif
diff --git a/dwtools/GaussianMixture.cpp b/dwtools/GaussianMixture.cpp
index 8f29b4c..e115159 100644
--- a/dwtools/GaussianMixture.cpp
+++ b/dwtools/GaussianMixture.cpp
@@ -1,6 +1,6 @@
 /* GaussianMixture.cpp
  *
- * Copyright (C) 2011-2013 David Weenink
+ * Copyright (C) 2011-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -444,7 +444,7 @@ void GaussianMixture_getIntervalsAlongDirections (GaussianMixture me, long d1, l
 	if (d1 < 1 || d1 > my dimension || d2 < 1 || d2 > my dimension) {
 		Melder_throw ("Incorrect directions.");
 	}
-	autoSSCPs sscps = SSCPs_extractTwoDimensions ( (SSCPs) my covariances, d1, d2);
+	autoSSCPs sscps = SSCPs_extractTwoDimensions ((SSCPs) my covariances, d1, d2);
 	SSCPs_getEllipsesBoundingBoxCoordinates (sscps.peek(), -nsigmas, 0, xmin, xmax, ymin, ymax);
 }
 
@@ -881,7 +881,9 @@ int GaussianMixture_and_TableOfReal_getProbabilities (GaussianMixture me, TableO
 
 			for (long i = 1; i <= thy numberOfRows; i++) {
 				double dsq = NUMmahalanobisDistance_chi (his lowerCholesky, thy data[i], his centroid, his numberOfRows, my dimension);
-				p[i][ic] = exp (- 0.5 * (ln2pid + his lnd + dsq));
+				double prob = exp (- 0.5 * (ln2pid + his lnd + dsq));
+				prob = prob < 1e-300 ? 1e-300 : prob; // prevent p from being zero
+				p[i][ic] = prob;
 			}
 		}
 
diff --git a/dwtools/ICA.cpp b/dwtools/ICA.cpp
index ad2dfa1..15a2d4e 100644
--- a/dwtools/ICA.cpp
+++ b/dwtools/ICA.cpp
@@ -465,13 +465,13 @@ static void NUMcrossCorrelate_rows (double **x, long nrows, long icol1, long ico
 	The cross-correlation between channel i and channel j is defined as
 		sum(k=1..nsamples, (z[i][k] - mean[i])(z[j][k + tau] - mean[j]))*samplingTime
 */
-CrossCorrelationTable Sound_to_CrossCorrelationTable (Sound me, double startTime, double endTime, double lagTime) {
+CrossCorrelationTable Sound_to_CrossCorrelationTable (Sound me, double startTime, double endTime, double lagStep) {
 	try {
 		if (endTime <= startTime) {
 			startTime = my xmin;
 			endTime = my xmax;
 		}
-		long lag = lagTime / my dx;
+		long lag = lagStep / my dx;
 		long i1 = Sampled_xToNearestIndex (me, startTime);
 		if (i1 < 1) {
 			i1 = 1;
@@ -501,7 +501,7 @@ CrossCorrelationTable Sound_to_CrossCorrelationTable (Sound me, double startTime
  * Both sounds are treated as if their domain runs from 0 to duration.
  * Outside the chosen interval the sounds are assumed to be zero
  */
-CrossCorrelationTable Sounds_to_CrossCorrelationTable_combined (Sound me, Sound thee, double relativeStartTime, double relativeEndTime, double lagTime) {
+CrossCorrelationTable Sounds_to_CrossCorrelationTable_combined (Sound me, Sound thee, double relativeStartTime, double relativeEndTime, double lagStep) {
 	try {
 		if (my dx != thy dx) {
 			Melder_throw ("Sampling frequencies must be equal.");
@@ -510,7 +510,7 @@ CrossCorrelationTable Sounds_to_CrossCorrelationTable_combined (Sound me, Sound
 			relativeStartTime = my xmin;
 			relativeEndTime = my xmax;
 		}
-		long ndelta = lagTime / my dx, nchannels = my ny + thy ny;
+		long ndelta = lagStep / my dx, nchannels = my ny + thy ny;
 		long i1 = Sampled_xToNearestIndex (me, relativeStartTime);
 		if (i1 < 1) {
 			i1 = 1;
@@ -545,8 +545,8 @@ CrossCorrelationTable Sounds_to_CrossCorrelationTable_combined (Sound me, Sound
 
 Covariance Sound_to_Covariance_channels (Sound me, double startTime, double endTime) {
     try {
-        double lagTime = 0.0;
-        autoCrossCorrelationTable thee = Sound_to_CrossCorrelationTable (me, startTime, endTime, lagTime);
+        double lagStep = 0.0;
+        autoCrossCorrelationTable thee = Sound_to_CrossCorrelationTable (me, startTime, endTime, lagStep);
         autoCovariance him = Thing_new (Covariance);
         thy structCrossCorrelationTable :: v_copy (him.peek());
         return him.transfer();
@@ -555,21 +555,21 @@ Covariance Sound_to_Covariance_channels (Sound me, double startTime, double endT
     }
 }
 
-CrossCorrelationTables Sound_to_CrossCorrelationTables (Sound me, double startTime, double endTime, double lagTime, long ncovars) {
+CrossCorrelationTables Sound_to_CrossCorrelationTables (Sound me, double startTime, double endTime, double lagStep, long ncovars) {
 	try {
-		if (lagTime < my dx) {
-			lagTime = my dx;
-		}
-		if (startTime + ncovars * lagTime >= endTime) {
-			Melder_throw ("Lag time too large.");
+		if (lagStep < my dx) {
+			lagStep = my dx;
 		}
 		if (endTime <= startTime) {
 			startTime = my xmin;
 			endTime = my xmax;
 		}
+		if (startTime + ncovars * lagStep >= endTime) {
+			Melder_throw ("Lag time too large.");
+		}
 		autoCrossCorrelationTables thee = CrossCorrelationTables_create ();
 		for (long i = 1; i <= ncovars; i++) {
-			double lag = (i - 1) * lagTime;
+			double lag = (i - 1) * lagStep;
 			autoCrossCorrelationTable ct = Sound_to_CrossCorrelationTable (me, startTime, endTime, lag);
 			Collection_addItem (thee.peek(), ct.transfer());
 		}
@@ -579,9 +579,9 @@ CrossCorrelationTables Sound_to_CrossCorrelationTables (Sound me, double startTi
 	}
 }
 
-Sound Sound_to_Sound_BSS (Sound me, double startTime, double endTime, long ncovars, double lagTime, long maxNumberOfIterations, double tol, int method) {
+Sound Sound_to_Sound_BSS (Sound me, double startTime, double endTime, long ncovars, double lagStep, long maxNumberOfIterations, double tol, int method) {
 	try {
-		autoMixingMatrix him = Sound_to_MixingMatrix (me, startTime, endTime, ncovars, lagTime, maxNumberOfIterations, tol, method);
+		autoMixingMatrix him = Sound_to_MixingMatrix (me, startTime, endTime, ncovars, lagStep, maxNumberOfIterations, tol, method);
 		autoSound thee = Sound_and_MixingMatrix_unmix (me, him.peek());
 		return thee.transfer();
 	} catch (MelderError) {
@@ -735,9 +735,9 @@ Sound Sound_and_MixingMatrix_unmix (Sound me, MixingMatrix thee) {
 	}
 }
 
-MixingMatrix Sound_to_MixingMatrix (Sound me, double startTime, double endTime, long ncovars, double lagTime, long maxNumberOfIterations, double tol, int method) {
+MixingMatrix Sound_to_MixingMatrix (Sound me, double startTime, double endTime, long ncovars, double lagStep, long maxNumberOfIterations, double tol, int method) {
 	try {
-		autoCrossCorrelationTables ccs = Sound_to_CrossCorrelationTables (me, startTime, endTime, lagTime, ncovars);
+		autoCrossCorrelationTables ccs = Sound_to_CrossCorrelationTables (me, startTime, endTime, lagStep, ncovars);
 		autoMixingMatrix thee = MixingMatrix_create (my ny, my ny);
 		MixingMatrix_and_CrossCorrelationTables_improveUnmixing (thee.peek(), ccs.peek(), maxNumberOfIterations, tol, method);
 		return thee.transfer();
@@ -746,9 +746,6 @@ MixingMatrix Sound_to_MixingMatrix (Sound me, double startTime, double endTime,
 	}
 }
 
-#undef your
-#define your ((MixingMatrix_Table) thy methods) ->
-
 MixingMatrix TableOfReal_to_MixingMatrix (TableOfReal me) {
 	try {
 		if (my numberOfColumns != my numberOfRows) {
diff --git a/dwtools/ICA.h b/dwtools/ICA.h
index 6aaa707..70b79d2 100644
--- a/dwtools/ICA.h
+++ b/dwtools/ICA.h
@@ -2,7 +2,7 @@
 #define _ICA_h_
 /* ICA.h
  *
- * Copyright (C) 2010-2012 David Weenink
+ * Copyright (C) 2010-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -78,9 +78,9 @@ Diagonalizer Diagonalizer_create (long dimension);
 Sound Sound_and_MixingMatrix_mix (Sound me, MixingMatrix thee);
 Sound Sound_and_MixingMatrix_unmix (Sound me, MixingMatrix thee);
 
-MixingMatrix Sound_to_MixingMatrix (Sound me, double startTime, double endTime, long ncovars, double lagTime, long maxNumberOfIterations, double delta_w, int method);
+MixingMatrix Sound_to_MixingMatrix (Sound me, double startTime, double endTime, long ncovars, double lagStep, long maxNumberOfIterations, double delta_w, int method);
 
-Sound Sound_to_Sound_BSS (Sound me, double startTime, double endTime, long ncovars, double lagTime, long maxNumberOfIterations, double delta_w, int method);
+Sound Sound_to_Sound_BSS (Sound me, double startTime, double endTime, long ncovars, double lagStep, long maxNumberOfIterations, double delta_w, int method);
 
 Sound Sound_whitenChannels (Sound me, double varianceFraction);
 Sound Sound_and_Covariance_whitenChannels (Sound me, Covariance thee, double varianceFraction);
@@ -107,15 +107,15 @@ MixingMatrix Diagonalizer_to_MixingMatrix (Diagonalizer me);
 	The cross-correlation between channel i and channel j is defined as
 		sum(k=1..nsamples; (z[i][k] - mean[i])(z[j][k + lag] - mean[j])) / (nsamples - 1).
 */
-CrossCorrelationTable Sound_to_CrossCorrelationTable (Sound me, double startTime, double endTime, double lagTime);
-CrossCorrelationTable Sounds_to_CrossCorrelationTable_combined (Sound me, Sound thee, double relativeStartTime, double relativeEndTime, double lagTime);
+CrossCorrelationTable Sound_to_CrossCorrelationTable (Sound me, double startTime, double endTime, double lagStep);
+CrossCorrelationTable Sounds_to_CrossCorrelationTable_combined (Sound me, Sound thee, double relativeStartTime, double relativeEndTime, double lagStep);
 
 // The covariance is the cross-correlation with lag 0.
 Covariance Sound_to_Covariance_channels (Sound me, double startTime, double endTime);
 /*
-	Determine a CrossCorrelationTable for lags (k-1)*lagTime, where k = 1...n.
+	Determine a CrossCorrelationTable for lags (k-1)*lagStep, where k = 1...n.
 */
-CrossCorrelationTables Sound_to_CrossCorrelationTables (Sound me, double startTime, double endTime, double lagTime, long n);
+CrossCorrelationTables Sound_to_CrossCorrelationTables (Sound me, double startTime, double endTime, double lagStep, long n);
 
 MixingMatrix TableOfReal_to_MixingMatrix (TableOfReal me);
 
diff --git a/dwtools/KlattGrid.cpp b/dwtools/KlattGrid.cpp
index c0bcc53..fd8fcb1 100644
--- a/dwtools/KlattGrid.cpp
+++ b/dwtools/KlattGrid.cpp
@@ -1,6 +1,6 @@
 /* KlattGrid.cpp
  *
- * Copyright (C) 2008-2011 David Weenink
+ * Copyright (C) 2008-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -63,6 +63,27 @@
 #include "oo_DESCRIPTION.h"
 #include "KlattGrid_def.h"
 
+/*
+ * A KlattGrid consists of a great many tiers that can be independently modified.
+ * 
+ * For any particular formant, the formant frequency tier and the formant bandwidth tier can only be added or removed jointly
+ * because they are part of a FormantGrid object. There will always be an equal number of formant frequency tiers and
+ * formant bandwidth tiers.
+ * 
+ * For parallel synthesis we also need, besides the frequency and bandwidth tier, an additional amplitude tier for each formant.
+ * It is not necessary that there are an equal number of formant frequency tiers (nf) and amplitude tiers (na).
+ * During parallel synthesis we simply synthesize with min(nf,na) number of formants.
+ * These numbers nf and na can get out of sync because of the following (add, remove, replace) actions:
+ * 	- we replace a FormantGrid that has not the same number of tiers as the corresponding number of amplitude tiers
+ * 	- we remove/add a formant tier and a bandwidth tier together and not the corresponding amplitude tier
+ * 	- we remove/add an amplitude tier and not the corresponding formant&bandwidth tiers
+ * 
+ * As of 20130113 the KlattGrid_addFormant (/remove) which also added automatically an amplitude tier has been split into two explicit actions
+ *	KlattGrid_addFormantFrequencyAndBandwidth (/remove)
+ *	KlattGrid_addFormantAmplitudeTier (/remove)
+ * 
+ */
+
 // Prototypes
 
 PointProcess PitchTier_to_PointProcess_flutter (PitchTier pitch, RealTier flutter, double maximumPeriod);
@@ -246,16 +267,21 @@ static RealTier RealTier_updateWithDelta (RealTier me, RealTier delta, Phonation
 }
 
 static bool FormantGrid_isFormantDefined (FormantGrid me, long iformant) {
+	// formant and bandwidth are always in sync
 	RealTier ftier = (RealTier) my formants -> item[iformant];
 	RealTier btier = (RealTier) my bandwidths -> item[iformant];
 	return ftier -> points -> size != 0 and btier -> points -> size != 0;
 }
 
 static bool FormantGrid_Intensities_isFormantDefined (FormantGrid me, Ordered thee, long iformant) {
-	RealTier ftier = (RealTier) my formants -> item[iformant];
-	RealTier btier = (RealTier) my bandwidths -> item[iformant];
-	RealTier atier = (RealTier) thy item[iformant];
-	return ftier -> points -> size != 0 and btier -> points -> size != 0 and atier -> points -> size != 0;
+	bool exists = false;
+	if (iformant <= my formants -> size && iformant <= my bandwidths -> size && iformant <= thy size) {
+		RealTier ftier = (RealTier) my formants -> item[iformant];
+		RealTier btier = (RealTier) my bandwidths -> item[iformant];
+		RealTier atier = (RealTier) thy item[iformant];
+		exists = ftier -> points -> size != 0 and btier -> points -> size != 0 and atier -> points -> size != 0;
+	}
+	return exists;
 }
 
 static void check_formants (long numberOfFormants, long *ifb, long *ife) {
@@ -1525,10 +1551,8 @@ static Sound Sound_VocalTractGrid_CouplingGrid_filter_cascade (Sound me, VocalTr
 			if (oral_formant_warning) {
 				MelderString_append (&warning, L"\tOral formants: one or more are missing.\n");
 			}
-			MelderInfo_open();
-            MelderInfo_writeLine (L"Warning:");
-			MelderInfo_writeLine (warning.string);
-            MelderInfo_close();
+            Melder_print (L"\nWarning:\n");
+			Melder_print (warning.string);
 		}
 		return him.transfer();
 	} catch (MelderError) {
@@ -2430,7 +2454,7 @@ double KlattGrid_getAmplitudeAtTime (KlattGrid me, int formantType, long iforman
 void KlattGrid_addAmplitudePoint (KlattGrid me, int formantType, long iformant, double t, double value) {
 	Ordered *ordered = KlattGrid_getAddressOfAmplitudes (me, formantType);
 	if (iformant < 0 || iformant > (*ordered) -> size) {
-		Melder_throw (L"Formant does not exist.");
+		Melder_throw (L"Formant amplitude tier ", Melder_integer (iformant), "does not exist.");
 	}
 	RealTier_addPoint ( (RealTier) (*ordered) -> item[iformant], t, value);
 }
@@ -2447,7 +2471,7 @@ IntensityTier KlattGrid_extractAmplitudeTier (KlattGrid me, int formantType, lon
 	try {
 		Ordered *ordered = KlattGrid_getAddressOfAmplitudes (me, formantType);
 		if (iformant < 0 || iformant > (*ordered) ->size) {
-			Melder_throw ("Formant does not exist.");
+			Melder_throw ("Formant amplitude tier ", Melder_integer (iformant), " does not exist.");
 		}
 		autoIntensityTier thee = Data_copy ( (IntensityTier) (*ordered) -> item[iformant]);
 		return thee.transfer();
@@ -2463,7 +2487,7 @@ void KlattGrid_replaceAmplitudeTier (KlattGrid me, int formantType, long iforman
 		}
 		Ordered *ordered = KlattGrid_getAddressOfAmplitudes (me, formantType);
 		if (iformant < 0 || iformant > (*ordered) -> size) {
-			Melder_throw ("Formant does not exist.");
+			Melder_throw ("Formant amplitude tier ", Melder_integer (iformant)," does not exist.");
 		}
 		autoIntensityTier any = Data_copy (thee);
 		forget ( ( (Thing *) (*ordered) -> item) [iformant]);
@@ -2497,6 +2521,39 @@ void KlattGrid_replaceFormantGrid (KlattGrid me, int formantType, FormantGrid th
 	}
 }
 
+void KlattGrid_addFormantAmplitudeTier (KlattGrid me, int formantType, long position) {
+	try {
+		if (formantType == KlattGrid_NASAL_ANTIFORMANTS || formantType == KlattGrid_TRACHEAL_ANTIFORMANTS || formantType == KlattGrid_DELTA_FORMANTS) {
+			Melder_throw (L"Cannot add amplitude tier to this formant type.");
+		}
+		Ordered *ordered = KlattGrid_getAddressOfAmplitudes (me, formantType);
+		long noa = (*ordered) -> size;
+		if (position > noa || position < 1) {
+			position = noa + 1;
+		}
+		autoIntensityTier it = IntensityTier_create (my xmin, my xmax);
+		Ordered_addItemPos ( (*ordered), it.transfer(), position);
+	} catch (MelderError) {
+		Melder_throw (me, ": no formant amplitude tier added.");
+	}
+}
+
+void KlattGrid_removeFormantAmplitudeTier (KlattGrid me, int formantType, long position) {
+	try {
+		if (formantType == KlattGrid_NASAL_ANTIFORMANTS || formantType == KlattGrid_TRACHEAL_ANTIFORMANTS || formantType == KlattGrid_DELTA_FORMANTS) {
+			Melder_throw (L"Cannot remove amplitude tier from this formant type.");
+		}
+		Ordered *ordered = KlattGrid_getAddressOfAmplitudes (me, formantType);
+		if (position > 0 && position <= (*ordered) -> size) {
+			Collection_removeItem (*ordered, position);
+		}
+	} catch (MelderError) {
+		Melder_throw (me, ": no formant amplitude tier removed.");
+	}
+}
+
+// The following two routines are deprecated.
+// We do this intwo separate steps now
 void KlattGrid_addFormant (KlattGrid me, int formantType, long position) {
 	try {
 		FormantGrid *fg =  KlattGrid_getAddressOfFormantGrid (me, formantType);
@@ -2534,25 +2591,36 @@ void KlattGrid_addFormant (KlattGrid me, int formantType, long position) {
 
 void KlattGrid_removeFormant (KlattGrid me, int formantType, long position) {
 	FormantGrid *fg =  KlattGrid_getAddressOfFormantGrid (me, formantType);
-
-	if (position < 1 || position > (*fg) -> formants -> size) {
-		return;
-	}
-	FormantGrid_removeFormantAndBandwidthTiers (*fg, position);
+	long nof = (*fg) -> formants -> size;
 	if (formantType == KlattGrid_NASAL_ANTIFORMANTS || formantType == KlattGrid_TRACHEAL_ANTIFORMANTS ||
-	        formantType == KlattGrid_DELTA_FORMANTS) {
-		return;    // Done, no amplitudes
+        formantType == KlattGrid_DELTA_FORMANTS) {
+		if (position < 1 || position > nof) {
+			return;
+		}
+		FormantGrid_removeFormantAndBandwidthTiers (*fg, position);
+	} else { 
+		// oral & nasal & tracheal formants can have amplitudes
+		// only remove a formant and its amplitude tier if number of formants and amplitudes are the same
+		Ordered *ordered = KlattGrid_getAddressOfAmplitudes (me, formantType);
+		long noa = (*ordered) -> size;
+		if (position < 1 || position > nof || position > noa) {
+			if (nof != noa) {
+				Melder_warning ("The number of formant tiers (", Melder_integer (nof), ") and the number of amplitude tiers (", 
+					Melder_integer (noa), ") don't match. Nothing removed.");
+			}
+			return;
+		}
+		FormantGrid_removeFormantAndBandwidthTiers (*fg, position);
+		Collection_removeItem (*ordered, position);
 	}
-	Ordered *ordered = KlattGrid_getAddressOfAmplitudes (me, formantType);
-	Collection_removeItem (*ordered, position);
 }
 
-void KlattGrid_addFormantAndBandwidthTier (KlattGrid me, int formantType, long position) {
+void KlattGrid_addFormantFrequencyAndBandwidthTiers (KlattGrid me, int formantType, long position) {
 	FormantGrid *fg =  KlattGrid_getAddressOfFormantGrid (me, formantType);
 	FormantGrid_addFormantAndBandwidthTiers (*fg, position);
 }
 
-void KlattGrid_removeFormantAndBandwidthTier (KlattGrid me, int formantType, long position) {
+void KlattGrid_removeFormantFrequencyAndBandwidthTiers (KlattGrid me, int formantType, long position) {
 	FormantGrid *fg =  KlattGrid_getAddressOfFormantGrid (me, formantType);
 	FormantGrid_removeFormantAndBandwidthTiers (*fg, position);
 }
diff --git a/dwtools/KlattGrid.h b/dwtools/KlattGrid.h
index 738c68a..b5b3de6 100644
--- a/dwtools/KlattGrid.h
+++ b/dwtools/KlattGrid.h
@@ -2,7 +2,7 @@
 #define _KlattGrid_h_
 /* KlattGrid.h
  *
- * Copyright (C) 2008-2011 David Weenink
+ * Copyright (C) 2008-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@
 
 /*
  * djmw 20080917 Initial version
- * djmw 20110306 Latest modification
+ * djmw 20140113 Latest modification
  */
 
 #include "Collection.h"
@@ -212,9 +212,12 @@ void KlattGrid_addFormant (KlattGrid me,int formantType, long position);
 void KlattGrid_removeFormant (KlattGrid me,int formantType, long position);
 
 // add/remove frequency + bandwidth tiers
-void KlattGrid_addFormantAndBandwidthTier (KlattGrid me, int formantType, long position);
-void KlattGrid_removeFormantAndBandwidthTier (KlattGrid me, int formantType, long position);
+void KlattGrid_addFormantFrequencyAndBandwidthTiers (KlattGrid me, int formantType, long position);
+void KlattGrid_removeFormantFrequencyAndBandwidthTiers (KlattGrid me, int formantType, long position);
 
+
+void KlattGrid_addFormantAmplitudeTier (KlattGrid me, int formantType, long position);
+void KlattGrid_removeFormantAmplitudeTier (KlattGrid me, int formantType, long position);
 /***************** KlattGrid & Sound *************************************/
 
 // reset PlayOptions to defaults
diff --git a/dwtools/Ltas_extensions.cpp b/dwtools/Ltas_extensions.cpp
index e0df589..e2c1ef8 100644
--- a/dwtools/Ltas_extensions.cpp
+++ b/dwtools/Ltas_extensions.cpp
@@ -1,6 +1,6 @@
 /* Ltas_extensions.cpp
  *
- * Copyright (C) 2012-2013 David Weenink
+ * Copyright (C) 2012-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/dwtools/Ltas_extensions.h b/dwtools/Ltas_extensions.h
index 2df5484..f9d6173 100644
--- a/dwtools/Ltas_extensions.h
+++ b/dwtools/Ltas_extensions.h
@@ -2,7 +2,7 @@
 #define _Ltas_extensions_h_
 /* Ltas_extensions.h
  *
- * Copyright (C) 2012 David Weenink
+ * Copyright (C) 2012-2013 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/dwtools/MDS.cpp b/dwtools/MDS.cpp
index 7b8ea08..d85595d 100644
--- a/dwtools/MDS.cpp
+++ b/dwtools/MDS.cpp
@@ -1,6 +1,6 @@
 /* MDS.cpp
  *
- * Copyright (C) 1993-2013 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -2827,9 +2827,9 @@ void Dissimilarities_Configuration_indscal (Dissimilarities dissims, Configurati
 	try {
 		*out1 = 0; *out2 = 0;
 		autoDistances distances = Dissimilarities_Configuration_monotoneRegression (dissims, conf, tiesProcessing);
-		auto Salience weights = Distances_Configuration_to_Salience (distances.peek(), conf, normalizeScalarProducts);
+		autoSalience weights = Distances_Configuration_to_Salience (distances.peek(), conf, normalizeScalarProducts);
 		double vaf;
-		Dissimilarities_Configuration_Salience_indscal (dissims, conf, weights, tiesProcessing, normalizeScalarProducts,
+		Dissimilarities_Configuration_Salience_indscal (dissims, conf, weights.peek(), tiesProcessing, normalizeScalarProducts,
 		        tolerance, numberOfIterations, showProgress, out1, out2, &vaf);
 	} catch (MelderError) {
 		Melder_throw ("No indscal performed.");
diff --git a/dwtools/Makefile b/dwtools/Makefile
index 60b39a3..7ae8089 100644
--- a/dwtools/Makefile
+++ b/dwtools/Makefile
@@ -1,6 +1,6 @@
 # Makefile of the library "dwtools"
 # David Weenink, 22 February 2010
-# djmw 20130826 Latest modification
+# djmw 20140409 Latest modification
 
 include ../makefile.defs
 
@@ -13,7 +13,7 @@ OBJECTS = Activation.o AffineTransform.o \
 	ClassificationTable.o Confusion.o \
 	Configuration.o ContingencyTable.o \
 	Configuration_AffineTransform.o \
-	Configuration_and_Procrustes.o Distance.o \
+	Configuration_and_Procrustes.o  DataModeler.o Distance.o \
 	DTW.o DTW_and_TextGrid.o \
 	Discriminant.o  Discriminant_Pattern_Categories.o \
 	EditDistanceTable.o EEG_extensions.o \
@@ -28,7 +28,7 @@ OBJECTS = Activation.o AffineTransform.o \
 	KlattGrid.o KlattGridEditors.o KlattTable.o \
 	Ltas_extensions.o \
 	MelFilter_and_MFCC.o MFCC.o \
-	manual_dwtools.o manual_BSS.o manual_HMM.o \
+	manual_DataModeler.o manual_dwtools.o manual_BSS.o manual_HMM.o \
 	manual_KlattGrid.o manual_MDS.o manual_Permutation.o \
 	Minimizers.o \
 	Matrix_extensions.o \
@@ -49,7 +49,8 @@ OBJECTS = Activation.o AffineTransform.o \
 	TableOfReal_and_Permutation.o \
 	TextGrid_extensions.o \
 	VowelEditor.o \
-	praat_MDS_init.o praat_BSS_init.o praat_HMM_init.o praat_KlattGrid_init.o praat_David_init.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
 
 .PHONY: all clean
 
diff --git a/dwtools/Polynomial.cpp b/dwtools/Polynomial.cpp
index f84ec48..d107370 100644
--- a/dwtools/Polynomial.cpp
+++ b/dwtools/Polynomial.cpp
@@ -1,6 +1,6 @@
 /* Polynomial.cpp
  *
- * Copyright (C) 1993-2013 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -54,9 +54,6 @@
 #include "oo_DESCRIPTION.h"
 #include "Polynomial_def.h"
 
-#undef our
-#define our ((Polynomial_Table) my methods) ->
-
 #define MAX(m,n) ((m) > (n) ? (m) : (n))
 #define MIN(m,n) ((m) < (n) ? (m) : (n))
 
@@ -415,7 +412,7 @@ static void Graphics_polyline_clipTopBottom (Graphics g, double *x, double *y, l
 			if (y1 < ymin && y2 > ymin) {
 				// Line enters from below: start new segment. Save start values
 
-				xb = x[i - 1]; yb = y[i - 1]; index = i - 1; // David, dit klopt toch niet? xb is tweemaal gedefinieerd, en hier verdwijnt een toekenning; Klopt, nu niet meer (was overigens geen probleem vanwege nieuwe toekenning!)
+				xb = x[i - 1]; yb = y[i - 1]; index = i - 1;
 				y[i - 1] = ymin; x[i - 1] = xcros_min;
 			}
 			if (y1 < ymax && y2 > ymax) {
@@ -1686,9 +1683,6 @@ void Spline_drawKnots (I, Graphics g, double xmin, double xmax, double ymin, dou
 	}
 }
 
-#undef our
-#define our ((Spline_Table) my methods) ->
-
 long Spline_getOrder (I) {
 	iam (Spline);
 	return my v_getOrder ();
diff --git a/dwtools/SSCP.cpp b/dwtools/SSCP.cpp
index aab4db1..88e8ae9 100644
--- a/dwtools/SSCP.cpp
+++ b/dwtools/SSCP.cpp
@@ -1,6 +1,6 @@
 /* SSCP.cpp
  *
- * Copyright (C) 1993-2012 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -104,7 +104,7 @@ void structSSCP :: v_info () {
 	Calculate scale factor by which sqrt(eigenvalue) has to
 	be multiplied to obtain the length of an ellipse axis.
 */
-static double ellipseScalefactor (I, double scale, int confidence) {
+double SSCP_getEllipseScalefactor (I, double scale, int confidence) {
 	iam (SSCP);
 	long n = SSCP_getNumberOfObservations (me);
 
@@ -128,7 +128,7 @@ static double ellipseScalefactor (I, double scale, int confidence) {
 static void getEllipseBoundingBoxCoordinates (SSCP me, double scale, int confidence,
         double *xmin, double *xmax, double *ymin, double *ymax) {
 	double a, b, cs, sn, width, height;
-	double lscale = ellipseScalefactor (me, scale, confidence);
+	double lscale = SSCP_getEllipseScalefactor (me, scale, confidence);
 
 	NUMeigencmp22 (my data[1][1], my data[1][2], my data[2][2], &a, &b, &cs, &sn);
 	NUMgetEllipseBoundingBox (sqrt (a), sqrt (b), cs, & width, & height);
@@ -198,6 +198,45 @@ SSCPs SSCPs_extractTwoDimensions (SSCPs me, long d1, long d2) {
 	}
 }
 
+void SSCP_drawTwoDimensionalEllipse_inside  (SSCP me, Graphics g, double scale, wchar_t * label, int fontSize) {
+	try {
+		long nsteps = 100;
+		autoNUMvector<double> x (0L, nsteps);
+		autoNUMvector<double> y (0L, nsteps);
+		// Get principal axes and orientation for the ellipse by performing the
+		// eigen decomposition of a symmetric 2-by-2 matrix.
+		// Principal axes are a and b with eigenvector/orientation (cs, sn).
+
+		double a, b, cs, sn;
+		NUMeigencmp22 (my data[1][1], my data[1][2], my data[2][2], &a, &b, &cs, &sn);
+		// 1. Take sqrt to get units of 'std_dev'
+
+		a = scale * sqrt (a) / 2;
+		b = scale * sqrt (b) / 2;
+		x[nsteps] = x[0] = my centroid[1] + cs * a;
+		y[nsteps] = y[0] = my centroid[2] + sn * a;
+		double angle = 0;
+		double angle_inc = NUM2pi / nsteps;
+		for (long i = 1; i < nsteps; i++, angle += angle_inc) {
+			double xc = a * cos (angle);
+			double yc = b * sin (angle);
+			double xt = xc * cs - yc * sn;
+			y[i] = my centroid[2] + xc * sn + yc * cs;
+			x[i] = my centroid[1] + xt;
+		}
+		Graphics_polyline (g, nsteps + 1, x.peek(), y.peek());
+		if (label != NULL) {
+			int oldFontSize = Graphics_inqFontSize (g);
+			Graphics_setFontSize (g, fontSize);
+			Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
+			Graphics_text (g, my centroid[1], my centroid[2], label);
+			Graphics_setFontSize (g, oldFontSize);
+		}
+	} catch (MelderError) {
+		//
+	}
+}
+
 static void _SSCP_drawTwoDimensionalEllipse (SSCP me, Graphics g, double scale, int fontSize) {
 	long nsteps = 100;
 	wchar_t *name;
@@ -315,7 +354,7 @@ double SSCP_getConcentrationEllipseArea (I, double scale, int confidence, long d
 		Melder_throw ("Incorrect axes.");
 	}
 	autoSSCP thee = _SSCP_extractTwoDimensions (me, d1, d2);
-	scale = ellipseScalefactor (thee.peek(), scale, confidence);
+	scale = SSCP_getEllipseScalefactor (thee.peek(), scale, confidence);
 	if (scale < 0) {
 		Melder_throw ("Invalid scale factor.");
 	}
@@ -372,7 +411,7 @@ void SSCP_drawConcentrationEllipse (SSCP me, Graphics g, double scale,
 	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
 	Graphics_setInner (g);
 
-	scale = ellipseScalefactor (thee.peek(), scale, confidence);
+	scale = SSCP_getEllipseScalefactor (thee.peek(), scale, confidence);
 	if (scale < 0) {
 		Melder_throw ("Invalid scale factor.");
 	}
@@ -524,9 +563,11 @@ SSCP TableOfReal_to_SSCP (I, long rowb, long rowe, long colb, long cole) {
 				thy data[i][j] = thy data[j][i] = t;
 			}
 		}
-
-		NUMstrings_copyElements (TOVEC (my columnLabels[colb]), thy columnLabels, 1, n);
-		NUMstrings_copyElements (thy columnLabels, thy rowLabels, 1, n);
+		for (long j = 1; j <= n; j++) {
+			wchar_t *label = my columnLabels[colb + j - 1];
+			TableOfReal_setColumnLabel (thee.peek(), j, label);
+			TableOfReal_setRowLabel (thee.peek(), j, label);
+		}
 		return thee.transfer();
 	} catch (MelderError) {
 		Melder_throw (me, ": SSCP not created.");
@@ -1047,9 +1088,8 @@ void SSCPs_drawConcentrationEllipses (SSCPs me, Graphics g, double scale, int co
 
 
 	for (long i = 1; i <= thy size; i++) {
-		double lscale;
 		t = (SSCP) thy item[i];
-		lscale = ellipseScalefactor (t, scale, confidence);
+		double lscale = SSCP_getEllipseScalefactor (t, scale, confidence);
 		if (lscale < 0) {
 			continue;
 		}
diff --git a/dwtools/SSCP.h b/dwtools/SSCP.h
index 31dc0f2..0c1c244 100644
--- a/dwtools/SSCP.h
+++ b/dwtools/SSCP.h
@@ -2,7 +2,7 @@
 #define _SSCP_h_
 /* SSCP.h
  *
- * Copyright (C) 1993-2011 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -43,6 +43,8 @@ void SSCP_init (I, long dimension, long storage);
 
 SSCP SSCP_create (long dimension);
 
+void SSCP_drawTwoDimensionalEllipse_inside  (SSCP me, Graphics g, double scale, wchar_t * label, int fontSize);
+double SSCP_getEllipseScalefactor (I, double scale, int confidence);
 void SSCP_drawConcentrationEllipse (SSCP me, Graphics g, double scale, int confidence,
 	long d1, long d2, double xmin, double xmax, double ymin, double ymax, int garnish);
 
@@ -198,6 +200,8 @@ void Covariances_equality (Collection me, int method, double *prob, double *chis
 
 SSCPs SSCPs_create ();
 
+SSCPs TableOfReal_to_SSCPs_byLabel (I);
+
 SSCP SSCPs_to_SSCP_sum (SSCPs me);
 /* Sum the sscp's and weigh each means with it's numberOfObservations. */
 
diff --git a/dwtools/Sound_extensions.cpp b/dwtools/Sound_extensions.cpp
index 0ca86f6..94576b3 100644
--- a/dwtools/Sound_extensions.cpp
+++ b/dwtools/Sound_extensions.cpp
@@ -69,6 +69,11 @@
 #include "Manipulation.h"
 #include "NUM2.h"
 
+#ifdef linux
+//#include <pulse/simple.h>
+//#include <pulse/error.h>
+#endif
+
 #define MAX_T  0.02000000001   /* Maximum interval between two voice pulses (otherwise voiceless). */
 
 static void PitchTier_modifyExcursionRange (PitchTier me, double tmin, double tmax, double multiplier, double fref_Hz) {
@@ -1984,7 +1989,7 @@ void Sound_drawWhere (Sound me, Graphics g, double tmin, double tmax, double min
 				Formula_run (channel, ix, & result);
 				if (result.result.numericResult) {
 					double x = Sampled_indexToX (me, ix);
-					Graphics_fillCircle_mm (g, x, my z [channel] [ix], 1.0);
+					Graphics_speckle (g, x, my z [channel] [ix]);
 				}
 			}
 		} else {
@@ -2272,4 +2277,5 @@ void Sound_playAsFrequencyShifted (Sound me, double shiftBy, double newSamplingF
 	}
 }
 
+
 /* End of file Sound_extensions.cpp */
diff --git a/dwtools/SpeechSynthesizer_and_TextGrid.cpp b/dwtools/SpeechSynthesizer_and_TextGrid.cpp
index 7290ca2..8b8d410 100644
--- a/dwtools/SpeechSynthesizer_and_TextGrid.cpp
+++ b/dwtools/SpeechSynthesizer_and_TextGrid.cpp
@@ -705,11 +705,11 @@ Table IntervalTiers_to_Table_textAlignmentment (IntervalTier target, IntervalTie
 			EditDistanceTable_setEditCosts (edit.peek(), costs);
 			EditDistanceTable_findPath (edit.peek(), NULL);
 		}
-		long pathLength = edit -> d_warpingPath -> d_pathLength;
+		long pathLength = edit -> warpingPath -> pathLength;
 		autoTable thee = Table_createWithColumnNames (pathLength - 1, L"targetInterval targetText targetStart targetEnd sourceInterval sourceText sourceStart sourceEnd operation");
 		for (long i = 2; i <= pathLength; i++) {
-			structPairOfInteger p = edit -> d_warpingPath -> d_path[i];
-			structPairOfInteger p1 = edit -> d_warpingPath -> d_path[i - 1];
+			structPairOfInteger p = edit -> warpingPath -> path[i];
+			structPairOfInteger p1 = edit -> warpingPath -> path[i - 1];
 			double targetStart = NUMundefined, targetEnd =  NUMundefined;
 			double sourceStart = NUMundefined, sourceEnd =  NUMundefined;
 			const wchar_t * targetText = L"", *sourceText = L"";
@@ -776,4 +776,4 @@ Table TextGrids_to_Table_textAlignmentment (TextGrid target, long ttier, TextGri
 	}
 }
 
-// End of file TextGrid_and_SpeechSynthesizer.cpp
\ No newline at end of file
+// End of file TextGrid_and_SpeechSynthesizer.cpp
diff --git a/dwtools/TableOfReal_and_Permutation.cpp b/dwtools/TableOfReal_and_Permutation.cpp
index 111f774..58cd661 100644
--- a/dwtools/TableOfReal_and_Permutation.cpp
+++ b/dwtools/TableOfReal_and_Permutation.cpp
@@ -1,6 +1,6 @@
 /* TableOfReal_and_Permutation.cpp
  *
- * Copyright (C) 2005-2011 David Weenink
+ * Copyright (C) 2005-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,6 +36,9 @@ TableOfReal TableOfReal_and_Permutation_permuteRows (I, Permutation thee) {
 		for (long i = 1; i <= thy numberOfElements; i++) {
 			TableOfReal_copyOneRowWithLabel (me, him.peek(), thy p[i], i);
 		}
+		for (long j = 1; j <= my numberOfColumns; j++) {
+			TableOfReal_setColumnLabel (him.peek(), j, my columnLabels[j]);
+		}
 		return him.transfer();
 	} catch (MelderError) {
 		Melder_throw (me, ": not permuted.");
diff --git a/dwtools/Table_extensions.cpp b/dwtools/Table_extensions.cpp
index c53998f..a9f2a77 100644
--- a/dwtools/Table_extensions.cpp
+++ b/dwtools/Table_extensions.cpp
@@ -1,6 +1,6 @@
 /* Table_extensions.cpp
 	 *
- * Copyright (C) 1997-2013 David Weenink
+ * Copyright (C) 1997-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,11 +25,13 @@
   djmw 20080125 Corrected mislabeling of vowels in the Peterson&Barney dataset according to Watrous
   djmw 20080508 Labeling back to original PB article.
   djmw 20110329 Table_get(Numeric|String)Value is now Table_get(Numeric|String)Value_Assert
+  djmw 20131219 Improved Table_scatterPlotWithConfidenceIntervals
 */
 /*	speaker type (m|w|c), sex(m|f), id, vowel_number, vowel_label
 	F0, F1, F2, F3
 */
 
+#include "Discriminant.h"
 #include "Formula.h"
 #include "GraphicsP.h"
 #include "Graphics_extensions.h"
@@ -38,8 +40,29 @@
 #include "NUM2.h"
 #include <ctype.h>
 #include "Strings_extensions.h"
+#include "SSCP.h"
 #include "Table_extensions.h"
 
+static bool Table_selectedColumnPartIsNumeric (Table me, long column, long *selectedRows, long numberOfSelectedRows) {
+	if (column < 1 || column > my numberOfColumns) return false;
+	for (long irow = 1; irow <= numberOfSelectedRows; irow++) {
+		if (! Table_isCellNumeric_ErrorFalse (me, selectedRows[irow], column)) return false;
+	}
+	return true;
+}
+
+// column and selectedRows are valid; *min & *max must be intialized
+static void Table_columnExtremesFromSelectedRows (Table me, long column, long *selectedRows, long numberOfSelectedRows, double *min, double *max) {
+	double cmin = 1e38, cmax = - cmin;
+	for (long irow = 1; irow <= numberOfSelectedRows; irow++) {
+		double val = Table_getNumericValue_Assert (me, selectedRows[irow], column);
+		if (val < cmin) { cmin = val; }
+		if (val > cmax) { cmax = val; }
+	}
+	*min = cmin;
+	*max = cmax;
+}
+
 /*
 The Peterson & Barney data were once (1991) obtained by me (djmw) as a compressed tar-file
 by anonymous ftp from ftp://linc.cis.upenn.edu/pub,
@@ -3146,96 +3169,159 @@ Table Table_createFromGanongData () {
 	}
 }
 
-void Table_scatterPlotWithConfidenceIntervals (Table me, Graphics g, long xcolumn, long ycolumn,
-        double xmin, double xmax, double ymin, double ymax, long xci_min, long xci_max,
-        long yci_min, long yci_max, double bar_mm, int garnish) {
-	long nrows = my rows -> size;
-	double x2min, x1max, y1max, y2min;
-	double bar = ceil (bar_mm * g -> resolution / 25.4);
-
-	// check validity of columns
-	if (xcolumn < 1 || xcolumn > nrows || ycolumn < 1 || ycolumn > nrows) {
-		return;
+static bool intervalsIntersect (double x1, double x2, double xmin, double xmax, double *xc1, double *xc2) {
+	if (x1 > x2) { 
+		double tmp = x1; x1 = x2; x2 = tmp;
 	}
-	if (labs (xci_min) > nrows || labs (xci_max) > nrows ||
-	        labs (yci_min) > nrows || labs (yci_max) > nrows) {
-		return;
+	if (xmin > xmax) {
+		double tmp = xmin; xmin = xmax; xmin = tmp;
 	}
-
-	if (xmin >= xmax &&
-	        Table_getExtrema (me, xci_min, &xmin, &x1max) &&
-	        Table_getExtrema (me, xci_max, &x2min, &xmax) &&
-	        xmin >= xmax) {
-		return;
+	*xc1 = x1; *xc2 = x2;
+	if (x2 <= xmin || x1 >= xmax) {
+		return false;
 	}
-
-	if (ymin >= ymax &&
-	        Table_getExtrema (me, yci_min, &ymin, &y1max) &&
-	        Table_getExtrema (me, yci_max, &y2min, &ymax) &&
-	        ymin >= ymax) {
-		return;
+	if (x1 < xmin) {
+		*xc1 = xmin;
 	}
+	if (x2 > xmax) {
+		*xc2 = xmax;
+	}
+	return true;
+}
 
-	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
-	Graphics_setInner (g);
-
-	for (long row = 1; row <= nrows; row++) {
-		double x  = Table_getNumericValue_Assert (me, row, xcolumn);
-		double y  = Table_getNumericValue_Assert (me, row, ycolumn);
-		double x1 = Table_getNumericValue_Assert (me, row, xci_min);
-		double x2 = Table_getNumericValue_Assert (me, row, xci_max);
-		double y1 = Table_getNumericValue_Assert (me, row, yci_min);
-		double y2 = Table_getNumericValue_Assert (me, row, yci_max);
-		double xo1, yo1, xo2, yo2;
-
-		if (xci_min > 0) {
-			if (NUMclipLineWithinRectangle (x1, y, x, y, xmin, ymin, xmax, ymax,
-			                                &xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
-			}
-			if (bar > 0 && NUMclipLineWithinRectangle (x1, y - bar / 2, x1, y + bar / 2,
-			        xmin, ymin, xmax, ymax,	&xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
+void Table_horizontalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
+	double ymin, double ymax, long xci_min, long xci_max, double bar_mm, int garnish, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		long nrows = my rows -> size;
+		if (xcolumn < 1 || xcolumn > nrows || ycolumn < 1 || ycolumn > nrows ||
+			(xci_min != 0 && xci_min > nrows) || (xci_max != 0 && xci_max > nrows)) {
+			return;
+		}
+		long numberOfSelectedRows = 0;
+		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfSelectedRows), 1);	
+		if (ymin >= ymax) {
+			Table_columnExtremesFromSelectedRows (me, ycolumn, selectedRows.peek(), numberOfSelectedRows, &ymin, &ymax);
+			if (ymin >= ymax) {
+				ymin -= 1; ymax += 1;
 			}
 		}
-		if (xci_max > 0) {
-			if (NUMclipLineWithinRectangle (x, y, x2, y, xmin, ymin, xmax, ymax,
-			                                &xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
+		double x1min, x1max;
+		if (xmin >= xmax) {
+			Table_columnExtremesFromSelectedRows (me, xcolumn, selectedRows.peek(), numberOfSelectedRows, &xmin, &xmax);
+			if (xci_min > 0) {
+				Table_columnExtremesFromSelectedRows (me, xci_min, selectedRows.peek(), numberOfSelectedRows, &x1min, &x1max);
+				xmin -= x1max;
 			}
-			if (bar > 0 && NUMclipLineWithinRectangle (x2, y - bar / 2, x2, y + bar / 2,
-			        xmin, ymin, xmax, ymax,	&xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
+			if (xci_max > 0) {
+				Table_columnExtremesFromSelectedRows (me, xci_max, selectedRows.peek(), numberOfSelectedRows, &x1min, &x1max);
+				xmax += x1max;
+			}
+			if (xmin >= xmax) {
+				xmin -= 1; xmax += 1;
 			}
 		}
-		if (yci_min > 0) {
-			if (NUMclipLineWithinRectangle (x, y1, x, y, xmin, ymin, xmax, ymax,
-			                                &xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
+		Graphics_setWindow (g, xmin, xmax, ymin, ymax);
+		Graphics_setInner (g);
+		double dy = Graphics_dyMMtoWC (g, bar_mm);
+		for (long row = 1; row <= numberOfSelectedRows; row++) {
+			double x  = Table_getNumericValue_Assert (me, selectedRows[row], xcolumn);
+			double y  = Table_getNumericValue_Assert (me, selectedRows[row], ycolumn);
+			double dx1 = xci_min > 0 ? Table_getNumericValue_Assert (me, selectedRows[row], xci_min) : 0;
+			double dx2 = xci_max > 0 ? Table_getNumericValue_Assert (me, selectedRows[row], xci_max) : 0;
+			double x1 = x - dx1, x2 = x + dx2, xc1, yc1, xc2, yc2;
+
+			if (x <= xmax && x >= xmin && y <= ymax && y >= ymin) {
+				// horizontal confidence interval
+				if (intervalsIntersect (x1, x2, xmin, xmax, &xc1, &xc2)) {
+					Graphics_line (g, xc1, y, xc2, y);
+					if (dy > 0 && intervalsIntersect (y - dy / 2, y + dy / 2, ymin, ymax, &yc1, &yc2)) {
+						if (xc1 >= xmin && dx1 > 0) {
+							Graphics_line (g, xc1, yc1, xc1, yc2);
+						}
+						if (xc2 <= xmax && dx2 > 0) {
+							Graphics_line (g, xc2, yc1, xc2, yc2);
+						}
+					}
+				}
 			}
-			if (bar > 0 && NUMclipLineWithinRectangle (x - bar / 2, y1, x + bar / 2, y1,
-			        xmin, ymin, xmax, ymax,	&xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
+		}
+		Graphics_unsetInner (g);
+
+		if (garnish) {
+			Graphics_drawInnerBox (g);
+			Graphics_marksLeft (g, 2, 1, 1, 0);
+			Graphics_marksBottom (g, 2, 1, 1, 0);
+		}
+	} catch (MelderError) {
+		//
+	}
+}
+
+void Table_verticalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
+	double ymin, double ymax, long yci_min, long yci_max, double bar_mm, int garnish, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		long nrows = my rows -> size;
+		if (xcolumn < 1 || xcolumn > nrows || ycolumn < 1 || ycolumn > nrows ||
+			(yci_min != 0 && yci_min > nrows) || (yci_max != 0 && yci_max > nrows)) {
+			return;
+		}
+		long numberOfSelectedRows = 0;
+		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfSelectedRows), 1);
+		if (xmin >= xmax) {
+			Table_columnExtremesFromSelectedRows (me, ycolumn, selectedRows.peek(), numberOfSelectedRows, &ymin, &ymax);
+			if (xmin >= xmax) {
+				xmin -= 1; xmax += 1;
 			}
 		}
-		if (yci_max > 0) {
-			if (NUMclipLineWithinRectangle (x, y, x, y2, xmin, ymin, xmax, ymax,
-			                                &xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
+		double y1min, y1max;
+		if (ymin >= ymax) {
+			Table_columnExtremesFromSelectedRows (me, ycolumn, selectedRows.peek(), numberOfSelectedRows, &ymin, &ymax);
+			if (yci_min > 0) {
+				Table_columnExtremesFromSelectedRows (me, yci_min, selectedRows.peek(), numberOfSelectedRows, &y1min, &y1max);
+				ymin -= y1max;
 			}
-			if (bar > 0 && NUMclipLineWithinRectangle (x - bar / 2, y2, x + bar / 2, y2,
-			        xmin, ymin, xmax, ymax,	&xo1, &yo1, &xo2, &yo2)) {
-				Graphics_line (g, xo1, yo1, xo2, yo2);
+			if (yci_max > 0) {
+				Table_columnExtremesFromSelectedRows (me, yci_max, selectedRows.peek(), numberOfSelectedRows, &y1min, &y1max);
+				ymax += y1max;
+			}
+			if (ymin >= ymax) {
+				ymin -= 1; ymax += 1;
 			}
 		}
-	}
+		Graphics_setWindow (g, xmin, xmax, ymin, ymax);
+		Graphics_setInner (g);
+		double dx = Graphics_dxMMtoWC (g, bar_mm);
+		for (long row = 1; row <= numberOfSelectedRows; row++) {
+			double x  = Table_getNumericValue_Assert (me, selectedRows[row], xcolumn);
+			double y  = Table_getNumericValue_Assert (me, selectedRows[row], ycolumn);
+			double dy1 = yci_min > 0 ? Table_getNumericValue_Assert (me, selectedRows[row], yci_min) : 0;
+			double dy2 = yci_max > 0 ? Table_getNumericValue_Assert (me, selectedRows[row], yci_max) : 0;
+			double y1 = y - dy1, y2 = y + dy2, xc1, yc1, xc2, yc2;
 
-	Graphics_unsetInner (g);
+			if (x <= xmax && x >= xmin && y <= ymax && y >= ymin) {
+				// vertical confidence interval
+				if (intervalsIntersect (y1, y2, ymin, ymax, &yc1, &yc2)) {
+					Graphics_line (g, x, yc1, x, yc2);
+					if (dx > 0 && intervalsIntersect (x - dx / 2, x + dx / 2, xmin, xmax, &xc1, &xc2)) {
+						if (yc1 >= ymin && dy1 > 0) {
+							Graphics_line (g, xc1, yc1, xc2, yc1);
+						}
+						if (yc2 <= ymax && dy2 > 0) {
+							Graphics_line (g, xc1, yc2, xc2, yc2);
+						}
+					}
+				}
+			}
+		}
+		Graphics_unsetInner (g);
 
-	if (garnish) {
-		Graphics_drawInnerBox (g);
-		Graphics_marksLeft (g, 2, 1, 1, 0);
-		Graphics_marksBottom (g, 2, 1, 1, 0);
+		if (garnish) {
+			Graphics_drawInnerBox (g);
+			Graphics_marksLeft (g, 2, 1, 1, 0);
+			Graphics_marksBottom (g, 2, 1, 1, 0);
+		}
+	} catch (MelderError) {
+		//
 	}
 }
 
@@ -3801,7 +3887,7 @@ void Table_normalProbabilityPlot (Table me, Graphics g, long column, long number
 		}
 		double mean, var;
 		NUMvector_avevar (data.peek(), numberOfData, &mean, &var);
-		double xmin = 100, xmax = -xmin, ymin = 1e38, ymax = -ymin, stdev = sqrt (var);
+		double xmin = 100, xmax = -xmin, ymin = 1e38, ymax = -ymin, stdev = sqrt (var / (numberOfData - 1));
 		if (numberOfSigmas != 0) {
 			xmin = -numberOfSigmas; 
 			xmax =  numberOfSigmas;
@@ -3978,6 +4064,69 @@ void Table_boxPlots (Table me, Graphics g, long dataColumn, long factorColumn, d
 	}
 }
 
+void Table_boxPlotsWhere (Table me, Graphics g, wchar_t *dataColumns_string, long factorColumn, double ymin, double ymax, int garnish, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		long numberOfSelectedColumns;
+		autoNUMvector<long> dataColumns (Table_getColumnIndicesFromColumnLabelString (me, dataColumns_string,  &numberOfSelectedColumns), 1);
+		if (factorColumn < 1 || factorColumn > my numberOfColumns) {
+			return;
+		}
+		Formula_compile (interpreter, me, formula, kFormula_EXPRESSION_TYPE_UNKNOWN, TRUE);
+		long numberOfData = my rows -> size;
+		autoStringsIndex si = Table_to_StringsIndex_column (me, factorColumn);
+		long numberOfLevels = si -> classes -> size;
+		if (ymin == ymax) {
+			ymin = 1e38, ymax = -ymin;
+			for (long icol = 1; icol <= numberOfSelectedColumns; icol++) {
+				double ymaxi = Table_getMaximum (me, dataColumns[icol]);
+				double ymini = Table_getMinimum (me, dataColumns[icol]);
+				ymax = ymaxi > ymax ? ymaxi : ymax;
+				ymin = ymini < ymin ? ymini : ymin;
+			}
+			if (ymax == ymin) {
+				ymax += 1; ymin -= 1;
+			}
+		}
+		Graphics_setWindow (g, 1 - 0.5, numberOfLevels + 0.5, ymin, ymax);
+		Graphics_setInner (g);
+		double boxWidth = 4, spaceBetweenBoxesInGroup = 1, barWidth = boxWidth / 3;
+		double spaceBetweenGroupsdiv2 = 3.0 / 2; 
+		double widthUnit = 1.0 / (numberOfSelectedColumns * boxWidth + (numberOfSelectedColumns - 1) * spaceBetweenBoxesInGroup + spaceBetweenGroupsdiv2 + spaceBetweenGroupsdiv2);
+		autoNUMvector<double> data (1, numberOfData);
+		for (long ilevel = 1; ilevel <= numberOfLevels; ilevel++) {
+			double xlevel = ilevel;
+			for (long icol = 1; icol <= numberOfSelectedColumns; icol++) {
+				long numberOfDataInLevelColumn = 0;
+				for (long irow = 1; irow <= numberOfData; irow++) {
+					if (si -> classIndex[irow] == ilevel) {
+						struct Formula_Result result;
+						Formula_run (irow, dataColumns[icol], & result);
+						if (result.result.numericResult) {
+							data[++numberOfDataInLevelColumn] = Table_getNumericValue_Assert (me, irow, dataColumns[icol]);
+						}
+					}
+				}
+				if (numberOfDataInLevelColumn > 0) {
+					// determine position
+					double xc = xlevel - 0.5 + (spaceBetweenGroupsdiv2 + (icol - 1) * (boxWidth + spaceBetweenBoxesInGroup) + boxWidth / 2) * widthUnit;
+					Graphics_boxAndWhiskerPlot (g, data.peek(), numberOfDataInLevelColumn, xc, 0.5 * barWidth * widthUnit , 0.5 * boxWidth * widthUnit, ymin, ymax);
+				}
+			}
+		}
+		Graphics_unsetInner (g);
+		if (garnish) {
+			Graphics_drawInnerBox (g);
+			for (long ilevel = 1; ilevel <= numberOfLevels; ilevel++) {
+				SimpleString ss = (SimpleString) si -> classes -> item[ilevel];
+				Graphics_markBottom (g, ilevel, 0, 1, 0, ss -> string);
+			}
+			Graphics_marksLeft (g, 2, 1, 1, 0);
+		}
+	} catch (MelderError) {
+		Melder_clearError ();   // drawing errors shall be ignored
+	}
+}
+
 void Table_distributionPlotWhere (Table me, Graphics g, long dataColumn, double minimum, double maximum, long nBins, double freqMin, double freqMax, int garnish, const wchar_t *formula, Interpreter interpreter) {
 	try {
 		if (dataColumn < 1 || dataColumn > my numberOfColumns) return;
@@ -3994,7 +4143,7 @@ void Table_distributionPlotWhere (Table me, Graphics g, long dataColumn, double
 		}
 		Matrix_drawDistribution (thee.peek(), g, 0, 1, 0.5, mrow+0.5, minimum, maximum, nBins, freqMin, freqMax, 0, garnish);
 	} catch (MelderError) {
-		//
+		Melder_clearError ();   // drawing errors shall be ignored
 	}
 }
 
@@ -4058,10 +4207,27 @@ Graphics_Colour Strings_colourToValue  (Strings me, long index) {
 	return colourValue;
 }
 
+long Table_getNumberOfRowsWhere (Table me, const wchar_t *formula, Interpreter interpreter) {
+	long numberOfRows = 0;
+	Formula_compile (interpreter, me, formula, kFormula_EXPRESSION_TYPE_UNKNOWN, TRUE);
+	for (long irow = 1; irow <= my rows -> size; irow ++) {
+		struct Formula_Result result;
+		Formula_run (irow, 1, & result);
+		if (result.result.numericResult) {
+			numberOfRows++;
+		}
+	}
+	return numberOfRows;
+}
+
 long *Table_findRowsMatchingCriterion (Table me, const wchar_t *formula, Interpreter interpreter, long *numberOfMatches) {
 	try {
-		Formula_compile (interpreter, me, formula, kFormula_EXPRESSION_TYPE_UNKNOWN, TRUE);
-		autoNUMvector<long> selectedRows (1, my rows -> size);
+		*numberOfMatches = Table_getNumberOfRowsWhere (me, formula, interpreter);
+		if (*numberOfMatches < 1) {
+			Melder_throw ("No rows selected.");
+		}
+		Formula_compile (interpreter, me, formula, kFormula_EXPRESSION_TYPE_UNKNOWN, TRUE); // again?
+		autoNUMvector<long> selectedRows (1, *numberOfMatches);
 		long n = 0;
 		for (long irow =1; irow <= my rows -> size; irow++) {
 			struct Formula_Result result;
@@ -4070,45 +4236,22 @@ long *Table_findRowsMatchingCriterion (Table me, const wchar_t *formula, Interpr
 				selectedRows[++n] = irow;
 			}
 		}
-		if (n < 1) {
-			Melder_throw ("No rows selected.");
-		}
-		*numberOfMatches = n;
+		Melder_assert (n == *numberOfMatches);
 		return selectedRows.transfer();
 	} catch (MelderError) {
 		Melder_throw (me, ": cannot find matches.");
 	}
 }
 
-static bool Table_selectedColumnPartIsNumeric (Table me, long column, long *selectedRows, long numberOfSelectedRows) {
-	if (column < 1 || column > my numberOfColumns) return false;
-	for (long irow = 1; irow <= numberOfSelectedRows; irow++) {
-		if (! Table_isCellNumeric_ErrorFalse (me, selectedRows[irow], column)) return false;
-	}
-	return true;
-}
-
-// column and selectedRows are valid; *min & *max must be intialized
-static void Table_columnExtremesFromSelectedRows (Table me, long column, long *selectedRows, long numberOfSelectedRows, double *min, double *max) {
-	double cmin = 1e38, cmax = - cmin;
-	for (long irow = 1; irow <= numberOfSelectedRows; irow++) {
-		double val = Table_getNumericValue_Assert (me, selectedRows[irow], column);
-		if (val < cmin) { cmin = val; }
-		if (val > cmax) { cmax = val; }
-	}
-	*min = cmin;
-	*max = cmax;
-}
 
-void Table_barPlotWhere (Table me, Graphics g, const wchar_t *columnLabels, double ymin, double ymax,const wchar_t *labelColumn, double xoffsetFraction, double interbarFraction, double interbarsFraction, const wchar_t *colours, double angle, int garnish, const wchar_t *formula, Interpreter interpreter) {
+void Table_barPlotWhere (Table me, Graphics g, const wchar_t *columnLabels, double ymin, double ymax, const wchar_t *factorColumn, double xoffsetFraction, double interbarFraction, double interbarsFraction, const wchar_t *colours, double angle, int garnish, const wchar_t *formula, Interpreter interpreter) {
 	try {
 		long numberOfColumns, numberOfRowMatches = 0;
 		autoNUMvector<long> columnIndex (Table_getColumnIndicesFromColumnLabelString (me, columnLabels, &numberOfColumns), 1);
-		long labelIndex = Table_findColumnIndexFromColumnLabel (me, labelColumn);
+		long labelIndex = Table_findColumnIndexFromColumnLabel (me, factorColumn);
 		autoStrings colour = itemizeColourString (colours);// removes all spaces within { } so each {} can be parsed as 1 item
 		
 		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfRowMatches), 1);
-
 		if (ymax <= ymin) { // autoscaling
 			ymin = 1e38; ymax= - ymin;
 			for (long icol = 1; icol <= numberOfColumns; icol++) {
@@ -4190,7 +4333,7 @@ void Table_barPlotWhere (Table me, Graphics g, const wchar_t *columnLabels, doub
 			Graphics_marksLeft (g, 2, 1, 1, 0);
 		}
 	} catch (MelderError) {
-		//
+		Melder_clearError ();   // drawing errors shall be ignored
 	}
 }
 
@@ -4211,7 +4354,7 @@ static int Graphics_getConnectingLine (Graphics g, const wchar_t *text1, double
 	return drawLine;
 }
 
-// take the xcolumn as labels if non-numeric column elsee as numbers and arrange distances accordingly.
+// take the xcolumn as labels if non-numeric column else as numbers and arrange distances accordingly.
 void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, double xmax, long ycolumn, double ymin, double ymax, const wchar_t *symbol, double angle, int garnish, const wchar_t *formula, Interpreter interpreter) {
 	try {
 		if (ycolumn < 1 || ycolumn > my rows -> size) return;
@@ -4234,15 +4377,15 @@ void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, doub
 		Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
 		double x1, y1;
 		double lineSpacing = Graphics_dyMMtoWC (g, 1.5 * Graphics_inqFontSize (g) * 25.4 / 72);
-		double symbolHeight = lineSpacing / 1.5;
+		//double symbolHeight = lineSpacing / 1.5;
 		for (long i = 1; i <= numberOfSelectedRows; i++) {
 			double y2 = Table_getNumericValue_Assert (me, selectedRows[i], ycolumn);
 			double x2 = xIsNumeric ? Table_getNumericValue_Assert (me, selectedRows[i], xcolumn) : i;
-			double symbolWidth = 0;
+			//double symbolWidth = 0;
 			if (x2 >= xmin && (x2 <= xmax || x1 < xmax)) {
 				if (symbol && y2 >= ymin && y2 <= ymax && x2 <= xmax) {
 					Graphics_text (g, x2, y2, symbol);
-					symbolWidth = Graphics_textWidth (g, symbol);
+					//symbolWidth = Graphics_textWidth (g, symbol);
 				}
 				if (i > 1) {
 					double x3, y3, x4, y4, xo1, yo1, xo2, yo2;
@@ -4298,7 +4441,41 @@ void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, doub
 			}
 		}
 	} catch (MelderError) {
-		//
+		Melder_clearError ();   // drawing errors shall be ignored
+	}
+}
+
+void Table_lagPlotWhere (Table me, Graphics g, long column, long lag, double xmin, double xmax, const wchar_t *symbol, int labelSize, int garnish, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		if (column < 1 || column > my rows -> size) {
+			return;
+		}
+		long numberOfSelectedRows = 0;
+		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfSelectedRows), 1);
+		if (xmax <= xmin) { // autoscaling
+			Table_columnExtremesFromSelectedRows (me, column, selectedRows.peek(), numberOfSelectedRows, &xmin, &xmax);
+		}
+		autoNUMvector<double> x (1, numberOfSelectedRows);
+		for (long i = 1; i <= numberOfSelectedRows; i++) {
+			x[i] = Table_getNumericValue_Assert (me, selectedRows[i], column);
+		}
+		Graphics_setInner (g);
+		Graphics_setWindow (g, xmin, xmax, xmin, xmax);
+		Graphics_lagPlot (g, x.peek(), numberOfSelectedRows, xmin, xmax, lag, labelSize, symbol);
+		Graphics_unsetInner (g);
+		if (garnish) {
+			Graphics_drawInnerBox (g);
+			Graphics_marksBottom (g, 2, TRUE, TRUE, FALSE);
+			Graphics_marksLeft (g, 2, TRUE, TRUE, FALSE);
+			if (my columnHeaders [column]. label) {
+				Graphics_textLeft (g, TRUE, my columnHeaders[column].label);
+				autoMelderString textbottom;
+				MelderString_append (&textbottom, my columnHeaders[column].label, L" (lag = ", Melder_integer (lag), L")");
+				Graphics_textBottom (g, TRUE, textbottom.string);
+			}
+		}
+	} catch (MelderError) {
+		Melder_clearError ();   // drawing errors shall be ignored
 	}
 }
 
@@ -4328,5 +4505,152 @@ Table Table_extractRowsWhere (Table me, const wchar_t *formula, Interpreter inte
 	}
 }
 
+TableOfReal Table_to_TableOfRealWhere (Table me, const wchar_t *columnLabels, const wchar_t *factorColumn, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		long numberOfColumns, numberOfSelectedRows = 0;
+		long factorColIndex = Table_findColumnIndexFromColumnLabel (me, factorColumn);
+		autoNUMvector<long> columnIndex (Table_getColumnIndicesFromColumnLabelString (me, columnLabels, &numberOfColumns), 1);
+		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfSelectedRows), 1);
+		autoTableOfReal thee = TableOfReal_create (numberOfSelectedRows, numberOfColumns);
+		for (long i = 1; i <= numberOfSelectedRows; i++) {
+			for (long icol = 1; icol <= numberOfColumns; icol++) {
+				double value = Table_getNumericValue_Assert (me, selectedRows[i], columnIndex[icol]);
+				thy data[i][icol] = value;
+			}
+			if (factorColIndex > 0) { // if no factorColumn given labels may be empty
+				const wchar_t *label = Table_getStringValue_Assert (me, selectedRows[i], factorColIndex);
+				TableOfReal_setRowLabel (thee.peek(), i, label);
+			}
+		}
+		for (long icol = 1; icol <= numberOfColumns; icol++) {
+			TableOfReal_setColumnLabel (thee.peek(), icol, my columnHeaders [columnIndex[icol]].label);
+		}
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, "No TableOfReal created from Table.");
+	}
+}
+
+SSCPs Table_to_SSCPsWhere (Table me, const wchar_t *columnLabels, const wchar_t *factorColumn, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		autoTableOfReal thee = Table_to_TableOfRealWhere (me, columnLabels, factorColumn, formula, interpreter);
+		autoSSCPs him = TableOfReal_to_SSCPs_byLabel (thee.peek());
+		return him.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, "No Discriminant created from Table.");
+	}
+}
+
+static long SSCPs_findIndexOfGroupLabel (SSCPs me, const wchar_t *label) {
+	for (long i = 1; i <= my size; i++) {
+		if (Melder_wcscmp (Thing_getName ((SSCP) my item[i]), label) == 0) {
+			return i;
+		}
+	}
+	return 0;
+}
+
+Table Table_and_SSCPs_extractMahalanobisWhere (Table me, SSCPs thee, double numberOfSigmas, int which_Melder_NUMBER, const wchar_t *factorColumn, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		SSCP sscp = (SSCP) thy item[1];
+		long numberOfColumns = sscp -> numberOfColumns, numberOfSelectedRows = 0;
+		long factorColIndex = Table_findColumnIndexFromColumnLabel (me, factorColumn); // can be absent
+		autoNUMvector<long> columnIndex (1, numberOfColumns);
+		autoNUMvector<double> vector (1, numberOfColumns);
+		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfSelectedRows), 1);
+		for (long icol = 1; icol <= numberOfColumns; icol++) {
+			columnIndex[icol] = Table_getColumnIndexFromColumnLabel (me, sscp -> columnLabels[icol]); // throw if not present
+		}
+		long numberOfGroups = thy size;
+		autoTable him = Table_create (0, my numberOfColumns);
+		for (long icol = 1; icol <= my numberOfColumns; icol ++) {
+			autostring newLabel = Melder_wcsdup (my columnHeaders[icol].label);
+			his columnHeaders[icol].label = newLabel.transfer();
+		}
+		autoOrdered covs = Ordered_create ();
+		for (long igroup = 1; igroup <= numberOfGroups; igroup++) {
+			autoCovariance cov = SSCP_to_Covariance ((SSCP) thy item[igroup], 1); 
+			SSCP_expandLowerCholesky (cov.peek());
+			Collection_addItem (covs.peek(), cov.transfer());
+		}
+		for (long i = 1; i <= numberOfSelectedRows; i++) {
+			long irow = selectedRows[i];
+			long igroup = 1; // if factorColIndex == 0 we don't need labels
+			if (factorColIndex > 0) {
+				const wchar_t *label = Table_getStringValue_Assert (me, irow, factorColIndex);
+				igroup = SSCPs_findIndexOfGroupLabel (thee, label);
+				if (igroup == 0) {
+					Melder_throw ("The label \"", label, "\" in row ", Melder_integer (irow), " is not valid in this context.");
+				}
+			}
+			Covariance covi = (Covariance) covs -> item[igroup];
+			for (long icol = 1; icol <= numberOfColumns; icol++) {
+				vector[icol] = Table_getNumericValue_Assert (me, irow, columnIndex[icol]);
+			}
+			double dm2 = NUMmahalanobisDistance_chi (covi -> lowerCholesky, vector.peek(), covi -> centroid, numberOfColumns, numberOfColumns);
+			if (Melder_numberMatchesCriterion (sqrt (dm2), which_Melder_NUMBER, numberOfSigmas)) {
+				TableRow row = static_cast <TableRow> (my rows -> item [irow]);
+				autoTableRow newRow = Data_copy (row);
+				Collection_addItem (his rows, newRow.transfer());
+			}
+		}
+		return him.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, "Table (mahalanobis) not extracted.");
+	}
+}
+
+Table Table_extractMahalanobisWhere(Table me, const wchar_t *columnLabels, const wchar_t *factorColumn, double numberOfSigmas, int which_Melder_NUMBER, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		autoSSCPs thee = Table_to_SSCPsWhere (me, columnLabels, factorColumn, formula, interpreter);
+		autoTable him = Table_and_SSCPs_extractMahalanobisWhere (me, thee.peek(), numberOfSigmas, which_Melder_NUMBER, factorColumn, formula, interpreter);
+		return him.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, "Table not extracted.");
+	}
+}
 
+void Table_drawEllipsesWhere (Table me, Graphics g, long xcolumn, long ycolumn, long factorColumn, double xmin, double xmax, double ymin, double ymax, double numberOfSigmas, long labelSize, int garnish, const wchar_t *formula, Interpreter interpreter) {
+	try {
+		long numberOfSelectedRows = 0;
+		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfSelectedRows), 1);	
+		autoTableOfReal thee = TableOfReal_create (numberOfSelectedRows, 2);
+		for (long i = 1; i <= numberOfSelectedRows; i++) {
+			double x = Table_getNumericValue_Assert (me, selectedRows[i], xcolumn);
+			double y = Table_getNumericValue_Assert (me, selectedRows[i], ycolumn);
+			const wchar_t *label = Table_getStringValue_Assert (me, selectedRows[i], factorColumn);
+			thy data[i][1] = x; thy data[i][2] = y;
+			TableOfReal_setRowLabel (thee.peek(), i, label);
+		}
+		autoSSCPs him = TableOfReal_to_SSCPs_byLabel (thee.peek());
+		int confidence = 0;
+		if (ymax == ymin) { // autoscaling
+			SSCPs_getEllipsesBoundingBoxCoordinates (him.peek(), numberOfSigmas, confidence, &xmin, &xmax, &ymin, &ymax);
+		}
+		Graphics_setWindow (g, xmin, xmax, ymin, ymax);
+		Graphics_setInner (g);
+		for (long i = 1; i <= his size; i++) {
+			SSCP sscpi = (SSCP) his item[i];
+			double scalei = SSCP_getEllipseScalefactor (sscpi, numberOfSigmas, confidence);
+			if (scalei > 0) {
+				SSCP_drawTwoDimensionalEllipse_inside  (sscpi, g, scalei, Thing_getName (sscpi), labelSize);
+			}
+		}
+		Graphics_unsetInner (g);
+
+		if (garnish) {
+			Graphics_drawInnerBox (g);
+			Graphics_marksBottom (g, 2, TRUE, TRUE, FALSE);
+			Graphics_marksLeft (g, 2, TRUE, TRUE, FALSE);
+			if (my columnHeaders [xcolumn]. label) {
+				Graphics_textBottom (g, TRUE, my columnHeaders[xcolumn].label);
+			}
+			if (my columnHeaders [ycolumn]. label) {
+				Graphics_textLeft (g, TRUE, my columnHeaders[ycolumn].label);
+			}
+		}
+	} catch (MelderError) {
+		Melder_clearError ();   // drawing errors shall be ignored
+	}
+}
 /* End of file Table_extensions.cpp */
diff --git a/dwtools/Table_extensions.h b/dwtools/Table_extensions.h
index da6156c..9a87237 100644
--- a/dwtools/Table_extensions.h
+++ b/dwtools/Table_extensions.h
@@ -2,7 +2,7 @@
 #define _Table_extensions_h_
 /* Table_extensions.h
  *
- * Copyright (C) 1993-2013 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@
 
 /*
  djmw 20020411 initial GPL
- djmw 20130602 Latest modification.
+ djmw 20131220 Latest modification.
 */
 
 #include "TableOfReal.h"
@@ -32,6 +32,8 @@
 #include "SSCP.h"
 #include "Table.h"
 
+long Table_getNumberOfRowsWhere (Table me, const wchar_t *formula, Interpreter interpreter);
+long *Table_findRowsMatchingCriterion (Table me, const wchar_t *formula, Interpreter interpreter, long *numberOfMatches);
 Table Table_createFromPetersonBarneyData ();
 Table Table_createFromPolsVanNieropData ();
 Table Table_createFromWeeninkData ();
@@ -46,9 +48,11 @@ Table Table_getOneWayKruskalWallis (Table me, long column, long groupColumn, dou
 
 Table Table_getTwoWayAnalysisOfVarianceF (Table me, long column, long groupColumnA, long groupColumnB, Table *means, Table *factorLevelSizes);
 
-void Table_scatterPlotWithConfidenceIntervals (Table me, Graphics g, long xcolumn, long ycolumn,
-	double xmin, double xmax, double ymin, double ymax, long xci_min, long xci_max,
-	long yci_min, long yci_max, double bar_mm, int garnish);
+void Table_verticalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
+	double ymin, double ymax, long yci_min, long yci_max, double bar_mm, int garnish, const wchar_t *formula, Interpreter interpreter);
+void Table_horizontalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
+	double ymin, double ymax, long xci_min, long xci_max, double bar_mm, int garnish, const wchar_t *formula, Interpreter interpreter);
+
 
 void Table_normalProbabilityPlot (Table me, Graphics g, long column, long numberOfQuantiles, double numberOfSigmas, int labelSize, const wchar_t *label, int garnish);
 
@@ -57,12 +61,22 @@ void Table_quantileQuantilePlot (Table me, Graphics g, long xcolumn, long ycolum
 void Table_quantileQuantilePlot_betweenLevels (Table me, Graphics g, long dataColumn, long factorColumn, wchar_t *xlevel, wchar_t *ylevel, long numberOfQuantiles, double xmin, double xmax, double ymin, double ymax, int labelSize, const wchar_t *label, int garnish);
 
 void Table_boxPlots (Table me, Graphics g, long dataColumn, long factorColumn, double ymin, double ymax, int garnish);
+void Table_boxPlotsWhere (Table me, Graphics g, wchar_t *dataColumns_string, long factorColumn, double ymin, double ymax, int garnish, const wchar_t *formula, Interpreter interpreter);
+
 Table Table_extractRowsWhere (Table me, const wchar_t *formula, Interpreter interpreter);
+
+Table Table_extractMahalanobisWhere (Table me, const wchar_t *columnLabels, const wchar_t *factorColumn, double numberOfSigmas, int which_Melder_NUMBER, const wchar_t *formula, Interpreter interpreter);
+
 void Table_distributionPlotWhere (Table me, Graphics g, long dataColumn, double minimum, double maximum, long nBins, double freqMin, double freqMax, int garnish, const wchar_t *formula, Interpreter interpreter);
 
 void Table_barPlotWhere (Table me, Graphics g, const wchar_t *columnLabels, double ymin, double ymax, const wchar_t *labelColumn, double xoffsetFraction, double interbarFraction, double interbarsFraction, const wchar_t *colours, double angle, int garnish, const wchar_t *formula, Interpreter interpreter);
 
 void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, double xmax, long ycolumn, double ymin, double ymax, const wchar_t *symbol, double angle, int garnish, const wchar_t *formula, Interpreter interpreter);
+
+void Table_lagPlotWhere (Table me, Graphics g, long column, long lag, double xmin, double xmax, const wchar_t *symbol, int labelSize, int garnish, const wchar_t *formula, Interpreter interpreter);
+
+void Table_drawEllipsesWhere (Table me, Graphics g, long xcolumn, long ycolumn, long labelcolumn, double xmin, double xmax, double ymin, double ymax, double numberOfSigmas, long labelSize, int garnish, const wchar_t *formula, Interpreter interpreter);
+
 void Table_printAsAnovaTable (Table me);
 void Table_printAsMeansTable (Table me);
 
diff --git a/dwtools/TextGrid_extensions.cpp b/dwtools/TextGrid_extensions.cpp
index d84df4a..4c74644 100644
--- a/dwtools/TextGrid_extensions.cpp
+++ b/dwtools/TextGrid_extensions.cpp
@@ -1,6 +1,6 @@
 /* TextGrid_extensions.cpp
  *
- * Copyright (C) 1993-2013 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -99,6 +99,13 @@ struct TIMIT_key {
 	{"p", "p"},				/* pea: PCL P iy */
 	{"t", "t"},				/* tea: TCL T iy */
 	{"k", "k"},				/* key: KCL K iy */
+	/* 20140315: Added silences before the burst */
+	{"bcl", ""},
+	{"dcl", ""},
+	{"gcl", ""},
+	{"pcl", ""},
+	{"tcl", ""},
+	{"kcl", ""},
 	/* flap */
 	{"dx", "\\fh"},			/* muddy: m ah DX iy & dirty: dcl d er DX iy */
 	/* glottal stop */
diff --git a/dwtools/manual_BSS.cpp b/dwtools/manual_BSS.cpp
index c9f5717..21413a1 100644
--- a/dwtools/manual_BSS.cpp
+++ b/dwtools/manual_BSS.cpp
@@ -1,6 +1,6 @@
 /* manual_BSS.cpp
  *
- * Copyright (C) 2010-2013 David Weenink
+ * Copyright (C) 2010-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -101,7 +101,7 @@ INTRO (L"Detemines the @@Covariance|covariances@ between the channels of a selec
 NORMAL (L"The covariance of a sound is determined by calculating the @@CrossCorrelationTable@ of a multichannel sound for a lag time equal to zero.")
 MAN_END
 
-MAN_BEGIN (L"Sound: To Sound (blind source separation)...", L"djmw", 20130502)
+MAN_BEGIN (L"Sound: To Sound (blind source separation)...", L"djmw", 20140224)
 INTRO (L"Analyze the selected multi-channel sound into its independent components by an iterative method.")
 NORMAL (L"The @@blind source separation@ method to find the independent components tries to simultaneously diagonalize a number of "
 	"@@CrossCorrelationTable at s that are calculated from the multi-channel sound at different lag times.")
@@ -136,13 +136,13 @@ NORMAL (L"In this model #Y is a matrix with the selected multi-channel sound, #A
 	"Essentially the model says that each channel in the multi-channel sound is a linear combination of the "
 	"independent sound components in #X. "
 	"If we would know the mixing matrix #A we could easily solve the model above for #X by standard means. "
-	"However, if we don't know #A and we don't know #X, the decomposition of #Y is underdetermined as there "
-	"are an infinite number of possibilities. ")
+	"However, if we don't know #A and we don't know #X, the decomposition of #Y is underdetermined.  This means there "
+	"are an infinite number of possible combinations of #A and #X that result in the same #Y. ")
 NORMAL (L"One approach to solve the equation above is to make assumptions about the statistical properties "
 	"of the components in the matrix #X: it turns out that a sufficient assumption is to assume that the "
 	"components in #X at each time instant are %%statistically independent%. This is not an unrealistic "
 	"assumption in many cases, although in practice it need not be exactly the case. Another assumption is "
-	"that the mixing matrix is constant." )
+	"that the mixing matrix is constant, which means that the mixing conditions did not change during the recoding of the sound." )
 NORMAL (L"The theory says that statistically independent signals are not correlated (although the reverse "
 	"is not always true: signals that are not correlated don't have to be statistically independent). "
 	"The methods implemented here all follow this lead as follows. If we calculate the @@CrossCorrelationTable@ "
@@ -166,50 +166,50 @@ NORMAL (L"Unfortunately the convergence criteria of these two algorithms cannot
 	"change in the eigenvectors norm during an iteration.")
 ENTRY (L"Example")
 NORMAL (L"We start by creating a speech synthesizer that need to create two sounds. We will mix the two sounds and finally our blind source separation software will try to undo our mixing by extracting the two original sounds as well as possible from the two mixtures.")
-CODE(L"synth = do (\"Create SpeechSynthesizer...\", \"English\", \"default\")")
-CODE(L"s1 = do (\"To Sound...\", \"This is some text\", \"no\")")
+CODE(L"synth = Create SpeechSynthesizer: \"English\", \"default\"")
+CODE(L"s1 = To Sound: \"This is some text\", \"no\"")
 NORMAL (L"The first speech sound was created from the text \"This is some text\" at a speed of 175 words per minute.")
-CODE(L"selectObject (synth)")
-CODE(L"do (\"Set speech output settings...\", 44100, 0.01, 80, 50, 145, \"no\", \"IPA\")")
-CODE(L"s2 = do (\"To Sound...\", \"Abracadabra, abra\", 0.01, 80, 50, 145, \"yes\", \"no\", \"no\", \"yes\")")
+CODE(L"selectObject: synth")
+CODE(L"Set speech output settings: 44100, 0.01, 80, 50, 145, \"no\", \"IPA\"")
+CODE(L"s2 = To Sound.: \"Abracadabra, abra\", 0.01, 80, 50, 145, \"yes\", \"no\", \"no\", \"yes\"")
 NORMAL (L"The second sound \"Abracadabra, abra\" was synthesized at 145 words per minute with a somewhat larger pitch excursion (80) than the previous sound (50).")
-CODE(L"plusObject (s1)")
-CODE(L"stereo = do (\"Combine to stereo\")")
+CODE(L"plusObject: s1")
+CODE(L"stereo = Combine to stereo")
 NORMAL (L"We combine the two separate sounds into one stereo sound because our blind source separation works on multichannel sounds only.")
-CODE(L"mm = do (\"Create simple MixingMatrix...\", \"mm\", 2, 2, \"1.0 2.0 2.0 1.0\")")
+CODE(L"mm = Create simple MixingMatrix: \"mm\", 2, 2, \"1.0 2.0 2.0 1.0\"")
 NORMAL (L"A two by two MixingMatrix is created.")
-CODE(L"plusObject (stereo)")
-CODE(L"do (\"Mix\")")
+CODE(L"plusObject: stereo")
+CODE(L"Mix")
 NORMAL (L"The last command, Mix, creates a new two-channel sound where each channel is a linear mixture of the two "
     "channels in the stereo sound, i.e. channel 1 is the sum of s1 and s2 with mixture strengths of 1 and 2, respectively. "
     "The second channel is also the sum of s1 and s2 but now with mixture strengths 2 and 1, respectively.")
-CODE (L"do (\"To Sound (blind source separation)...\", 0.1, 1, 20, 0.0002, 100, 0.001, \"ffdiag\")")
+CODE (L"To Sound (blind source separation): 0.1, 1, 20, 0.0002, 100, 0.001, \"ffdiag\"")
 NORMAL (L"The two channels in the new sound that results from this command contain a reasonable approximation of "
     "the two originating sounds.")
 NORMAL (L"In the top panel the two speech sounds \"This is some text\" and \"abracadabra, abra\". "
     "The middle panel shows the two mixed sounds while the lower panel shows the two sounds after unmixing.")
 SCRIPT (6, 6, L" "
-	"syn = do (\"Create SpeechSynthesizer...\", \"English\", \"default\")\n"
-	"s1 = do (\"To Sound...\", \"This is some text\", \"no\")\n"
-    "selectObject (syn)\n"
-	"do (\"Set speech output settings...\", 44100, 0.01, 80, 50, 145, \"no\", \"IPA\")\n"
-	"s2 = do (\"To Sound...\", \"abracadabra, abra\", \"no\")\n"
-    "plusObject (s1)\n"
-	"stereo = do (\"Combine to stereo\")\n"
-	"do (\"Select inner viewport...\", 1, 6, 0.1, 1.9)\n"
-	"do (\"Draw...\", 0, 0, 0, 0, \"no\", \"Curve\")\n"
-	"do (\"Draw inner box\")\n"
-	"mm = do (\"Create simple MixingMatrix...\", \"mm\", 2, 2, \"1.0 2.0 2.0 1.0\")\n"
-    "plusObject (stereo)\n"
-	"mixed = do (\"Mix\")\n"
-	"do (\"Select inner viewport...\", 1, 6, 2.1, 3.9)\n"
-	"do (\"Draw...\", 0, 0, 0, 0, \"no\", \"Curve\")\n"
-	"do (\"Draw inner box\")\n"
-	"unmixed = do (\"To Sound (bss)...\", 0.1, 1, 20, 0.00021, 100, 0.001, \"ffdiag\")\n"
-	"do (\"Select inner viewport...\", 1, 6, 4.1, 5.9)\n"
-	"do (\"Draw...\", 0, 0, 0, 0, \"no\", \"Curve\")\n"
-	"do (\"Draw inner box\")\n"
-	"removeObject (unmixed, syn, stereo, s1, s2, mixed, mm)\n"
+	"syn = Create SpeechSynthesizer: \"English\", \"default\"\n"
+	"s1 = To Sound: \"This is some text\", \"no\"\n"
+    "selectObject: syn\n"
+	"Set speech output settings: 44100, 0.01, 80, 50, 145, \"no\", \"IPA\"\n"
+	"s2 = To Sound: \"abracadabra, abra\", \"no\"\n"
+    "plusObject: s1\n"
+	"stereo = Combine to stereo\n"
+	"Select inner viewport: 1, 6, 0.1, 1.9\n"
+	"Draw: 0, 0, 0, 0, \"no\", \"Curve\"\n"
+	"Draw inner box\n"
+	"mm = Create simple MixingMatrix: \"mm\", 2, 2, \"1.0 2.0 2.0 1.0\"\n"
+    "plusObject: stereo\n"
+	"mixed = Mix\n"
+	"Select inner viewport: 1, 6, 2.1, 3.9\n"
+	"Draw: 0, 0, 0, 0, \"no\", \"Curve\"\n"
+	"Draw inner box\n"
+	"unmixed = To Sound (bss): 0.1, 1, 20, 0.00021, 100, 0.001, \"ffdiag\"\n"
+	"Select inner viewport: 1, 6, 4.1, 5.9\n"
+	"Draw: 0, 0, 0, 0, \"no\", \"Curve\"\n"
+	"Draw inner box\n"
+	"removeObject: unmixed, syn, stereo, s1, s2, mixed, mm\n"
 )
 NORMAL (L"The first two panels will not change between different sessions of praat. The last panel, which shows "
     "the result of the blind source separation, i.e. unmixing, will not always be the same because of two things. In the first place the unmixing always starts with an initialisation with random values of the parameters that "
diff --git a/dwtools/manual_DataModeler.cpp b/dwtools/manual_DataModeler.cpp
new file mode 100644
index 0000000..93af1f4
--- /dev/null
+++ b/dwtools/manual_DataModeler.cpp
@@ -0,0 +1,59 @@
+/* manual_DataModeler.cpp
+ *
+ * Copyright (C) 2014 David Weenink
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+	djmw 20101009 Initial version
+*/
+
+#include "ManPagesM.h"
+
+void manual_DataModeler (ManPages me);
+void manual_DataModeler (ManPages me)
+{
+
+MAN_BEGIN (L"FormantModeler: Get residual sum of squares...", L"djmw", 20140421)
+INTRO (L"Get the residual sum of squares for a formant in the selected FormantModeler.")
+NORMAL (L"The residual sum of squares, RSS,  is defined as follows")
+FORMULA (L"RSS = \\su__i=1_^^n^ (%f__%i_ - %F__%i_)^^2^,")
+NORMAL (L"where %f__%i_ is the frequency value of the %i-ith data point, %F__%i_ is the frequency at the i-data point as estimated by the model and %n is the number of data points.")
+MAN_END
+
+MAN_BEGIN (L"Formants: Extract smoothest part (constrained)...", L"djmw", 20140424)
+NORMAL (L"Extracts the best matching part from the slected @@formant|Formant at s.")
+ENTRY (L"Settings")
+TAG (L"##Minimum F1 (Hz)")
+DEFINITION (L"suppresses models whose average first formant frequency, %f1, is below %%minimumF1% by a factor sqrt (%minimumF1 - %f1 + 1). You can use this constraint to disfavour models with a low average first formant. Sometimes due to the generally high frequency of the /a/ a lower harmonic of the fundamental frequency is taken as a candidate for the first formant. You can suppress these models with this constraint.")
+TAG (L"##Maximum F1 (Hz)")
+DEFINITION (L"suppresses models whose average first formant frequency, %f1, is above %%maximumF1% by a factor sqrt (%f1 - %%maximumF1% + 1).You can use this constraint to disfavour models in which the first formant is missing. ")
+TAG (L"##Minimum F2 (Hz)")
+DEFINITION (L"suppresses models whose average second formant frequency, %f2, is below %%minimumF2% by a factor sqrt (%minimumF2 - %f2 + 1). This constraint might be used for high front vowels that normally have a large distance between the first and second formant. ")
+TAG (L"##Maximum F2 (Hz)")
+DEFINITION (L"suppresses models whose average second formant frequency, %f2, is above %%maximumF2% by a factor sqrt (%f2 - %%maximumF2% + 1). This factor is sometimes necessary to suppress models for high back vowels where the second formant is \"missing\", i.e. where the third formant is playing the role of the second. ")
+TAG (L"##Minimum F3 (Hz)")
+DEFINITION (L"suppress models whose average third formant frequency, %%f3%, is below %%minimumF3% by a factor sqrt (%%minimumF3% - %%f3% + 1). This constraint might sometimes be usefull to suppress low lying third formants that can occur for /a/-like vowels.")
+MAN_END
+
+
+MAN_BEGIN (L"Buse (1973)", L"djmw", 20140328)
+NORMAL (L"A. Buse (1973): \"Goodness of fit in generalized least squares estimation.\", %%The American Statistician% #27: 106\\--108.")
+MAN_END
+
+}
+
+/* End of file manual_HMM.cpp */
diff --git a/dwtools/manual_HMM.cpp b/dwtools/manual_HMM.cpp
index 2a1366e..c4c637d 100644
--- a/dwtools/manual_HMM.cpp
+++ b/dwtools/manual_HMM.cpp
@@ -1,6 +1,6 @@
 /* manual_HMM.cpp
  *
- * Copyright (C) 2011-2013 David Weenink
+ * Copyright (C) 2011-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -256,7 +256,7 @@ TAG (L"##Covariance matrices are")
 DEFINITION (L"defines whether the complete covariance matrices in the mixture have to be calculated or only the diagonal.")
 MAN_END
 
-MAN_BEGIN (L"HMM_ObservationSequence", L"djmw", 20101010)
+MAN_BEGIN (L"HMM_ObservationSequence", L"djmw", 20140117)
 INTRO (L"An HMM_ObservationSequence models a sequence of observations. The observation sequence can be generated "
 	"by the @HMM or it can be used to train a model.")
 MAN_END
@@ -271,9 +271,9 @@ INTRO (L"A HMM is a Hidden Markov Model. Markov models are often used to model o
 	"depend on the previous observation. "
 	"A HMM can be visualised as a graph with a number of %%states%. If states are connected they have line connecting them. The following picture shows a HMM with two states, labeled \"Rainy\" and \"Sunny\". Each state can emit three symbols (these are not visible in the graph).   ")
 SCRIPT (5, 5,
-	L"do (\"Create simple HMM...\", \"wheather\", \"no\", \"Rainy Sunny\", \"Walk Shop Clean\")\n"
-	"do (\"Draw...\", \"no\")\n"
-	"do (\"Remove\")\n")
+	L"Create simple HMM: \"wheather\", \"no\", \"Rainy Sunny\", \"Walk Shop Clean\"\n"
+	"Draw: \"no\"\n"
+	"Remove\n")
 INTRO (L"For an introduction into HMM's see @@Rabiner (1989)@.")
 MAN_END
 
diff --git a/dwtools/manual_KlattGrid.cpp b/dwtools/manual_KlattGrid.cpp
index 0f7a047..961fb24 100644
--- a/dwtools/manual_KlattGrid.cpp
+++ b/dwtools/manual_KlattGrid.cpp
@@ -1,6 +1,6 @@
 /* manual_KlattGrid.cpp
  *
- * Copyright (C) 2009-2013 David Weenink
+ * Copyright (C) 2009-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,17 +23,17 @@ void manual_KlattGrid (ManPages me);
 void manual_KlattGrid (ManPages me)
 {
 
-MAN_BEGIN (L"KlattGrid", L"djmw", 20130410)
+MAN_BEGIN (L"KlattGrid", L"djmw", 20140117)
 INTRO (L"A KlattGrid represents the source-filter model as a function of time. It consists of a number of tiers that model aspects of the source and the filter, and the interaction between source and filter. The KlattGrid implements a superset of the speech synthesizer described in figure 14 in the @@Klatt & Klatt (1990)@ article.")
 NORMAL (L"The following drawing represents a cascade synthesizer with six oral formants, one nasal formant, "
 	"one nasal antiformant, one tracheal formant, one tracheal antiformant and six frication formants. ")
 SCRIPT (7.0, 6.5,
-	L"Create KlattGrid... kg 0 1 6 1 1 6 1 1 1\n"
-	"Draw synthesizer... Cascade\n"
+	L"Create KlattGrid: \"kg\", 0, 1, 6, 1, 1, 6, 1, 1, 1\n"
+	"Draw synthesizer: \"Cascade\"\n"
 	"Remove\n")
 NORMAL (L"In the next picture a parallel synthesizer branch is used instead of the cascade one.")
 SCRIPT (7.0, 6.5,
-	L"Create KlattGrid... kg 0 1 6 1 1 6 1 1 1\n"
+	L"Create KlattGrid: \"kg\", 0, 1, 6, 1, 1, 6, 1, 1, 1\n"
 	"Draw synthesizer... Parallel\n"
 	"Remove\n")
 NORMAL (L"All parameters in the synthesizer are represented by separate tiers.")
@@ -96,18 +96,18 @@ ENTRY (L"A minimal synthesizer")
 NORMAL (L"The following script produces a minimal voiced sound. The first line creates the standard KlattGrid."
 	"The next two lines define a pitch point, in Hz, and the voicing amplitude, in dB. The last line "
 	"creates the sound.")
-CODE (L"do (\"Create KlattGrid...\", \"kg\", 0, 1, 6, 1, 1, 6, 1, 1, 1)")
-CODE (L"do (\"Add pitch point...\", 0.5, 100)")
-CODE (L"do (\"Add voicing amplitude point...\", 0.5, 90)")
-CODE (L"do (\"To Sound\")")
+CODE (L"Create KlattGrid: \"kg\", 0, 1, 6, 1, 1, 6, 1, 1, 1")
+CODE (L"Add pitch point: 0.5, 100")
+CODE (L"Add voicing amplitude point: 0.5, 90")
+CODE (L"To Sound")
 NORMAL (L"The following script will produce raw frication noise. Because we do not specify formant amplitudes, "
 	"we turn off the formants in the parallel section.")
-CODE (L"do (\"Create KlattGrid...\", \"kg\", 0, 1, 6, 1, 1, 6, 1, 1, 1)")
-CODE (L"do (\"Add frication amplitude point...\", 0.5 ,80)")
-CODE (L"do (\"Add frication bypass point...\", 0.5, 0)")
-CODE (L"do (\"To Sound (special)...\", 0, 0, 44100, \"yes\", \"no\", \"yes\", \"yes\", \"yes\", \"yes\",")
+CODE (L"Create KlattGrid: \"kg\", 0, 1, 6, 1, 1, 6, 1, 1, 1")
+CODE (L"Add frication amplitude point: 0.5 ,80")
+CODE (L"Add frication bypass point: 0.5, 0")
+CODE (L"To Sound (special): 0, 0, 44100, \"yes\", \"no\", \"yes\", \"yes\", \"yes\", \"yes\",")
 CODE (L"... \"Powers in tiers\", \"yes\", \"yes\", \"yes\",")
-CODE (L"... \"Cascade\", 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, \"yes\")")
+CODE (L"... \"Cascade\", 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, \"yes\"")
 ENTRY (L"Changes")
 NORMAL (L"In praat versions before 5.1.05 the values for the %%oral / nasal / tracheal formant amplitudes% and"
 	" %%frication bypass amplitude% had to be given in dB SPL; "
diff --git a/dwtools/manual_MDS.cpp b/dwtools/manual_MDS.cpp
index c307df6..d0d4a7b 100644
--- a/dwtools/manual_MDS.cpp
+++ b/dwtools/manual_MDS.cpp
@@ -1,6 +1,6 @@
 /* manual_MDS.cpp
  *
- * Copyright (C) 1993-2013 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -1388,7 +1388,7 @@ INTRO (L"According to the measurement theory of @@Stevens (1951)@, there are fou
 	"two levels, Nominal and Ordinal, are often called %non-%metric. The last two are %metric.")
 MAN_END
 
-MAN_BEGIN (L"Multidimensional scaling", L"djmw", 20130502)
+MAN_BEGIN (L"Multidimensional scaling", L"djmw", 20140117)
 INTRO (L"This tutorial describes how you can use P\\s{RAAT} to "
 	"perform ##M#ulti##D#imensional ##S#caling (MDS) analysis.")
 NORMAL (L"MDS helps us to represent %dissimilarities between objects as "
@@ -1420,9 +1420,9 @@ NORMAL (L"Select the Configuration and choose @@Configuration: Draw...|Draw...@
 	"and the following picture will result")
 PICTURE (4.0, 4.0, drawLetterRConfigurationExample2)
 NORMAL (L"The following script summarizes:")
-CODE (L"dissimilarity = do (\"Create letter R example...\", 32.5)")
-CODE (L"configuration = do (\"To Configuration (monotone mds)...\", 2, \"Primary approach\", 0.00001, 50, 1)")
-CODE (L"do (\"Draw...\", 1, 2, -0.8, 1.2, -0.8, 0.7, \"yes\")")
+CODE (L"dissimilarity = Create letter R example: 32.5")
+CODE (L"configuration = To Configuration (monotone mds): 2, \"Primary approach\", 0.00001, 50, 1")
+CODE (L"Draw: 1, 2, -0.8, 1.2, -0.8, 0.7, \"yes\"")
 ENTRY (L"Obtaining the stress value")
 NORMAL (L"Select the Dissimilarity and the Configuration together and query for "
 	"the @stress value with: "
@@ -1430,8 +1430,8 @@ NORMAL (L"Select the Dissimilarity and the Configuration together and query for
 	"Get stress (monotone mds)... at . ")
 NORMAL (L"The following script summarizes:")
 CODE (L"selectObject (dissimilarity, configuration)")
-CODE (L"do (\"Get stress (monotone mds)...\", \"Primary approach\", \"Kruskals's "
-	"stress-1\")")
+CODE (L"Get stress (monotone mds): \"Primary approach\", \"Kruskals's "
+	"stress-1\"")
 ENTRY (L"The Shepard diagram")
 NORMAL (L"Select the Dissimilarity and the Configuration together to "
 	"@@Dissimilarity & Configuration: Draw Shepard diagram...|"
@@ -1439,7 +1439,7 @@ NORMAL (L"Select the Dissimilarity and the Configuration together to "
 PICTURE (4.0, 4.0, drawLetterRShepard)
 NORMAL (L"The following script summarizes:")
 CODE (L"selectObject (dissimilarity, configuration)")
-CODE (L"do (\"Draw Shepard diagram...\", 0, 200, 0, 2.2, 1, \"+\", \"yes\")")
+CODE (L"Draw Shepard diagram: 0, 200, 0, 2.2, 1, \"+\", \"yes\"")
 ENTRY (L"The (monotone) regression")
 NORMAL (L"Select the Dissimilarity and the Configuration together to "
 	"@@Dissimilarity & Configuration: Draw regression (monotone mds)...|"
@@ -1447,7 +1447,7 @@ NORMAL (L"Select the Dissimilarity and the Configuration together to "
 PICTURE (4.0, 4.0, drawLetterRRegression)
 NORMAL (L"The following script summarizes:")
 CODE (L"selectObject (dissimilarity, configuration)")
-CODE (L"do (\"Draw monotone regresion...\", \"Primary approach\", 0, 200, 0, 2.2, 1, \"+\", \"yes\")")
+CODE (L"Draw monotone regresion: \"Primary approach\", 0, 200, 0, 2.2, 1, \"+\", \"yes\"")
 NORMAL (L"When you enter %noiseRange = 0 in the form for the letter #R, perfect "
 	"reconstruction is possible. The Shepard diagram then will show "
 	"a perfectly smooth monotonically increasing function.")
@@ -1461,19 +1461,19 @@ NORMAL (L"When you can't have equal confidence in all the number in the "
 	"value...| Set value...@ command (remember: make %w__%ij_ = %w__%ji_).")
 NORMAL (L"The following script summarizes:")
 CODE (L"selectObject (dissimilarity)")
-CODE (L"weight = do (\"To Weight\")")
+CODE (L"weight = To Weight")
 CODE (L"! Change [i][j] and [j][i] cells in the Weight object")
-CODE (L"do (\"Set value...\", i, j, val)")
-CODE (L"do (\"Set value...\", j, i, val)")
+CODE (L"Set value: i, j, val")
+CODE (L"Set value: j, i, val")
 CODE (L"...")
 CODE (L"! now we can do a weighed analysis.")
 CODE (L"selectObject (dissimilarity, weight)")
-CODE (L"do (\"To Configuration (monotone mds)...\", 2, \"Primary approach\", 0.00001, 50, 1)")
+CODE (L"To Configuration (monotone mds): 2, \"Primary approach\", 0.00001, 50, 1)")
 NORMAL (L"You can also query the @stress values with three objects selected. "
 	"The following script summarizes:")
 CODE (L"selectObject (dissimilarity, weight, configuration)")
-CODE (L"do (\"Get stress (monotone mds)...\", \"Primary approach\", \"Kruskals's "
-	"stress-1\")")
+CODE (L"Get stress (monotone mds): \"Primary approach\", \"Kruskals's "
+	"stress-1\"")
 ENTRY (L"Using a start Configuration")
 NORMAL (L"You could also use a Configuration object as a starting "
 	"configuration in the minimization process. "
@@ -1483,7 +1483,7 @@ NORMAL (L"You could also use a Configuration object as a starting "
 	"starting point for further analysis:")
 NORMAL (L"The following script summarizes:")
 CODE (L"selectObject (dissimilarity, configuration, weight)")
-CODE (L"do (\"To Configuration (monotone mds)...\", 2, \"Primary approach\", 0.00001, 50, 1)")
+CODE (L"To Configuration (monotone mds): 2, \"Primary approach\", 0.00001, 50, 1")
 ENTRY (L"Multiple Dissimilarity's (INDSCAL)")
 NORMAL (L"When you have multiple Dissimilarity objects you can also perform "
 	"@@individual difference scaling@ (often called @@INDSCAL analysis@). ")
diff --git a/dwtools/manual_dwtools.cpp b/dwtools/manual_dwtools.cpp
index ec71fb1..35efc2d 100644
--- a/dwtools/manual_dwtools.cpp
+++ b/dwtools/manual_dwtools.cpp
@@ -1,6 +1,6 @@
 /* manual_dwtools.cpp
  *
- * Copyright (C) 1993-2013 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -471,7 +471,7 @@ NORMAL (L"The scores for the dependent data will be in the lower numbered column
 MAN_END
 
 
-MAN_BEGIN (L"Canonical correlation analysis", L"djmw", 20130502)
+MAN_BEGIN (L"Canonical correlation analysis", L"djmw", 20140509)
 INTRO (L"This tutorial will show you how to perform canonical correlation "
        "analysis with  P\\s{RAAT}.")
 ENTRY (L"1. Objective of canonical correlation analysis")
@@ -493,9 +493,9 @@ NORMAL (L"As an example, we will use the dataset from @@Pols et al. (1973)@ "
 	"@@discriminant analysis@ tutorial you can find how to get these data, "
 	"how to take the logarithm of the formant frequency values and how to "
 	"standardize them. The following script summarizes:")
-CODE (L"pols50m = do (\"Create TableOfReal (Pols 1973)...\", \"yes\")")
-CODE (L"do (\"Formula...\", \"if col < 4 then log10 (self) else self endif\")")
-CODE (L"do (\"Standardize columns\")")
+CODE (L"pols50m = Create TableOfReal (Pols 1973): \"yes\"")
+CODE (L"Formula: \"if col < 4 then log10 (self) else self endif\"")
+CODE (L"Standardize columns")
 NORMAL (L"Before we start with the %canonical correlation analysis we will first have "
 	"a look at the %Pearson correlations of this table and  "
 	"calculate the @Correlation matrix. It is given by:")
@@ -507,9 +507,9 @@ CODE (L"L1   0.384 -0.106  0.113  1     -0.038  0.085")
 CODE (L"L2  -0.505  0.526 -0.038 -0.038  1      0.128")
 CODE (L"L3  -0.014 -0.568  0.019  0.085  0.128  1")
 NORMAL (L"The following script summarizes:")
-CODE (L"selectObject (pols50m)")
-CODE (L"do (\"To Correlation\")")
-CODE (L"do (\"Draw as numbers...\", 1, 0, \"decimal\", 3)")
+CODE (L"selectObject: pols50m")
+CODE (L"To Correlation")
+CODE (L"Draw as numbers: 1, 0, \"decimal\", 3")
 NORMAL (L"The correlation matrix shows that high correlations exist between some "
 	"formant frequencies and some levels. For example, the correlation "
 	"coefficient between F2 and L2 equals 0.526.")
@@ -537,18 +537,18 @@ NORMAL (L"Select the TableOfReal and choose from the dynamic menu the option "
 	"\"Multivariate statistics\" action button. We fill out the form and supply "
 	"3 for %%Dimension of dependent variate%. The resulting CCA object will bear "
 	"the same name as the TableOfReal object. The following script summarizes:")
-CODE (L"selectObject (pols50m)")
-CODE (L"cca = do (\"To CCA...\", 3)")
+CODE (L"selectObject: pols50m")
+CODE (L"cca = To CCA: 3")
 ENTRY (L"3. How to get the canonical correlation coefficients")
 NORMAL (L"You can get the canonical correlation coefficients by queries of the CCA "
 	"object. You will find that the three canonical correlation coefficients, "
 	"\\ro(%u__1_, %v__1_), \\ro(%u__2_, %v__2_) and \\ro(%u__3_, %v__3_) are "
 	" approximately 0.86, 0.53 and 0.07, respectively. "
 	"The following script summarizes:")
-CODE (L"cc1 = do (\"Get correlation...\", 1)")
-CODE (L"cc2 = do (\"Get correlation...\", 2)")
-CODE (L"cc3 = do (\"Get correlation...\", 3)")
-CODE (L"writeInfoLine (\"cc1 = \", cc1, \", cc2 = \", cc2, \", cc3 = \", cc3)")
+CODE (L"cc1 = Get correlation: 1")
+CODE (L"cc2 = Get correlation: 2")
+CODE (L"cc3 = Get correlation: 3")
+CODE (L"writeInfoLine: \"cc1 = \", cc1, \", cc2 = \", cc2, \", cc3 = \", cc3")
 ENTRY (L"4. How to obtain canonical scores")
 NORMAL (L"Canonical #scores, also named @@canonical variate at s, are the linear combinations:")
 FORMULA (L"%u__%i_ = %y__%i1_%F__1_+%y__%i2_%F__2_ + %y__%i3_%F__3_, and,")
@@ -569,10 +569,10 @@ CODE (L"v3     .      .     0.070   .      .      1")
 NORMAL (L"The scores with a dot are zero to numerical precision. In this table the "
 	"only correlations that differ from zero are the canonical correlations. "
 	"The following script summarizes:")
-CODE (L"selectObject (cca, pols50m)")
-CODE (L"do (\"To TableOfReal (scores)...\", 3)")
-CODE (L"do (\"To Correlation\")")
-CODE (L"do (\"Draw as numbers if...\", 1, 0, \"decimal\", 2, \"abs(self) > 1e-14\")")
+CODE (L"selectObject: cca, pols50m")
+CODE (L"To TableOfReal (scores): 3)")
+CODE (L"To Correlation")
+CODE (L"Draw as numbers if: 1, 0, \"decimal\", 2, \"abs(self) > 1e-14")
 ENTRY (L"5. How to predict one dataset from the other")
 NORMAL (L"@@CCA & TableOfReal: Predict...@")
 NORMAL (L"Additional information can be found in @@Weenink (2003)@.")
@@ -713,17 +713,17 @@ TAG (L"columnLabels")
 DEFINITION (L"the names of the responses.")
 MAN_END
 
-MAN_BEGIN (L"Create simple Confusion...", L"djmw", 20130410)
+MAN_BEGIN (L"Create simple Confusion...", L"djmw", 20140117)
 INTRO (L"Creates a square @@Confusion|confusion matrix@ with equal stimulus labels and response labels.")
 ENTRY (L"Example")
-NORMAL (L"The command ##do (\"Create simple Confusion...\", \"simple\", \"u i a\")# results in the following Confusion:")
+NORMAL (L"The command ##Create simple Confusion: \"simple\", \"u i a\"# results in the following Confusion:")
 CODE (L"     u    i    a   ! The response labels")
 CODE (L"u    0    0    0   ! Responses on stimulus  u,")
 CODE (L"i    0    0    0   ! Responses on stimulus  i")
 CODE (L"a    0    0    0   ! Responses on stimulus  a")
 MAN_END
 
-MAN_BEGIN (L"Confusion: Increase...", L"djmw", 20130410)
+MAN_BEGIN (L"Confusion: Increase...", L"djmw", 20140117)
 INTRO (L"Increases the contents of the corresponding cell in the selected @@Confusion@ by one.")
 ENTRY (L"Settings")
 TAG (L"##Stimulus# and ##Response#")
@@ -734,14 +734,14 @@ CODE (L"       u    i    a   ! The response labels")
 CODE (L" u     6    2    1   ! Responses on stimulus  u,")
 CODE (L" i     3    4    2   ! Responses on stimulus  i")
 CODE (L" a     1    4    4   ! Responses on stimulus  a")
-NORMAL (L"The command ##do (\"Increase...\", \"u\", \"i\")# results in:")
+NORMAL (L"The command ##Increase: \"u\", \"i\"# results in:")
 CODE (L"       u    i    a   ! The responses")
 CODE (L" u     6    3    1   ! Responses on stimulus  u,")
 CODE (L" i     3    4    2   ! Responses on stimulus  i")
 CODE (L" a     1    4    4   ! Responses on stimulus  a")
 MAN_END
 
-MAN_BEGIN (L"Confusion: Group...", L"djmw", 20130410)
+MAN_BEGIN (L"Confusion: Group...", L"djmw", 20140117)
 INTRO (L"Groups a number of stimuli and responses into one new category.")
 ENTRY (L"Settings")
 TAG (L"##Stimuli & Responses")
@@ -756,17 +756,17 @@ CODE (L"       u    i    a   ! The response labels")
 CODE (L" u     6    2    1   ! Responses on stimulus  u,")
 CODE (L" i     3    4    2   ! Responses on stimulus  i")
 CODE (L" a     1    4    4   ! Responses on stimulus  a")
-NORMAL (L"After the command ##do (\"Group stimuli...\", \"u i\", \"high\", 0)#, the new Confusion will be:")
+NORMAL (L"After the command ##Group stimuli: \"u i\", \"high\", 0#, the new Confusion will be:")
 CODE (L"         high   a   ! The new response labels")
 CODE (L" high     15    3   ! Responses on group %%high%")
 CODE (L" a         5    4   ! Responses on stimulus  a")
-NORMAL (L"Instead after the command ##do (\"Group stimuli...\", \"u i\", \"high\", 2)#, the new Confusion will be:")
+NORMAL (L"Instead after the command ##Group stimuli: \"u i\", \"high\", 2#, the new Confusion will be:")
 CODE (L"        a   high  ! The new response labels")
 CODE (L" a      4     5   ! Responses on stimulus  a")
 CODE (L" high   3    15   ! Responses on group %%high%")
 MAN_END
 
-MAN_BEGIN (L"Confusion: Group stimuli...", L"djmw", 20130410)
+MAN_BEGIN (L"Confusion: Group stimuli...", L"djmw", 20140117)
 INTRO (L"Groups a number of stimuli into one new category.")
 ENTRY (L"Settings")
 TAG (L"##Stimuli")
@@ -781,13 +781,13 @@ CODE (L"       u    i    a   ! The response labels")
 CODE (L" u     6    2    1   ! Responses on stimulus  u,")
 CODE (L" i     3    4    2   ! Responses on stimulus  i")
 CODE (L" a     1    4    4   ! Responses on stimulus  a")
-NORMAL (L"After the command ##do (\"Group stimuli...\", \"u i\", \"high\", 1)#, the new Confusion will be:")
+NORMAL (L"After the command ##Group stimuli: \"u i\", \"high\", 1#, the new Confusion will be:")
 CODE (L"          u    i    a   ! The response labels")
 CODE (L" high     9    6    3   ! Responses on stimulus group %%high%,")
 CODE (L" a        1    4    4   ! Responses on stimulus  a")
 MAN_END
 
-MAN_BEGIN (L"Confusion: Group responses...", L"djmw", 20130410)
+MAN_BEGIN (L"Confusion: Group responses...", L"djmw", 20140117)
 INTRO (L"Groups a number of responses into one new category.")
 ENTRY (L"Settings")
 TAG (L"##Responses")
@@ -802,14 +802,14 @@ CODE (L"       u    i    a   ! The response labels")
 CODE (L" u     6    2    1   ! Responses on stimulus  u,")
 CODE (L" i     3    4    2   ! Responses on stimulus  i")
 CODE (L" a     1    4    4   ! Responses on stimulus  a")
-NORMAL (L"After the command ##do (\"Group responses...\", \"a i\", \"front\", 1)#, the new Confusion will be:")
+NORMAL (L"After the command ##Group responses: \"a i\", \"front\", 1#, the new Confusion will be:")
 CODE (L"   front    i    ! The new response labels")
 CODE (L" u     7    2    ! Responses on stimulus  u,")
 CODE (L" i     5    4    ! Responses on stimulus  i")
 CODE (L" a     5    4    ! Responses on stimulus  a")
 MAN_END
 
-MAN_BEGIN (L"Confusion: Get stimulus sum...", L"djmw", 20130410)
+MAN_BEGIN (L"Confusion: Get stimulus sum...", L"djmw", 20140117)
 INTRO (L"Returns the number of responses for the chosen stimulus (the sum of all the numbers in the row with this stimulus label). ")
 ENTRY (L"Example")
 NORMAL (L"Given the following selected Confusion:")
@@ -817,10 +817,10 @@ CODE (L"       u    i    a   ! The response labels")
 CODE (L" u     6    2    1   ! Responses on stimulus  u,")
 CODE (L" i     3    4    2   ! Responses on stimulus  i")
 CODE (L" a     1    4    4   ! Responses on stimulus  a")
-NORMAL (L"The command ##do (\"Get stimulus sum...\", \"a\")# will return the number 9.")
+NORMAL (L"The command ##Get stimulus sum: \"a\"# will return the number 9.")
 MAN_END
 
-MAN_BEGIN (L"Confusion: Get response sum...", L"djmw", 20130410)
+MAN_BEGIN (L"Confusion: Get response sum...", L"djmw", 20140117)
 INTRO (L"Returns the number of times the chosen response was given (the sum of all the numbers in the column with this response label).")
 ENTRY (L"Example")
 NORMAL (L"Given the following selected Confusion:")
@@ -828,7 +828,7 @@ CODE (L"       u    i    a   ! The response labelss")
 CODE (L" u     6    2    1   ! Responses on stimulus  u,")
 CODE (L" i     3    4    2   ! Responses on stimulus  i")
 CODE (L" a     1    4    4   ! Responses on stimulus  a")
-NORMAL (L"The command ##do (\"Get response sum...\", \"a\")# will return the number 7.")
+NORMAL (L"The command ##Get response sum: \"a\"# will return the number 7.")
 MAN_END
 
 MAN_BEGIN (L"Confusion: Condense...", L"djmw", 20130410)
@@ -1142,7 +1142,7 @@ INTRO (L"Extract those rows from the selected @TableOfReal object whose Mahalano
 	"quantile range.")
 MAN_END
 
-MAN_BEGIN (L"Covariance & TableOfReal: To TableOfReal (mahalanobis)...", L"djmw", 20130502)
+MAN_BEGIN (L"Covariance & TableOfReal: To TableOfReal (mahalanobis)...", L"djmw", 20140509)
 INTRO (L"Calculate Mahalanobis distance for the selected @TableOfReal with respect to the "
 	"selected @Covariance object.")
 ENTRY (L"Setting")
@@ -1159,20 +1159,20 @@ NORMAL (L"We first create a table with only one column and 10000 rows and fill i
 	"a normal distribution with mean zero and standard deviation one. Its covariance matrix, of course, is "
 	"one dimensional. We next create a table with Mahalanobis distances.")
 CODE (L"n = 100000")
-CODE (L"t0 = do (\"Create TableOfReal...\", \"table\", n, 1)")
-CODE (L"do (\"Formula...\",  randomGauss(0,1))")
-CODE (L"c = do (\"To Covariance\")")
-CODE (L"selectObject (c, t0)")
-CODE (L"ts = do (\"To TableOfReal (mahalanobis)...\", \"no\")")
+CODE (L"t0 = Create TableOfReal: \"table\", n, 1")
+CODE (L"Formula:  \"randomGauss(0,1)\"")
+CODE (L"c = To Covariance")
+CODE (L"selectObject: c, t0")
+CODE (L"ts = To TableOfReal (mahalanobis): \"no\"")
 CODE (L"")
 CODE (L"for nsigma to 5")
-CODE1 (L"  selectObject (ts)")
-CODE1 (L"  extraction = do (\"Extract rows where...\",  \"self < nsigma\")")
-CODE1 (L"  nr = do (\"Get number of rows\")")
+CODE1 (L"  selectObject: ts")
+CODE1 (L"  extraction = Extract rows where:  \"self < nsigma\"")
+CODE1 (L"  nr = Get number of rows")
 CODE1 (L"  nrp = nr / n * 100")
 CODE1 (L"  expect = (1 - 2 * gaussQ (nsigma)) * 100")
-CODE1 (L"  writeInfoLine (nsigma, \"-sigma: \", nrp, \"%, \", expect, \"%\")")
-CODE1 (L"  removeObject (extraction)")
+CODE1 (L"  writeInfoLine: nsigma, \"-sigma: \", nrp, \"%, \", expect, \"%\"")
+CODE1 (L"  removeObject: extraction")
 CODE (L"endfor")
 MAN_END
 
@@ -1286,7 +1286,7 @@ NORMAL (L"To avoid @aliasing in the chirp sound, a sound is only generated durin
 	"instantaneous frequency is greater than zero and smaller than the @@Nyquist frequency at .")
 MAN_END
 
-MAN_BEGIN (L"Create Sound from Shepard tone...", L"djmw", 20130410)
+MAN_BEGIN (L"Create Sound from Shepard tone...", L"djmw", 20140117)
 INTRO (L"One of the commands that create a @Sound.")
 ENTRY (L"Settings")
 TAG (L"##Name")
@@ -1337,9 +1337,9 @@ NORMAL (L"The following script generates 12 static Shepard tone complexes, 1 sem
 CODE (L"fadeTime = 0.010")
 CODE (L"for i to 12")
 CODE1 (L"fraction = (i-1)/12")
-CODE1 (L"do (\"Create Sound from Shepard tone...\", \"s\" + string\\$  (i), 0, 0.1, 22050, 4.863, 10, 0, 34, fraction)")
-CODE1 (L"do (\"Fade in...\", 0, 0, fadeTime, \"no\")")
-CODE1 (L"do (\"Fade out...\", 0, 0.1, -fadeTime, \"no\")")
+CODE1 (L"Create Sound from Shepard tone: \"s\" + string\\$  (i), 0, 0.1, 22050, 4.863, 10, 0, 34, fraction")
+CODE1 (L"Fade in: 0, 0, fadeTime, \"no\"")
+CODE1 (L"Fade out: 0, 0.1, -fadeTime, \"no\"")
 CODE (L"endfor")
 MAN_END
 
@@ -1479,7 +1479,7 @@ LIST_ITEM (L"\\bu Draw eigenvector...")
 LIST_ITEM (L"\\bu @@Discriminant: Draw sigma ellipses...|Draw sigma ellipses...@")
 MAN_END
 
-MAN_BEGIN (L"Discriminant analysis", L"djmw", 20130502)
+MAN_BEGIN (L"Discriminant analysis", L"djmw", 20140509)
 INTRO (L"This tutorial will show you how to perform discriminant analysis with P\\s{RAAT}")
 NORMAL (L"As an example, we will use the dataset from @@Pols et al. (1973)@ "
 	"with the frequencies and levels of the first three formants from the 12 "
@@ -1499,24 +1499,24 @@ NORMAL (L"Pols et al. use logarithms of frequency values, we will too. Because "
 	"the measurement units in the first three columns are in Hz and in the last "
 	"three columns in dB, it is probably better to standardize the columns. "
 	"The following script summarizes our achievements up till now:")
-CODE (L"table = do (\"Create TableOfReal (Pols 1973)...\", \"yes\"")
-CODE (L"do (\"Formula...\", \"if col < 4 then log10 (self) else self fi\"")
-CODE (L"do (\"Standardize columns\"")
+CODE (L"table = Create TableOfReal (Pols 1973): \"yes\"")
+CODE (L"Formula: \"if col < 4 then log10 (self) else self fi\"")
+CODE (L"Standardize columns\"")
 CODE (L"\\#  change the column labels too, for nice plot labels.")
-CODE (L"do (\"Set column label (index)...\", 1, \"standardized log (\\% F\\_ \\_ 1\\_ )\")")
-CODE (L"do (\"Set column label (index)...\", 2, \"standardized log (\\% F\\_ \\_ 2\\_ )\")")
-CODE (L"do (\"Set column label (index)...\", 3, \"standardized log (\\% F\\_ \\_ 3\\_ )\")")
-CODE (L"do (\"Set column label (index)...\", 4, \"standardized \\% L\\_ \\_ 1\\_ \")")
-CODE (L"do (\"Set column label (index)...\", 5, \"standardized \\% L\\_ \\_ 2\\_ \")")
-CODE (L"do (\"Set column label (index)...\", 6, \"standardized \\% L\\_ \\_ 3\\_ \")")
+CODE (L"Set column label (index): 1, \"standardized log (\\% F\\_ \\_ 1\\_ )\"")
+CODE (L"Set column label (index): 2, \"standardized log (\\% F\\_ \\_ 2\\_ )\"")
+CODE (L"Set column label (index): 3, \"standardized log (\\% F\\_ \\_ 3\\_ )\"")
+CODE (L"Set column label (index): 4, \"standardized \\% L\\_ \\_ 1\\_ \"")
+CODE (L"Set column label (index): 5, \"standardized \\% L\\_ \\_ 2\\_ \"")
+CODE (L"Set column label (index): 6, \"standardized \\% L\\_ \\_ 3\\_ \"")
 NORMAL (L"To get an indication of what these data look like, we make a scatter "
 	"plot of the "
 	"first standardized log-formant-frequency against the second standardized "
 	"log-formant-frequency. With the next script fragment you can reproduce the "
 	"following picture.")
-CODE (L"do (\"Viewport...\", 0, 5, 0, 5)")
-CODE (L"selectObject (table)")
-CODE (L"do (\"Draw scatter plot...\", 1, 2, 0, 0, -2.9, 2.9, -2.9, 2.9, 10, \"yes\", \"+\", \"yes\")")
+CODE (L"Viewport: 0, 5, 0, 5")
+CODE (L"selectObject: table")
+CODE (L"Draw scatter plot: 1, 2, 0, 0, -2.9, 2.9, -2.9, 2.9, 10, \"yes\", \"+\", \"yes\"")
 PICTURE (5, 5, drawPolsF1F2_log)
 NORMAL (L"Apart from a difference in scale this plot is the same as fig. 3 in the "
 	"Pols et al. article.")
@@ -1526,8 +1526,8 @@ NORMAL (L"Select the TableOfReal and choose from the dynamic menu the option "
 	"in the \"Multivariate statistics\" action button. The resulting Discriminant "
 	"object will bear the same name as the TableOfReal object. The following "
 	"script summarizes:")
-CODE (L"selectObject (table)")
-CODE (L"discrimimant = do (\"To Discriminant\")")
+CODE (L"selectObject: table")
+CODE (L"discrimimant = To Discriminant\"")
 ENTRY (L"2. How to project data on the discriminant space")
 NORMAL (L"You select a TableOfReal and a Discriminant object together and choose: "
 	"@@Discriminant & TableOfReal: To Configuration...|To Configuration... at . "
@@ -1537,10 +1537,10 @@ NORMAL (L"You select a TableOfReal and a Discriminant object together and choose
 	"configuration are the eigenvectors from the Discriminant.")
 PICTURE (5, 5, drawPolsDiscriminantConfiguration)
 NORMAL (L"The following script summarizes:")
-CODE (L"selectObject (table, discriminant)")
-CODE (L"do (\"To Configuration...\", 0)")
-CODE (L"do (\"Viewport...\", 0, 5, 0, 5)")
-CODE (L"do (\"Draw...\", 1, 2, -2.9, 2.9, -2.9, 2.9, 12, \"yes\", \"+\", \"yes\")")
+CODE (L"selectObject: table, discriminant")
+CODE (L"To Configuration: 0")
+CODE (L"Viewport: 0, 5, 0, 5")
+CODE (L"Draw: 1, 2, -2.9, 2.9, -2.9, 2.9, 12, \"yes\", \"+\", \"yes\"")
 NORMAL (L"If you are only interested in this projection, there also is a short cut "
 	"without an intermediate Discriminant object:  "
 	"select the TableOfReal object and choose @@TableOfReal: To Configuration "
@@ -1554,8 +1554,8 @@ NORMAL (L"Select the Discriminant object and choose @@Discriminant: Draw sigma "
 	"standardized log %F__1_ vs log %F__2_ plane. When the data are multinormally distributed, "
 	"a 1-%\\si ellipse will cover approximately 39.3\\%  of the data. "
 	"The following code summarizes:")
-CODE (L"selectObject (discriminant)")
-CODE (L"do (\"Draw sigma ellipses...\", 1.0, \"no\", 1, 2, -2.9, 2.9, -2.9, 2.9, 12, \"yes\")")
+CODE (L"selectObject: discriminant")
+CODE (L"Draw sigma ellipses: 1.0, \"no\", 1, 2, -2.9, 2.9, -2.9, 2.9, 12, \"yes\"")
 PICTURE (5, 5, drawPolsF1F2ConcentrationEllipses)
 ENTRY (L"4. How to classify")
 NORMAL (L"Select together the Discriminant object (the classifier), and "
@@ -1716,7 +1716,7 @@ NORMAL (L"The number of columns in the TableOfReal must equal the dimension of t
 NORMAL (L"See also @@Eigen & TableOfReal: Project... at .")
 MAN_END
 
-MAN_BEGIN (L"Discriminant & TableOfReal: To TableOfReal (mahalanobis)...", L"djmw", 20130502)
+MAN_BEGIN (L"Discriminant & TableOfReal: To TableOfReal (mahalanobis)...", L"djmw", 20140509)
 INTRO (L"Calculate Mahalanobis distances for the selected @TableOfReal with respect to one group in the "
 	"selected @Discriminant object.")
 ENTRY (L"Settings")
@@ -1730,19 +1730,18 @@ ENTRY (L"Example")
 NORMAL (L"Calculate the number of datapoints that are within the one-sigma elipses of two different groups, i.e. "
 	"the number of data points that are in the overlapping area. ")
 NORMAL (L"Suppose the group labels are \\o/ and \\yc.")
-CODE (L"pols50m = do (\"Create TableOfReal (Pols 1973)...\", \"no\")")
-CODE (L"do (\"Formula...\", \"log10(self)\")")
-CODE (L"discriminant = do (\"To Discriminant\")")
-CODE (L"selectObject (pols50m, discriminant)")
-CODE (L"t1 = do (\"To TableOfReal (mahalanobis)...\", \"\\bso/\", \"no\")")
-CODE (L"selectObject (pols50m, discriminant)")
-CODE (L"t2 = do (\"To TableOfReal (mahalanobis)...\", \"\\bsyc\", \"no\")")
+CODE (L"pols50m = Create TableOfReal (Pols 1973): \"no\"")
+CODE (L"Formula: \"log10(self)\"")
+CODE (L"discriminant = To Discriminant")
+CODE (L"selectObject: pols50m, discriminant")
+CODE (L"t1 = To TableOfReal (mahalanobis): \"\\bso/\", \"no\"")
+CODE (L"selectObject: pols50m, discriminant")
+CODE (L"t2 = To TableOfReal (mahalanobis): \"\\bsyc\", \"no\"")
 NORMAL (L"Now we count when both the t1 and t2 values are smaller than 1 (sigma):")
-CODE (L"do (\"Copy...\", \"tr\")")
-CODE (L"do (\"Formula...\", \"Object_'t1'[] < 1 and Object_'t2'[] < 1\")")
-CODE (L"do (\"Extract rows where column...\", 1, \"equal to\", 1)")
-CODE (L"no = do (\"Get number of rows\")")
-
+CODE (L"Copy: \"tr\"")
+CODE (L"Formula: \"Object_'t1'[] < 1 and Object_'t2'[] < 1\"")
+CODE (L"Extract rows where column: 1, \"equal to\", 1")
+CODE (L"no = Get number of rows\"")
 MAN_END
 
 MAN_BEGIN (L"DTW", L"djmw", 20110603)
@@ -1886,7 +1885,7 @@ DEFINITION (L"the number of different symbols in the source symbol set that you
 	"that fall in a %rest% category. If you don't want to treat any source symbol is a special way you may set this value 0.")
 MAN_END
 
-MAN_BEGIN (L"EditCostsTable", L"djmw", 20130502)
+MAN_BEGIN (L"EditCostsTable", L"djmw", 20140509)
 INTRO (L"One of the @@types of objects@ in Praat.")
 NORMAL (L"The EditCostsTable determines the %%string edit costs%, i.e. the costs involved in changing one string of "
 	"symbols (the %%source%) into another one (the %%target%). "
@@ -1894,20 +1893,20 @@ NORMAL (L"The EditCostsTable determines the %%string edit costs%, i.e. the costs
 	"The latter terms refer to the operations that may be performed on a source string to transform it to a target "
 	"string. For example, to change the source string \"execution\" to the target string \"intention\" we would need "
 	"one insertion (i), one deletion (d) and three substitutions (s) as the following figure shows.")
-SCRIPT (4, 1.0,  L"target = do (\"Create Strings as characters...\", \"intention\")\n"
-"source = do (\"Create Strings as characters...\", \"execution\")\n"
-	"selectObject (source, target)\n"
-	"edt = do (\"To EditDistanceTable\")\n"
-	"do (\"Draw edit operations\")\n"
-	"removeObject (edt, target, source)\n")
+SCRIPT (4, 1.0,  L"target = Create Strings as characters: \"intention\"\n"
+"source = Create Strings as characters: \"execution\"\n"
+	"selectObject: source, target\n"
+	"edt = To EditDistanceTable\n"
+	"Draw edit operations\n"
+	"removeObject: edt, target, source\n")
 NORMAL (L"The figure above was produced with default values for the costs, i.e. the insertion and deletion costs were 1.0 while the "
 	"substitution cost was 2.0. The actual edit distance between the target and source strings is calculated by the @@EditDistanceTable@ "
 	"which uses an EditCostsTable to access the specific string edit costs. The figure above was produced by the following commands:")
-CODE (L"target = do (\"Create Strings as characters...\", \"intention\")")
-CODE (L"source = do (\"Create Strings as characters...\", \"execution\")")
-CODE (L"plusObject (target)")
-CODE (L"edt = do (\"To EditDistanceTable\")")
-CODE (L"do (\"Draw edit operations\")")
+CODE (L"target = Create Strings as characters: \"intention\"")
+CODE (L"source = Create Strings as characters: \"execution\"")
+CODE (L"plusObject: target")
+CODE (L"edt = To EditDistanceTable")
+CODE (L"Draw edit operations")
 NORMAL (L"The default EditCostsTable which is in every new EditDistanceTable object has only two rows and two columns, "
 	"where the cells in this EditCostsTable have the following interpretation:\n")
 TAG (L"Cell [1][2]:")
@@ -1944,15 +1943,15 @@ CODE (L"t 1.1 1.2 1.3")
 CODE (L"  1.4 1.5 1.6")
 CODE (L"  1.7 1.8 0.0")
 NORMAL (L"By issuing the following series of commands this particular table can be created:")
-CODE (L"do (\"Create empty EditCostsTable...\", \"editCosts\", 1, 1)")
-CODE (L"do (\"Set target symbol (index)...\", 1, \"t\")")
-CODE (L"do (\"Set source symbol (index)...\", 1, \"s\")")
-CODE (L"do (\"Set insertion costs...\", \"t\", 1.3)")
-CODE (L"do (\"Set deletion costs...\", \"s\", 1.7)")
-CODE (L"do (\"Set substitution costs...\", \"t\", \"s\", 1.1)")
-CODE (L"do (\"Set substitution costs...\", \"\", \"s\", 1.4)")
-CODE (L"do (\"Set substitution costs...\", \"t\", \"\", 1.2)")
-CODE (L"do (\"Set costs (others)...\", 1.6, 1.8, 0, 1.5)")
+CODE (L"Create empty EditCostsTable: \"editCosts\", 1, 1")
+CODE (L"Set target symbol (index): 1, \"t\"")
+CODE (L"Set source symbol (index): 1, \"s\"")
+CODE (L"Set insertion costs: \"t\", 1.3")
+CODE (L"Set deletion costs: \"s\", 1.7")
+CODE (L"Set substitution costs: \"t\", \"s\", 1.1")
+CODE (L"Set substitution costs: \"\", \"s\", 1.4")
+CODE (L"Set substitution costs: \"t\", \"\", 1.2")
+CODE (L"Set costs (others): 1.6, 1.8, 0, 1.5")
 NORMAL (L"In the first line we create the (empty) table, we name it %%editCosts% and it creates space for one target "
 	"and one source symbol. The next line defines the target symbol which becomes the label of the first row of the table. "
 	"Line 3 defines the source symbol which will become the label of the first column of the table. "
@@ -1970,23 +1969,23 @@ ENTRY (L"How to use a special EditCostsTable")
 NORMAL (L"After creating the special EditCostsTable you select it together with the EditDistanceTable and issue the command @@EditDistanceTable & EditCostsTable: Set new edit costs|Set new edit costs at . The EditDistanceTable will then find the minimum edit distance based on the new cost values.")
 MAN_END
 
-MAN_BEGIN (L"EditDistanceTable", L"djmw", 20130502)
+MAN_BEGIN (L"EditDistanceTable", L"djmw", 20140509)
 INTRO (L"One of the @@types of objects@ in Praat.")
 NORMAL (L"An EditDistanceTable shows the accumulated distances between a target string and a source string. "
 	"For example, the accumulated distances between the target string \"intention\" and the source string "
 	"\"execution\" can be expressed by the following EditDistanceTable:")
-SCRIPT (5, 3.5, L"target = do (\"Create Strings as characters...\", \"intention\")\n"
-	"source = do (\"Create Strings as characters...\", \"execution\")\n"
-	"selectObject (source, target)\n"
-	"edt = do (\"To EditDistanceTable\")\n"
-	"do (\"Draw...\", \"decimal\", 1, 0)\n"
-	"removeObject (edt, target, source)\n")
+SCRIPT (5, 3.5, L"target = Create Strings as characters: \"intention\"\n"
+	"source = Create Strings as characters: \"execution\"\n"
+	"selectObject: source, target\n"
+	"edt = To EditDistanceTable\n"
+	"Draw: \"decimal\", 1, 0\n"
+	"removeObject: edt, target, source\n")
 NORMAL (L"This figure was created by issuing the following commands:")
-CODE (L"target = do (\"Create Strings as characters...\", \"intention\")")
-CODE (L"source = do (\"Create Strings as characters...\", \"execution\")")
-CODE (L"plusObject (target)")
-CODE (L"edt = do (\"To EditDistanceTable\")")
-CODE (L"do (\"Draw...\", \"decimal\", 1, 0)")
+CODE (L"target = Create Strings as characters: \"intention\"")
+CODE (L"source = Create Strings as characters: \"execution\"")
+CODE (L"plusObject: target")
+CODE (L"edt = To EditDistanceTable")
+CODE (L"Draw: \"decimal\", 1, 0")
 NORMAL (L"The target string is always displayed vertically while the source string is displayed horizontally and the origin is at the bottom-left corner of the table. "
 	"Each cell of this table, dist[%i, %j], contains the accumulated distance between the first %i characters of the target and the first %j characters of the source. The cells on the path through this table which have the "
 	"minimum accumulated cost are shown with boxes around them. Below we will explain how this path is calculated.")
@@ -1996,12 +1995,12 @@ NORMAL (L"If we trace the path from its start at the origin to its end, we see t
 	"In the next step which is in the diagonal direction, the \"n\" target is substituted for the \"e\" source symbol. Next follows another substitution, \"t\" for \"x\". "
 	"The next diagonal step substitutes \"e\" for an identical \"e\". This step is followed by a horizontal step in which the source symbol \"c\" is deleted. "
 	"The next diagonal step substitutes an \"n\" for a \"u\". The path now continues in the diagonal direction until the end point and only identical substitutions occur in the last part. The following figure shows these operations more explicitly.")
-SCRIPT (4, 1.5,  L"target = do (\"Create Strings as characters...\", \"intention\")\n"
-	"source = do (\"Create Strings as characters...\", \"execution\")\n"
-	"plusObject (target)\n"
-	"edt = do (\"To EditDistanceTable\")\n"
-	"do (\"Draw edit operations\")\n"
-	"removeObject (edt, target, source)\n")
+SCRIPT (4, 1.5,  L"target = Create Strings as characters: \"intention\"\n"
+	"source = Create Strings as characters: \"execution\"\n"
+	"plusObject: target\n"
+	"edt = To EditDistanceTable\n"
+	"Draw edit operations\n"
+	"removeObject: edt, target, source\n")
 NORMAL (L"The value of the accumulated costs in a cell of the table is computed by taking the minimum of the accumulated distances from three possible paths that end in the current cell, i.e. the paths that come from the %%left%, from the %%diagonal% and from %%below%.")
 CODE (L"dist[i,j] = min (d__left_, d__diag_, d__below_), ")
 NORMAL (L"where ")
@@ -2597,7 +2596,7 @@ MAN_BEGIN (L"Polygon: Rotate...", L"djmw", 20100418)
 INTRO (L"Rotates the selected @@Polygon@ counterclockwise with respect to the given coordinates.")
 MAN_END
 
-MAN_BEGIN (L"Create simple Polygon...", L"djmw", 20130409)
+MAN_BEGIN (L"Create simple Polygon...", L"djmw", 20140117)
 INTRO (L"Creates a @@Polygon@ from user  supplied x/y pairs.")
 ENTRY (L"Settings")
 TAG (L"##Name")
@@ -2605,11 +2604,11 @@ DEFINITION (L"defines the name of the resulting Polygon.")
 TAG (L"##Vertices as X-Y pairs#,")
 DEFINITION (L"defines the x-y values of the vertices of the Polygon. The Polygon will be automatically closed, i.e., the first and the last point will be connected.")
 ENTRY (L"Example")
-NORMAL (L"The command ##Create simple Polygon... p 0.0 0.0  0.0 1.0  1.0 0.0# defines  a Polygon with three points. In the figure the three points are indicated with open circles while the Polygon is drawn as a closed figure.")
-SCRIPT (4,4, L"do (\"Create simple Polygon...\", \"p\", \"0.0 0.0 0.0 1.0 1.0 0.0\")\n"
-	"do (\"Draw circles...\", 0, 1, 0, 1, 3)\n"
-	"do (\"Draw closed...\", 0, 1, 0, 1)\n"
-	"do (\"Remove\")\n")
+NORMAL (L"The command ##Create simple Polygon: \"p\", \"0.0 0.0 0.0 1.0 1.0 0.0\"# defines  a Polygon with three points. In the figure the three points are indicated with open circles while the Polygon is drawn as a closed figure.")
+SCRIPT (4,4, L"Create simple Polygon: \"p\", \"0.0 0.0 0.0 1.0 1.0 0.0\"\n"
+	"Draw circles: 0, 1, 0, 1, 3\n"
+	"Draw closed: 0, 1, 0, 1\n"
+	"Remove\n")
 MAN_END
 
 MAN_BEGIN (L"Polygon: Get location of point...", L"djmw", 20120220)
@@ -2618,18 +2617,18 @@ ENTRY (L"Algorithm")
 NORMAL (L"We determine how often a horizontal line extending from the point crosses the polygon. If the number of crossings is even, the point is on the outside, else on the inside. Special care is taken to be able to detect if a point is on the boundary of the polygon. The used algorithm is from @@Hormann & Agathos (2001)@")
 MAN_END
 
-MAN_BEGIN (L"Polygon: Simplify", L"djmw", 20130502)
+MAN_BEGIN (L"Polygon: Simplify", L"djmw", 20140509)
 INTRO (L"Removes collinear vertices from a @@Polygon at .")
 ENTRY (L"Example")
 SCRIPT (4, 4,
-	L"p1 = do (\"Create simple Polygon...\", \"p\", \"0.0 0.0 0.0 1.0 0.5 0.5 1.0 0.0 0.5 0 0 -0.5 0 -0.25\")\n"
-	"do (\"Draw closed...\", 0, 0, 0, 0)\n"
-	"do (\"Colour...\", \"Red\")\n"
-	"do (\"Draw circles...\", 0, 0, 0, 0, 3)\n"
-	"p2 = do (\"Simplify\")\n"
-	"do (\"Colour...\", \"Black\")\n"
-	"do (\"Paint circles...\", 0, 0, 0, 0, 1.5)\n"
-	"removeObject (p1, p2)\n"
+	L"p1 = Create simple Polygon: \"p\", \"0.0 0.0 0.0 1.0 0.5 0.5 1.0 0.0 0.5 0 0 -0.5 0 -0.25\"\n"
+	"Draw closed: 0, 0, 0, 0\n"
+	"Colour: \"Red\"\n"
+	"Draw circles: 0, 0, 0, 0, 3\n"
+	"p2 = Simplify\n"
+	"Colour: \"Black\"\n"
+	"Paint circles: 0, 0, 0, 0, 1.5\n"
+	"removeObject: p1, p2\n"
 )
 NORMAL (L"Given the Polygon with the seven vertices indicated by the red open circles, the Simplify action results in the Polygon with four vertices indicated by the filled black circles.")
 MAN_END
@@ -3299,7 +3298,7 @@ DEFINITION (L"The method of %%spectral subtraction% was defined in @@Boll (1979)
 	"after a script by Ton Wempe.")
 MAN_END
 
-MAN_BEGIN (L"Sound: Draw where...", L"djmw", 20130409)
+MAN_BEGIN (L"Sound: Draw where...", L"djmw", 20140509)
 INTRO (L"A command to draw only those parts of a @Sound where a condition holds.")
 ENTRY (L"Settings")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (5), L""
@@ -3320,38 +3319,38 @@ DEFINITION (L"determines the part of the sound that will be drawn. All parts whe
 	"This formula may ##not# contain references to the sampling of the sound, i.e. don't use 'col', 'x1', 'dx' and 'ncol' in it.")
 ENTRY (L"Example 1")
 NORMAL (L"The following script draws all amplitudes larger than one in red.")
-CODE (L"do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 2000, \"1.8*sin(2*pi*5*x)+randomGauss(0,0.1)\")")
-CODE (L"do (\"Colour...\", \"Red\")")
-CODE (L"do (\"Draw where...\", 0, 0, -2, 2, \"no\", \"Curve\", \"abs(self)>1\")")
-CODE (L"do (\"Colour...\", \"Black\")")
-CODE (L"do (\"Draw where...\", 0, 0, -2, 2, \"yes\", \"Curve\", \"not (abs(self)>1)\")")
+CODE (L"Create Sound from formula: \"s\", \"Mono\", 0, 1, 2000, \"1.8*sin(2*pi*5*x)+randomGauss(0,0.1)\"")
+CODE (L"Colour: \"Red\"")
+CODE (L"Draw where: 0, 0, -2, 2, \"no\", \"Curve\", \"abs(self)>1\"")
+CODE (L"Colour: \"Black\"")
+CODE (L"Draw where: 0, 0, -2, 2, \"yes\", \"Curve\", \"not (abs(self)>1)\"")
 SCRIPT (8, 3,
-	L"do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 2000, \"1.8*sin(2*pi*5*x)+randomGauss(0,0.1)\")\n"
-	"do (\"Colour...\", \"Red\")\n"
-	"do (\"Draw where...\", 0, 0, -2, 2, \"no\", \"Curve\", \"abs(self)>1\")\n"
-	"do (\"Colour...\", \"Black\")\n"
-	"do (\"Draw where...\",  0, 0, -2, 2, \"yes\", \"Curve\", \"not (abs(self)>1)\")\n"
-	"do (\"Remove\")\n"
+	L"Create Sound from formula: \"s\", \"Mono\", 0, 1, 2000, \"1.8*sin(2*pi*5*x)+randomGauss(0,0.1)\"\n"
+	"Colour: \"Red\"\n"
+	"Draw where: 0, 0, -2, 2, \"no\", \"Curve\", \"abs(self)>1\"\n"
+	"Colour: \"Black\"\n"
+	"Draw where:  0, 0, -2, 2, \"yes\", \"Curve\", \"not (abs(self)>1)\"\n"
+	"Remove\n"
 )
 ENTRY (L"Example 2")
 NORMAL (L"Draw the second half of a sound:")
-CODE (L"do (\"Draw where...\", 0, 0, -1, 1, \"no\", \"Curve\", \"x > xmin + (xmax - xmin) / 2\")")
+CODE (L"Draw where: 0, 0, -1, 1, \"no\", \"Curve\", \"x > xmin + (xmax - xmin) / 2\"")
 ENTRY (L"Example 3")
 NORMAL (L"Draw only positive amplitudes:")
-CODE (L"do (\"Draw where...\", 0, 0, -1, 1, \"no\", \"Curve\", \"self>0\")")
+CODE (L"Draw where: 0, 0, -1, 1, \"no\", \"Curve\", \"self>0\"")
 ENTRY (L"Example 4")
 NORMAL (L"Draw parts where pitch is larger than 300 Hz in red:")
 CODE (L"s = selected (\"Sound\")")
-CODE (L"p = do (\"To Pitch...\", 0, 75, 600)")
-CODE (L"pt = do (\"Down to PitchTier\")")
-CODE (L"selectObject (s)")
-CODE (L"do (\"Colour...\", \"Red\")")
-CODE (L"do (\"Draw where...\", 0, 0, -1, 1, \"yes\", \"Curve\", \"Object_'pt'(x) > 300\")")
-CODE (L"do (\"Colour...\", \"Black\")")
-CODE (L"do (\"Draw where...\", 0, 0, -1, 1, \"yes\", \"Curve\", \"not (Object_'pt'(x) > 300)\")")
+CODE (L"p = To Pitch: 0, 75, 600")
+CODE (L"pt = Down to PitchTier\"")
+CODE (L"selectObject: s")
+CODE (L"Colour: \"Red\"")
+CODE (L"Draw where: 0, 0, -1, 1, \"yes\", \"Curve\", \"Object_'pt'(x) > 300\"")
+CODE (L"Colour: \"Black\"")
+CODE (L"Draw where: 0, 0, -1, 1, \"yes\", \"Curve\", \"not (Object_'pt'(x) > 300)\"")
 MAN_END
 
-MAN_BEGIN (L"Sound: Fade in...", L"djmw", 20130410)
+MAN_BEGIN (L"Sound: Fade in...", L"djmw", 20140117)
 INTRO (L"A command to gradually increase the amplitude of a selected @Sound.")
 ENTRY (L"Settings")
 TAG (L"##Channel")
@@ -3369,11 +3368,11 @@ ENTRY (L"Cross-fading two sounds")
 NORMAL (L"The following script cross-fades two sounds s1 and s2 at time 1 second and leaves the result in s2.")
 CODE1 (L"crossFTime = 0.5")
 CODE1 (L"t = 1")
-CODE1 (L"do (\"Create Sound from formula...\", \"s1\", \"Mono\", 0, 2, 44100, \"sin(2*pi*500*x)\")")
-CODE1 (L"do (\"Fade out...\", 0, t-crossFTime/2, crossFTime, \"yes\")")
-CODE1 (L"do (\"Create Sound from formula...\", \"s2\", \"Mono\", 0, 2, 44100, \"sin(2*pi*1000*x)\")")
-CODE1 (L"do (\"Fade in...\", 0, t-crossFTime/2, crossFTime, \"yes\")")
-CODE1 (L"do (\"Formula...\", \"self+Sound_s1[]\")")
+CODE1 (L"Create Sound from formula: \"s1\", 1, 0, 2, 44100, \"sin(2*pi*500*x)\"")
+CODE1 (L"Fade out: 0, t-crossFTime/2, crossFTime, \"yes\"")
+CODE1 (L"Create Sound from formula: \"s2\", 1, 0, 2, 44100, \"sin(2*pi*1000*x)\"")
+CODE1 (L"Fade in.: 0, t-crossFTime/2, crossFTime, \"yes\"")
+CODE1 (L"Formula: \"self+Sound_s1[]\"")
 MAN_END
 
 MAN_BEGIN (L"Sound: Fade out...", L"djmw", 20121010)
@@ -3408,18 +3407,18 @@ NORMAL (L"The derivation of the filter coefficients %a__%i_ and %b__%j_ is "
 	"The gain of the filter is scaled to unity at the centre frequency.")
 MAN_END
 
-MAN_BEGIN (L"Sound: Play as frequency shifted...", L"djmw", 20121025)
-INTRO (L"Plays the selected @Sound with all frequencies shifted by the same amount. This command can be used to make "
-	"audible those sounds that are normally not audible by human beings, like ultrasound or infrasound.")
+MAN_BEGIN (L"Sound: Play as frequency shifted...", L"djmw", 20140106)
+INTRO (L"Plays the selected @Sound with all frequencies shifted by the same amount. This trick can be used to make "
+	"audible those sounds that are normally not audible at all by human beings, like for example ultrasounds or infrasounds.")
 ENTRY (L"Settings")
 TAG (L"##Shift by (Hz)")
 DEFINITION (L"the amount by which frequencies are shifted. A positive number shifts frequencies up, a negative number "
 	"shifts frequencies down. ")
 ENTRY (L"##Example")
-NORMAL (L"Rodents produce sounds with frequencies far outside the human audible range. Some meaningfull sqeeks "
-	"are present in the frequency range from 54 kHz up to sometimes 100kHz. By choosing a shift value of -54000 all " "frequencies will be shifted down by 54000 Hz. This means that now the rodents frequencies from 54000 Hz and up fall "
-	"in your audible domain. If the highest frequency you can hear is, say, 14000 Hz than you can now hear rodent's "
-	"frequencies between 54000 and 68000 Hz (=54000+14000).")
+NORMAL (L"Rodents produce sounds with frequencies far outside the human audible range. Some meaningfull sqeeks of these animals "
+	"are present in the frequency range from 54 kHz up to sometimes 100kHz. By choosing a shift value of -54000 Hz and a sampling "
+	"frequency of 44100 Hz, all frequencies between 54000 Hz and (54000+22050=) 76050 Hz  will be shifted down by 54000 Hz. The "
+	"rodents frequencies in the interval from 54000 Hz to 76050 Hz will theredore be mapped to the frequency interval between 0 and 22050 Hz. ")
 MAN_END
 
 MAN_BEGIN (L"Sound: To BarkFilter...", L"djmw", 20010404)
@@ -3445,7 +3444,7 @@ LIST_ITEM (L"2. We perform a filter bank analysis on a linear frequency scale. "
 	"Pitch: To FormantFilter...@ for details).")
 MAN_END
 
-MAN_BEGIN (L"Sound: Paint where...", L"djmw", 20130502)
+MAN_BEGIN (L"Sound: Paint where...", L"djmw", 20140509)
 INTRO (L"A command to paint only those parts of a @Sound where a condition holds. The painted area is the area "
 	"between the Sound and a horizontal line at a certain level.")
 ENTRY (L"Settings")
@@ -3474,47 +3473,47 @@ ENTRY (L"Example 1")
 NORMAL (L"The following script paints the area under a sine curve in red and the area above in green."
 	"For the first paint the horizontal line is at y=-1, for the second paint the line is at y=+1. "
 	"The formula always evaluates to true.")
-CODE (L"s = do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 10000, \"0.5*sin(2*pi*5*x)\")")
-CODE (L"do (\"Paint where...\", \"Red\", 0, 0, -1, 1, -1, \"yes\", \"1\")")
-CODE (L"do (\"Paint where...\", \"Green\", 0, 0, -1, 1, 1, \"no\", \"1\")")
+CODE (L"s = Create Sound from formula: \"s\", 1, 0, 1, 10000, \"0.5*sin(2*pi*5*x)\"")
+CODE (L"Paint where: \"Red\", 0, 0, -1, 1, -1, \"yes\", \"1\"")
+CODE (L"Paint where: \"Green\", 0, 0, -1, 1, 1, \"no\", \"1\"")
 SCRIPT (8, 5,
-	L"s = do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 10000, \"0.5*sin(2*pi*5*x)\")\n"
-	"do (\"Paint where...\", \"Red\", 0, 0, -1, 1, -1, \"no\", \"1\")\n"
-	"do (\"Paint where...\", \"Green\", 0, 0, -1, 1, 1, \"yes\", \"1\")\n"
-	"do (\"Remove\")\n")
+	L"s = Create Sound from formula: \"s\", 1, 0, 1, 10000, \"0.5*sin(2*pi*5*x)\"\n"
+	"Paint where: \"Red\", 0, 0, -1, 1, -1, \"no\", \"1\"\n"
+	"Paint where: \"Green\", 0, 0, -1, 1, 1, \"yes\", \"1\"\n"
+	"Remove\n")
 ENTRY (L"Example 2")
 NORMAL (L"The following script paints the area below zero in red and the area above in green."
 	"The horizontal line is now always at y=0 and we use the formula to differentiate the areas.")
-CODE (L"s = do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 10000, \"0.5*sin(2*pi*5*x)\")")
-CODE (L"do (\"Paint where...\", \"Red\", 0, 0, -1, 1, 0, \"no\", \"self>0\")")
-CODE (L"do (\"Paint where...\", \"Green\", 0, 0, -1, 1, 0, \"yes\", \"self<0\")")
+CODE (L"s = Create Sound from formula: \"s\", 1, 0, 1, 10000, \"0.5*sin(2*pi*5*x)\"")
+CODE (L"Paint where: \"Red\", 0, 0, -1, 1, 0, \"no\", \"self>0\"")
+CODE (L"Paint where: \"Green\", 0, 0, -1, 1, 0, \"yes\", \"self<0\"")
 SCRIPT (8, 5,
-	L"s = do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 10000, \"0.5*sin(2*pi*5*x)\")\n"
-	"do (\"Paint where...\", \"Red\", 0, 0, -1, 1, 0, \"no\", \"self<0\")\n"
-	"do (\"Paint where...\", \"Green\", 0, 0, -1, 1, 0, \"yes\", \"self>0\")\n"
-	"removeObject (s)\n")
+	L"s = Create Sound from formula: \"s\", 1, 0, 1, 10000, \"0.5*sin(2*pi*5*x)\"\n"
+	"Paint where: \"Red\", 0, 0, -1, 1, 0, \"no\", \"self<0\"\n"
+	"Paint where: \"Green\", 0, 0, -1, 1, 0, \"yes\", \"self>0\"\n"
+	"removeObject: s\n")
 ENTRY (L"Example 3")
 NORMAL (L"To give an indication that the area under a 1/x curve between the points %a and %b and the area "
 	"between %c and %d are equal if %b/%a = %d/%c. For example, for %a=1, %b=2, %c=4 and %d=8: ")
-CODE (L"do (\"Create Sound from formula...\", \"1dx\", \"Mono\", 0, 20, 100, \"1/x\")")
-CODE (L"do (\"Draw...\", 0, 20, 0, 1.5, \"yes\", \"Curve\")")
-CODE (L"do (\"Paint where...\", \"Grey\", 0, 20, 0, 1.5, 0, \"yes\", \"(x >= 1 and x <2) or (x>=4 and x<8)\")")
-CODE (L"do (\"One mark bottom...\", 1, \"yes\", \"yes\", \"no\", \"\")")
-CODE (L"do (\"One mark bottom...\", 2, \"yes\", \"yes\", \"no\", \"\")")
-CODE (L"do (\"One mark bottom...\", 4, \"yes\", \"yes\", \"no\", \"\")")
-CODE (L"do (\"One mark bottom...\", 8, \"yes\", \"yes\", \"no\", \"\")")
+CODE (L"Create Sound from formula: \"1dx\", \"Mono\", 0, 20, 100, \"1/x\"")
+CODE (L"Draw: 0, 20, 0, 1.5, \"yes\", \"Curve\"")
+CODE (L"Paint where: \"Grey\", 0, 20, 0, 1.5, 0, \"yes\", \"(x >= 1 and x <2) or (x>=4 and x<8)\"")
+CODE (L"One mark bottom: 1, \"yes\", \"yes\", \"no\", \"\"")
+CODE (L"One mark bottom: 2, \"yes\", \"yes\", \"no\", \"\"")
+CODE (L"One mark bottom: 4, \"yes\", \"yes\", \"no\", \"\"")
+CODE (L"One mark bottom: 8, \"yes\", \"yes\", \"no\", \"\"")
 SCRIPT (8, 5,
-	L"s = do (\"Create Sound from formula...\", \"1dx\", \"Mono\", 0, 20, 100, \"1/x\")\n"
-	"do (\"Draw...\", 0, 20, 0, 1.5, \"yes\", \"Curve\")\n"
-	"do (\"Paint where...\", \"Grey\", 0, 20, 0, 1.5, 0, \"yes\", \"(x >= 1 and x <2) or (x>=4 and x<8)\")\n"
-	"do (\"One mark bottom...\", 1, \"yes\", \"yes\", \"no\", \"\")\n"
-	"do (\"One mark bottom...\", 2, \"yes\", \"yes\", \"no\", \"\")\n"
-	"do (\"One mark bottom...\", 4, \"yes\", \"yes\", \"no\", \"\")\n"
-	"do (\"One mark bottom...\", 8, \"yes\", \"yes\", \"no\", \"\")\n"
-	"removeObject (s)\n")
+	L"s = Create Sound from formula: \"1dx\", \"Mono\", 0, 20, 100, \"1/x\"\n"
+	"Draw: 0, 20, 0, 1.5, \"yes\", \"Curve\"\n"
+	"Paint where: \"Grey\", 0, 20, 0, 1.5, 0, \"yes\", \"(x >= 1 and x <2) or (x>=4 and x<8)\"\n"
+	"One mark bottom: 1, \"yes\", \"yes\", \"no\", \"\"\n"
+	"One mark bottom: 2, \"yes\", \"yes\", \"no\", \"\"\n"
+	"One mark bottom: 4, \"yes\", \"yes\", \"no\", \"\"\n"
+	"One mark bottom: 8, \"yes\", \"yes\", \"no\", \"\"\n"
+	"removeObject: s\n")
 MAN_END
 
-MAN_BEGIN (L"Sounds: Paint enclosed...", L"djmw", 20130502)
+MAN_BEGIN (L"Sounds: Paint enclosed...", L"djmw", 20140509)
 INTRO (L"Paints the area between the two selected @@Sound at s. ")
 ENTRY (L"Settings")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (4), L""
@@ -3532,19 +3531,19 @@ TAG (L"##Vertical range")
 DEFINITION (L"defines the vertical limits, larger amplitudes will be clipped.")
 ENTRY (L"Example")
 NORMAL (L"The following script paints the area enclosed between a sine tone of 5 Hz and the straight line %y = %x/2.")
-CODE (L"s1 = do (\"Create Sound from formula...\", \"sine\", \"Mono\", 0, 1, 10000, \"1/2 * sin(2*pi*5*x)\")")
-CODE (L"s2 = do (\"Create Sound from formula...\", \"line\", \"Mono\", 0, 1, 10000, \"x / 2\")")
+CODE (L"s1 = Create Sound from formula: \"sine\", \"Mono\", 0, 1, 10000, \"1/2 * sin(2*pi*5*x)\"")
+CODE (L"s2 = Create Sound from formula: \"line\", \"Mono\", 0, 1, 10000, \"x / 2\"")
 CODE (L"plusObject (s1)")
-CODE (L"do (\"Paint enclosed...\", \"Grey\", 0, 0, -1, 1, \"yes\")")
+CODE (L"Paint enclosed: \"Grey\", 0, 0, -1, 1, \"yes\"")
 SCRIPT ( 4, 2,
-	 L"s1 = do (\"Create Sound from formula...\", \"sine\", \"Mono\", 0, 1, 10000, \"1/2 * sin(2*pi*5*x)\")\n"
-	"s2 = do (\"Create Sound from formula...\", \"line\", \"Mono\", 0, 1, 10000, \"x / 2\")\n"
-	"selectObject (s1, s2)\n"
-	"do (\"Paint enclosed...\", \"Grey\", 0, 0, -1, 1, \"yes\")\n"
-	"removeObject (s1, s2)\n")
+	 L"s1 = Create Sound from formula: \"sine\", \"Mono\", 0, 1, 10000, \"1/2 * sin(2*pi*5*x)\"\n"
+	"s2 = Create Sound from formula: \"line\", \"Mono\", 0, 1, 10000, \"x / 2\"\n"
+	"selectObject: s1, s2\n"
+	"Paint enclosed: \"Grey\", 0, 0, -1, 1, \"yes\"\n"
+	"removeObject: s1, s2\n")
 MAN_END
 
-MAN_BEGIN (L"Sound: To Polygon...", L"djmw", 20130502)
+MAN_BEGIN (L"Sound: To Polygon...", L"djmw", 20140509)
 INTRO (L"A command that creates a @@Polygon@ from a selected @@Sound@, where the Polygon's "
 	" points are defined by the (%time, %amplitude) pairs of the sound. ")
 ENTRY (L"Settings")
@@ -3559,22 +3558,22 @@ DEFINITION (L"defines the y-value of the first and last point of the Polygon. Th
 	" draw a closed Polygon with the horizontal connection line at any position you like. ")
 ENTRY (L"Example")
 NORMAL (L"The following script paints the area under a sound curve in red and the area above in green.")
-CODE (L"s = do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 10000, \"0.5*sin(2*pi*5*x)\")")
+CODE (L"s = Create Sound from formula: \"s\", 1, 0, 1, 10000, \"0.5*sin(2*pi*5*x)\"")
 CODE (L"\\# Connection y-value is at amplitude -1: area under the curve.")
-CODE (L"p1 = do (\"To Polygon...\", 1, 0, 0, -1, 1, -1)")
-CODE (L"do (\"Paint...\", \"{1,0,0}\", 0, 0, -1, 1)")
-CODE (L"selectObject (s)")
+CODE (L"p1 = To Polygon: 1, 0, 0, -1, 1, -1")
+CODE (L"Paint: \"{1,0,0}\", 0, 0, -1, 1")
+CODE (L"selectObject: s")
 CODE (L"\\# Connection y-value is now at amplitude 1: area above the curve.")
-CODE (L"p2 = do (\"To Polygon...\", 1, 0, 0, -1, 1, 1)")
-CODE (L"do (\"Paint...\", \"{0,1,0}\", 0, 0, -1, 1)")
+CODE (L"p2 = To Polygon: 1, 0, 0, -1, 1, 1")
+CODE (L"Paint: \"{0,1,0}\", 0, 0, -1, 1")
 SCRIPT (4.5, 2,
-	L"s = do (\"Create Sound from formula...\", \"s\", \"Mono\", 0, 1, 10000, \"0.5*sin(2*pi*5*x)\")\n"
-	"p1 = do (\"To Polygon...\", 1, 0, 0, -1, 1, -1)\n"
-	"do (\"Paint...\", \"{1,0,0}\", 0, 0, -1, 1)\n"
-	"selectObject (s)\n"
-	"p2 = do (\"To Polygon...\", 1, 0, 0, -1, 1, 1)\n"
-	"do (\"Paint...\", \"{0,1,0}\", 0, 0, -1, 1)\n"
-	"removeObject (p2, p1, s)\n"
+	L"s = Create Sound from formula: \"s\", 1, 0, 1, 10000, \"0.5*sin(2*pi*5*x)\"\n"
+	"p1 = To Polygon: 1, 0, 0, -1, 1, -1\n"
+	"Paint: \"{1,0,0}\", 0, 0, -1, 1\n"
+	"selectObject: s\n"
+	"p2 = To Polygon: 1, 0, 0, -1, 1, 1\n"
+	"Paint: \"{0,1,0}\", 0, 0, -1, 1\n"
+	"removeObject: p2, p1, s\n"
 )
 MAN_END
 
@@ -3997,7 +3996,7 @@ NORMAL (L"In a quantile-quantile plot the quantiles of the data in the first sel
 	"same distribution, the points should fall approximately along the reference line.")
 MAN_END
 
-MAN_BEGIN (L"Table: Bar plot where...", L"djmw", 20130624)
+MAN_BEGIN (L"Table: Bar plot where...", L"djmw", 20140509)
 INTRO (L"Draws a bar plot from data in one or more columns of the selected @Table. In a bar plot the horizontal axis has nominal values (labels). ")
 ENTRY (L"Settings")
 SCRIPT (6, Manual_SETTINGS_WINDOW_HEIGHT (10), L""
@@ -4052,33 +4051,47 @@ NORMAL (L"As you can see the labels in the first column are very long texts and
 	"the viewport. ")
 NORMAL (L"Sometimes you need to plot only a part of the Table and for the selection of this part, the \"Formula\" field can be "
 	"used. Since we only have a small table we put a \"1\" in this field which always evaluates to true. In effect, all the rows will be selected. The following script line will produce the picture below.")
-CODE (L"do (\"Bar plot where...\", \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"0.9 0.5\", 15.0, \"yes\", \"1\")")
-SCRIPT (5, 3,  L"h1h2 = do (\"Create H1H2 table (Esposito 2006)\")\n"
-	"do (\"Font size...\", 10)\n"
-	"do (\"Bar plot where...\", \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"0.9 0.5\", 15.0, \"yes\", \"1\")\n"
-	"removeObject (h1h2)\n")
+CODE (L"Bar plot where: \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"0.9 0.5\", 15.0, \"yes\", \"1\"")
+SCRIPT (5, 3,  L"h1h2 = Create H1H2 table (Esposito 2006)\n"
+	"Font size: 10\n"
+	"Bar plot where: \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"0.9 0.5\", 15.0, \"yes\", \"1\"\n"
+	"removeObject: h1h2\n")
 NORMAL (L"The essentials of the bart plot in their paper are perfectly reproduced in the figure above. If you want the bars within a group to be placed somewhat more apart say 0.2 (times the bar width) you can set the \"Distance between bars in a group\" to a value of 0.2:")
-CODE (L"do (\"Bar plot where...\", \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.2, \"0.9 0.5\", 15.0, \"yes\", \"1\")")
-SCRIPT (5, 3,  L"h1h2 = do (\"Create H1H2 table (Esposito 2006)\")\n"
-	"do (\"Font size...\", 10)\n"
-	"do (\"Bar plot where...\", \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.2, \"0.9 0.5\", 15.0, \"yes\", \"1\")\n"
-	"removeObject (h1h2)\n")
+CODE (L"Bar plot where: \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.2, \"0.9 0.5\", 15.0, \"yes\", \"1\"")
+SCRIPT (5, 3,  L"h1h2 = Create H1H2 table (Esposito 2006)\n"
+	"Font size: 10\n"
+	"Bar plot where: \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.2, \"0.9 0.5\", 15.0, \"yes\", \"1\"\n"
+	"removeObject: h1h2\n")
 NORMAL (L"Of course we can also work with colours and we can add vertical marks as the following sriptlet shows")
-CODE (L"do (\"Bar plot where...\", \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"Green Red\", 15.0, \"yes\", \"1\")")
-CODE (L"do (\"Marks left every...\", 1, 5, 1, 1, 1)")
-CODE (L"do (\"Text left...\", 1, \"H__1_-H__2_ (dB)\")")
-SCRIPT (5, 3,  L"h1h2 = do (\"Create H1H2 table (Esposito 2006)\")\n"
-	"do (\"Font size...\", 10)\n"
-	"do (\"Bar plot where...\", \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"Green Red\", 15.0, \"yes\", \"1\")\n"
-	"do (\"Marks left every...\", 1, 5, 1, 1, 1)\n"
-	"do (\"Text left...\", 1, \"H__1_-H__2_ (dB)\")\n"
-	"removeObject (h1h2)\n")
-MAN_END
-
-MAN_BEGIN (L"Table: Line graph where...", L"djmw", 20130624)
+CODE (L"Bar plot where: \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"Green Red\", 15.0, \"yes\", \"1\"")
+CODE (L"Marks left every: 1, 5, 1, 1, 1")
+CODE (L"Text left: 1, \"H__1_-H__2_ (dB)\"")
+SCRIPT (5, 3,  L"h1h2 = Create H1H2 table (Esposito 2006)\n"
+	"Font size: 10\n"
+	"Bar plot where: \"Modal Breathy\", -10, 20, \"Language\", 1.0, 1.0, 0.0, \"Green Red\", 15.0, \"yes\", \"1\"\n"
+	"Marks left every: 1, 5, 1, 1, 1\n"
+	"Text left: 1, \"H__1_-H__2_ (dB)\"\n"
+	"removeObject: h1h2\n")
+MAN_END
+
+MAN_BEGIN (L"Table: Box plots where...", L"djmw", 20140509)
+INTRO (L"A command to draw @@box plot at s from the data in a column of the selected @Table object.")
+ENTRY (L"Example")
+NORMAL (L"To draw separate box plots for the male, female and children F0 for the @@Peterson & Barney (1952)@ data: ")
+CODE (L"Create formant table (Peterson & Barney 1952)")
+CODE (L"Box plots where: \"F0\", \"Type\", 70, 400, \"1\"")
+CODE (L"Text left: \"yes\", \"F0 (Hz)\"")
+SCRIPT (5,3, L"pb = Create formant table (Peterson & Barney 1952)\n"
+	"Box plots where: \"F0\", \"Type\", 70, 400, \"yes\", \"1\"\n"
+	"Text left: \"yes\", \"F0 (Hz)\"\n"
+	"removeObject: pb\n"
+)
+MAN_END
+
+MAN_BEGIN (L"Table: Line graph where...", L"djmw", 20140509)
 INTRO (L"Draws a line graph from the data in a column of the selected @Table. In a line plot the horizontal axis can have a nominal scale or a numeric scale. The data point are connected by line segments.")
 ENTRY (L"Settings")
-SCRIPT (6, Manual_SETTINGS_WINDOW_HEIGHT (8), L""
+SCRIPT (7, Manual_SETTINGS_WINDOW_HEIGHT (8), L""
 	Manual_DRAW_SETTINGS_WINDOW ("Table: Line graph where", 8)
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Vertical column", "")
 	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (=autoscaling)")
@@ -4092,7 +4105,7 @@ SCRIPT (6, Manual_SETTINGS_WINDOW_HEIGHT (8), L""
 TAG (L"##Vertical column")
 DEFINITION (L"The column whose data points you want to plot.")
 TAG (L"##Vertical range")
-DEFINITION (L"determine the lower and upper limit of the plot.")
+DEFINITION (L"determine the lower and upper limits of the plot.")
 TAG (L"##Horizontal column")
 DEFINITION (L"determines the horizontal scale. If you leave it empty, or, if the (selected part of the) selected column contains nominal values, i.e. the values are not numeric but text, the horizontal "
 	"distance between the data points will be constant (i.e. 1) and the nominal values (texts) will be put as labels at the bottom of the horizontal axis. "
@@ -4117,52 +4130,103 @@ CODE (L"  2.5   0.29      0.10")
 CODE (L"  7.5   0.12      0.02")
 CODE (L" 17.5   0.10      0.02")
 NORMAL (L"We can reproduce fig. 3 from Ganong (1980) with the following script, where we labeled the word - nonword curve with \"wn\" and the nonword - word curve with \"nw\". We deselect \"Garnish\" because we want to put special marks at the bottom.")
-CODE (L"do (\"Dotted line\")\n")
-CODE (L"do (\"Line graph where...\", \"dash-tash\", 0, 1, \"VOT\", -20, 20, \"wn\", 0, 0, \"1\")")
-CODE (L"do (\"Dashed line\")\n")
-CODE (L"do (\"Line graph where...\", \"dask-task\", 0, 1, \"VOT\", -20, 20, \"nw\", 0, 0, \"1\")")
-CODE (L"do (\"Draw inner box\")")
-CODE (L"do (\"One mark bottom...\", 2.5, 0, 1, 0, \"+2.5\")")
-CODE (L"do (\"One mark bottom...\", -2.5, 1, 1, 0, \"\")")
-CODE (L"do (\"One mark bottom...\", -7.5,1, 1, 0, \"\")")
-CODE (L"do (\"One mark bottom...\", 7.5, 0, 1, 0, \"+7.5\")")
-CODE (L"do (\"One mark bottom...\", 2.5, 0, 0, 0, \"+2.5\")")
-CODE (L"do (\"One mark bottom...\", -20, 0, 0, 0, \"Short VOT\")")
-CODE (L"do (\"One mark bottom...\", 20, 0, 0, 0, \"Long VOT\")")
-CODE (L"do (\"Text bottom...\", 1, \"VOT (ms)\")")
-CODE (L"do (\"Marks left every...\", 1, 0.2, 1, 1, 0)")
-CODE (L"do (\"Text left...\", 1, \"Prop. of voiced responses\")")
-
-SCRIPT (5,3, L"ganong = do (\"Create Table (Ganong 1980)\")\n"
-	"do (\"Dotted line\")\n"
-	"do (\"Line graph where...\", \"dash-tash\", 0, 1, \"VOT\", -20, 20, \"wn\", 0, 0, \"1\")\n"
-	"do (\"Dashed line\")\n"
-	"do (\"Line graph where...\", \"dask-task\", 0, 1, \"VOT\", -20, 20, \"nw\", 0, 0, \"1\")\n"
-	"do (\"Draw inner box\")\n"
-	"do (\"One mark bottom...\", 2.5, 0, 1, 0, \"+2.5\")\n"
-	"do (\"One mark bottom...\", -2.5, 1, 1, 0, \"\")\n"
-	"do (\"One mark bottom...\", -7.5,1, 1, 0, \"\")\n"
-	"do (\"One mark bottom...\", 7.5, 0, 1, 0, \"+7.5\")\n"
-	"do (\"One mark bottom...\", 2.5, 0, 0, 0, \"+2.5\")\n"
-	"do (\"One mark bottom...\", -20, 0, 0, 0, \"Short VOT\")\n"
-	"do (\"One mark bottom...\", 20, 0, 0, 0, \"Long VOT\")\n"
-	"do (\"Text bottom...\", 1, \"VOT (ms)\")\n"
-	"do (\"Marks left every...\", 1, 0.2, 1, 1, 0)\n"
-	"do (\"Text left...\", 1, \"Prop. of voiced responses\")\n"
-	"removeObject (ganong)\n"
+CODE (L"Dotted line\n")
+CODE (L"Line graph where: \"dash-tash\", 0, 1, \"VOT\", -20, 20, \"wn\", 0, 0, \"1\"")
+CODE (L"Dashed line\n")
+CODE (L"Line graph where: \"dask-task\", 0, 1, \"VOT\", -20, 20, \"nw\", 0, 0, \"1\"")
+CODE (L"Draw inner box")
+CODE (L"One mark bottom: 2.5, 0, 1, 0, \"+2.5\"")
+CODE (L"One mark bottom: -2.5, 1, 1, 0, \"\"")
+CODE (L"One mark bottom: -7.5,1, 1, 0, \"\"")
+CODE (L"One mark bottom: 7.5, 0, 1, 0, \"+7.5\"")
+CODE (L"One mark bottom: 2.5, 0, 0, 0, \"+2.5\"")
+CODE (L"One mark bottom: -20, 0, 0, 0, \"Short VOT\"")
+CODE (L"One mark bottom: 20, 0, 0, 0, \"Long VOT\"")
+CODE (L"Text bottom: 1, \"VOT (ms)\"")
+CODE (L"Marks left every: 1, 0.2, 1, 1, 0")
+CODE (L"Text left: 1, \"Prop. of voiced responses\"")
+
+SCRIPT (5,3, L"ganong = Create Table (Ganong 1980)\n"
+	"Dotted line\n"
+	"Line graph where: \"dash-tash\", 0, 1, \"VOT\", -20, 20, \"wn\", 0, 0, \"1\"\n"
+	"Dashed line\n"
+	"Line graph where: \"dask-task\", 0, 1, \"VOT\", -20, 20, \"nw\", 0, 0, \"1\"\n"
+	"Draw inner box\n"
+	"One mark bottom: 2.5, 0, 1, 0, \"+2.5\"\n"
+	"One mark bottom: -2.5, 1, 1, 0, \"\"\n"
+	"One mark bottom: -7.5,1, 1, 0, \"\"\n"
+	"One mark bottom: 7.5, 0, 1, 0, \"+7.5\"\n"
+	"One mark bottom: 2.5, 0, 0, 0, \"+2.5\"\n"
+	"One mark bottom: -20, 0, 0, 0, \"Short VOT\"\n"
+	"One mark bottom: 20, 0, 0, 0, \"Long VOT\"\n"
+	"Text bottom: 1, \"VOT (ms)\"\n"
+	"Marks left every: 1, 0.2, 1, 1, 0\n"
+	"Text left: 1, \"Prop. of voiced responses\"\n"
+	"removeObject: ganong\n"
 )
 NORMAL (L"As an example of what happens if you don't supply an argument for the \"Horizontal column\" we will use the same table as for the previous plot. However the resulting plot may not be as meaningful (note that the horizontal nominal scale makes all points equidistant in the horizontal direction.)")
-CODE (L"do (\"Dotted line\")\n")
-CODE (L"do (\"Line graph where...\", \"dash-tash\", 0, 1, \"\", 0, 0, \"wn\", 0, 1, \"1\")")
-CODE (L"do (\"One mark bottom...\", 1, 0, 1, 0, \"Short VOT\")")
-SCRIPT (5,3, L"ganong = do (\"Create Table (Ganong 1980)\")\n"
-	"do (\"Dotted line\")\n"
-	"do (\"Line graph where...\", \"dash-tash\", 0, 1, \"\", 0, 0, \"wn\", 0, 1, \"1\")\n"
-	"do (\"One mark bottom...\", 1, 0, 1, 0, \"Short VOT\")\n"
-	"removeObject (ganong)\n"
+CODE (L"Dotted line\")\n")
+CODE (L"Line graph where: \"dash-tash\", 0, 1, \"\", 0, 0, \"wn\", 0, 1, \"1\"")
+CODE (L"One mark bottom: 1, 0, 1, 0, \"Short VOT\"")
+SCRIPT (5,3, L"ganong = Create Table (Ganong 1980)\n"
+	"Dotted line\n"
+	"Line graph where: \"dash-tash\", 0, 1, \"\", 0, 0, \"wn\", 0, 1, \"1\"\n"
+	"One mark bottom: 1, 0, 1, 0, \"Short VOT\"\n"
+	"removeObject: ganong\n"
 )
 MAN_END
 
+MAN_BEGIN (L"Table: Horizontal error bars plot where...", L"djmw", 20131220)
+INTRO (L"Draws horizontal lines that represent the error intervals of a data column from the selected @@Table at .")
+NORMAL (L"This command behaves analogous to @@Table: Vertical error bars plot where... at .")
+MAN_END
+
+MAN_BEGIN (L"Table: Horizontal error bars plot...", L"djmw", 20131220)
+INTRO (L"Draws horizontal lines that represent the error intervals of a data column from the selected @@Table at .")
+NORMAL (L"This command behaves analogous to @@Table: Vertical error bars plot where... at .")
+MAN_END
+
+MAN_BEGIN (L"Table: Vertical error bars plot...", L"djmw", 20131223)
+INTRO (L"Draws vertical lines that represent the error intervals of a data column from the selected @@Table at .")
+NORMAL (L"For more info see @@Table: Vertical error bars plot where...@")
+MAN_END
+
+MAN_BEGIN (L"Table: Vertical error bars plot where...", L"djmw", 20131223)
+INTRO (L"Draws vertical lines that represent the error intervals of a data column from the selected @@Table at .")
+ENTRY (L"Settings")
+SCRIPT (6, Manual_SETTINGS_WINDOW_HEIGHT (9), L""
+	Manual_DRAW_SETTINGS_WINDOW ("Table: Vertical confidence intervals plot where", 9)
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Horizontal column", "")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Horizontal range", "0.0", "0.0 (=autoscaling)")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Vertical column", "")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (=autoscaling)")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Lower error value column", "")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Upper error value column", "")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Bar size (mm)", "1.0")
+	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Garnish", 1)
+	Manual_DRAW_SETTINGS_WINDOW_TEXT("Formula", "1; (=everything)")
+)
+TAG (L"##Horizontal column")
+DEFINITION (L"determines the data along the horizontal axis.")
+TAG (L"##Horizontal range")
+DEFINITION (L"determines the lower and upper limits of the plot.")
+TAG (L"##Vertical column")
+DEFINITION (L"determines the data along the horizontal axis.")
+TAG (L"##Vertical range")
+DEFINITION (L"determines the lower and upper limits of the plot.")
+TAG (L"##Lower error value column, Upper error value column")
+DEFINITION (L"determine the size of the vertical lines that will be drawn. These lines are drawn between the points (%x,%y-%low) and (%x, %y+%up), "
+	"where %x and %y are the values from the %%horizontal column% and the %%vertical column%, respectively, and, %low and %up are the corresponding values "
+	"in the %%lower error value column% and the %%upper error value column%, respectively. If either of these column names is not given the corresponding values (%low and/or %up) will taken as zero. This makes it possible to draw one-sided and two-sided error bars. If your "
+	"errors are symmetric around the y-position, your table only needs one column and you can supply the name of this column in both fields.")
+TAG (L"##Bar size (mm)")
+DEFINITION (L"determines the width of the horizontal bars or whishers at the lower an postion of the drawn line. ")
+TAG (L"##Garnish")
+DEFINITION (L"determines whether or not some decoration is drawn.")
+TAG (L"##Formula")
+DEFINITION (L"can be used to supply an expression to select only those rows for plotting where the expression evaluates to %%true%. A 1 value always evaluates to %%true%.")
+
+MAN_END
 
 MAN_BEGIN (L"Table: Get median absolute deviation...", L"djmw", 20120405)
 INTRO (L"Get the median absolute deviation (MAD) of the column in the selected @@Table@ (adjusted by a scale factor).")
@@ -4190,7 +4254,7 @@ TAG (L"##Table with Tukey's post-hoc test")
 DEFINITION (L"if checked, a Table with Tukey's HSD tests will be created. Each value in this Table measures the probability that the corresponding difference between the level means happened by chance. The test compares all possible level means and is based on the studentized range distribution.")
 MAN_END
 
-MAN_BEGIN (L"Table: Report two-way anova...", L"djmw", 20130410)
+MAN_BEGIN (L"Table: Report two-way anova...", L"djmw", 20140117)
 INTRO (L"Performs a two-way analysis of variance on the data in one column of a selected %%fully factorial% @@Table@ and reports the fixed-effects anova table in the info window. ")
 ENTRY (L"Settings")
 TAG (L"##Column with data#")
@@ -4205,8 +4269,8 @@ ENTRY (L"Example")
 NORMAL (L"Suppose you want to check if fundamental frequency depends on the type of vowel and speaker type. We will use the "
 	"@@Create formant table (Peterson & Barney 1952)|Peterson & Barney@ vowel data set to illustrate this. "
 	"The following script will first create the data set and then produce the two-way anova report." )
-CODE (L"do (\"Create formant table (Peterson & Barney 1952)\")")
-CODE (L"do (\"Report two-way anova...\", \"F0\", \"Vowel\", \"Type\")")
+CODE (L"Create formant table (Peterson & Barney 1952)")
+CODE (L"Report two-way anova: \"F0\", \"Vowel\", \"Type\"")
 NORMAL (L"This will produce the following anova table in the info window:")
 CODE (L"Two-way analysis of \"F0\" by \"Vowel\" and \"Type\".")
 CODE (L"")
@@ -4357,7 +4421,7 @@ NORMAL (L"The first bar for the %k-th row starts at:")
 FORMULA (L"%x1 = %hoffset \\.c %width + (%k - 1) \\.c (1 + %interbar) \\.c %width")
 MAN_END
 
-MAN_BEGIN (L"TableOfReal: Select columns where row...", L"djmw", 20130410)
+MAN_BEGIN (L"TableOfReal: Select columns where row...", L"djmw", 20140117)
 INTRO (L"Copy columns from the selected @TableOfReal object to a new "
 	"TableOfReal object.")
 ENTRY (L"Settings")
@@ -4371,14 +4435,14 @@ DEFINITION (L"specifies a condition for the selection of rows. If the "
 	"in this row will be copied. See @@Matrix: Formula...@ for the kind of "
 	"expressions that can be used here.")
 ENTRY (L"Examples")
-CODE (L"do (\"Select columns where row...\", \"1 2 3\", \"1\")")
-CODE (L"do (\"Select columns where row...\", \"1 : 3\", \"1\")")
+CODE (L"Select columns where row: \"1 2 3\", \"1\"")
+CODE (L"Select columns where row: \"1 : 3\", \"1\"")
 NORMAL (L"Two alternative expressions to copy the first three columns to a new table "
 	"with the same number of rows.")
-CODE (L"do (\"Select columns where row...\", \"3 : 1\", \"1\")")
+CODE (L"Select columns where row: \"3 : 1\", \"1\"")
 NORMAL (L"Copy the first three columns to a new table with the same number of "
 	"rows. The new table will have the 3 columns reversed.")
-CODE (L"do (\"Select columns where row...\", \"1:6 9:11\", \"self[row,8]>0\")")
+CODE (L"Select columns where row: \"1:6 9:11\", \"self[row,8]>0\"")
 NORMAL (L"Copy the first six columns and columns 9, 10, and 11 to a new table. "
 	"Copy only elements from rows where the element in column 8 is greater "
 	"than zero.")
@@ -4575,7 +4639,7 @@ NORMAL (L"For example, when the vector #y equals the first column of #Y and "
 	"correlation coefficient.")
 MAN_END
 
-MAN_BEGIN (L"TableOfReal: To TableOfReal (means by row labels)...", L"djmw", 20130410)
+MAN_BEGIN (L"TableOfReal: To TableOfReal (means by row labels)...", L"djmw", 20140117)
 INTRO (L"A command that appears in the ##Multivariate statistics# menu if you select a @@TableOfReal at . "
 	"It calculates the multivariate means for the different row labels from the selected TableOfReal.")
 ENTRY (L"Setting")
@@ -4585,12 +4649,12 @@ DEFINITION (L"when %off, then for a table with %n rows and %m different labels (
 	"in each row.")
 ENTRY (L"Example")
 NORMAL (L"The following commands")
-CODE (L"do (\"@@Create TableOfReal (Pols 1973)...@\", \"no\")")
-CODE (L"do (\"To TableOfReal (means by row labels)...\", 0)")
+CODE (L"@@Create TableOfReal (Pols 1973)...|Create TableOfReal (Pols 1973)@: \"no\"")
+CODE (L"To TableOfReal (means by row labels): 0")
 NORMAL (L"will result in a new TableOfReal that has 12 rows. Each row will contain the mean F1, F2 and F3 values for a particular vowel. These means "
 	" were obtained from 50 representations of that vowel.")
 NORMAL (L"If we had chosen the %expansion:")
-CODE (L"do (\"To TableOfReal (means by row labels)...\", \"yes\")")
+CODE (L"To TableOfReal (means by row labels): \"yes\"")
 NORMAL (L"the resulting TableOfReal would have had 600 rows. This representation  comes in handy when, for example, you have to calculate deviations from the mean.")
 MAN_END
 
@@ -4628,10 +4692,10 @@ INTRO (L"An Editor for generating vowel-like @@sound|Sound at s from mouse movement
 ENTRY (L"How to get a sound")
 NORMAL (L"With the mouse button down, you can move the mouse cursor around in the plane "
 	"spanned by the first two formants. While you move the cursor around, the positions you trace will be "
-	"indicated by  blue dots. After you release the mouse button, the color of the trajectory will change "
-	"to black and you will hear the vowel-like sound whose "
-	"first two formants follow this trajectory. The small bars on the trajectory are time markers. With "
-	"default settings time markers are at 50 milliseconds apart and they may give you an indication of the speed you traversed the trajectory.")
+	"indicated by blue dots. After you release the mouse button, the color of the trajectory will change "
+	"to black. Next you will hear the vowel-like sound whose "
+	"first two formants follow this trajectory. (The small bars on the trajectory are time markers. With "
+	"default settings, time markers are at 50 milliseconds apart and they may give you an indication of the speed by which you traversed the trajectory.)")
 ENTRY (L"The interface")
 NORMAL (L"In the lower part of the editor a number of buttons and fields are displayed.")
 TAG (L"##Play")
diff --git a/dwtools/octave-workspace b/dwtools/octave-workspace
new file mode 100644
index 0000000..8823136
Binary files /dev/null and b/dwtools/octave-workspace differ
diff --git a/dwtools/praat_BSS_init.cpp b/dwtools/praat_BSS_init.cpp
index 70c6de5..75798a9 100644
--- a/dwtools/praat_BSS_init.cpp
+++ b/dwtools/praat_BSS_init.cpp
@@ -1,6 +1,6 @@
 /* praat_BSS_init.c
  *
- * Copyright (C) 2010-2011 David Weenink
+ * Copyright (C) 2010-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -41,13 +41,13 @@ void praat_TableOfReal_init3 (ClassInfo klas);
 FORM (EEG_to_CrossCorrelationTable, L"EEG: To CrossCorrelationTable", L"EEG: To CrossCorrelationTable...")
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
-	REAL (L"Lag time (s)", L"0.05")
+	REAL (L"Lag step (s)", L"0.05")
 	TEXTFIELD (L"Channel ranges", L"1:64")
 	LABEL (L"", L"To supply rising or falling ranges, use e.g. 2:6 or 5:3.")
 	OK
 DO
 	double startTime = GET_REAL (L"left Time range"), endTime = GET_REAL (L"right Time range");
-	double lagTime = GET_REAL (L"Lag time");
+	double lagTime = GET_REAL (L"Lag step");
 	const wchar_t *channelRanges = GET_STRING (L"Channel ranges");
 	LOOP {
 		iam (EEG);
@@ -75,7 +75,7 @@ END
 FORM (EEG_to_CrossCorrelationTables, L"EEG: To CrossCorrelationTables", L"EEG: To CrossCorrelationTables...")
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
-	POSITIVE (L"Lag time (s)", L"0.02")
+	POSITIVE (L"Lag step (s)", L"0.02")
 	NATURAL (L"Number of cross-correlations", L"40")
 	LABEL (L"", L"To supply rising or falling ranges, use e.g. 2:6 or 5:3.")
 	TEXTFIELD (L"Channel ranges", L"1:64")
@@ -84,7 +84,7 @@ DO
 	LOOP {
 		iam (EEG);
 		autoCrossCorrelationTables thee = EEG_to_CrossCorrelationTables (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
-			GET_REAL (L"Lag time"), GET_INTEGER (L"Number of cross-correlations"), GET_STRING (L"Channel ranges"));
+			GET_REAL (L"Lag step"), GET_INTEGER (L"Number of cross-correlations"), GET_STRING (L"Channel ranges"));
 		praat_new (thee.transfer(), my name);
 	}
 END
@@ -93,7 +93,7 @@ FORM (EEG_to_EEG_bss, L"EEG: To EEG (bss)", L"EEG: To EEG (bss)...")
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
 	NATURAL (L"Number of cross-correlations", L"40")
-	POSITIVE (L"Lag times (s)", L"0.002")
+	POSITIVE (L"Lag step (s)", L"0.002")
 	LABEL (L"", L"To supply rising or falling ranges, use e.g. 2:6 or 5:3.")
 	TEXTFIELD (L"Channel ranges", L"1:64")
 	LABEL (L"", L"Pre-whitening parameters")
@@ -113,7 +113,7 @@ DO
 	LOOP {
 		iam (EEG);
 		autoEEG thee = EEG_to_EEG_bss (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
-			GET_INTEGER (L"Number of cross-correlations"), GET_REAL (L"Lag times"), GET_STRING (L"Channel ranges"),
+			GET_INTEGER (L"Number of cross-correlations"), GET_REAL (L"Lag step"), GET_STRING (L"Channel ranges"),
 			whiteningMethod, GET_INTEGER (L"Diagonalization method"),
 			GET_INTEGER (L"Maximum number of iterations"), GET_REAL (L"Tolerance"));
 		praat_new (thee.transfer(), my name, L"_bss");
@@ -280,10 +280,10 @@ END
 FORM (Sound_to_CrossCorrelationTable, L"Sound: To CrossCorrelationTable", L"Sound: To CrossCorrelationTable...")
     REAL (L"left Time range (s)", L"0.0")
     REAL (L"right Time range (s)", L"10.0")
-    REAL (L"Lag time (s)", L"0.0")
+    REAL (L"Lag step (s)", L"0.0")
     OK
 DO
-	double lagTime = fabs (GET_REAL (L"Lag time"));
+	double lagTime = fabs (GET_REAL (L"Lag step"));
     LOOP {
         iam (Sound);
         praat_new (Sound_to_CrossCorrelationTable (me, GET_REAL (L"left Time range"),
@@ -294,7 +294,7 @@ END
 FORM (Sounds_to_CrossCorrelationTable_combined, L"Sound: To CrossCorrelationTable (combined)", 0)
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
-	REAL (L"Lag time (s)", L"0.0")
+	REAL (L"Lag step (s)", L"0.0")
 	OK
 DO
 	Sound s1 = NULL, s2 = NULL;
@@ -304,7 +304,7 @@ DO
 	}
 	Melder_assert (s1 != NULL && s2 != NULL);
 	autoCrossCorrelationTable thee = Sounds_to_CrossCorrelationTable_combined (s1, s2, GET_REAL (L"left Time range"),
-		GET_REAL (L"right Time range"), GET_REAL (L"Lag time"));
+		GET_REAL (L"right Time range"), GET_REAL (L"Lag step"));
 	praat_new (thee.transfer(), s1 -> name, L"_", s2 -> name, L"_cc");
 END
 
@@ -419,7 +419,7 @@ FORM (Sound_to_MixingMatrix, L"Sound: To MixingMatrix", 0)
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
 	NATURAL (L"Number of cross-correlations", L"40")
-	POSITIVE (L"Lag times (s)", L"0.002")
+	POSITIVE (L"Lag step (s)", L"0.002")
 	LABEL (L"", L"Iteration parameters")
 	NATURAL (L"Maximum number of iterations", L"100")
 	POSITIVE (L"Tolerance", L"0.001")
@@ -432,7 +432,7 @@ DO
 		iam (Sound);
 		praat_new (Sound_to_MixingMatrix (me,
 			GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Number of cross-correlations"),
-			GET_REAL (L"Lag times"), GET_INTEGER (L"Maximum number of iterations"), GET_REAL (L"Tolerance"),
+			GET_REAL (L"Lag step"), GET_INTEGER (L"Maximum number of iterations"), GET_REAL (L"Tolerance"),
 			GET_INTEGER (L"Diagonalization method")), my name);
 	}
 END
@@ -441,12 +441,12 @@ FORM (Sound_to_CrossCorrelationTables, L"Sound: To CrossCorrelationTables", 0)
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
 	NATURAL (L"Number of cross-correlations", L"40")
-	POSITIVE (L"Lag times (s)", L"0.002")
+	POSITIVE (L"Lag step (s)", L"0.002")
 	OK
 DO
 	LOOP {
 		iam (Sound);
-		praat_new (Sound_to_CrossCorrelationTables (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_REAL (L"Lag times"), GET_INTEGER (L"Number of cross-correlations")), my name);
+		praat_new (Sound_to_CrossCorrelationTables (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_REAL (L"Lag step"), GET_INTEGER (L"Number of cross-correlations")), my name);
 	}
 END
 
@@ -454,7 +454,7 @@ FORM (Sound_to_Sound_bss, L"Sound: To Sound (blind source separation)", L"Sound:
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
 	NATURAL (L"Number of cross-correlations", L"40")
-	POSITIVE (L"Lag times (s)", L"0.002")
+	POSITIVE (L"Lag step (s)", L"0.002")
 	LABEL (L"", L"Iteration parameters")
 	NATURAL (L"Maximum number of iterations", L"100")
 	POSITIVE (L"Tolerance", L"0.001")
@@ -466,7 +466,7 @@ DO
 	LOOP {
 		iam (Sound);
 		praat_new (Sound_to_Sound_BSS (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
-			GET_INTEGER (L"Number of cross-correlations"), GET_REAL (L"Lag times"),
+			GET_INTEGER (L"Number of cross-correlations"), GET_REAL (L"Lag step"),
 			GET_INTEGER (L"Maximum number of iterations"), GET_REAL (L"Tolerance"),
 			GET_INTEGER (L"Diagonalization method")), my name, L"_bss");
 	}
diff --git a/dwtools/praat_DataModeler_init.cpp b/dwtools/praat_DataModeler_init.cpp
new file mode 100644
index 0000000..13765de
--- /dev/null
+++ b/dwtools/praat_DataModeler_init.cpp
@@ -0,0 +1,1401 @@
+/* praat_DataModeler_init.cpp
+ *
+ * Copyright (C) 2014 David Weenink
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "praatP.h"
+#include <math.h>
+#include "DataModeler.h"
+#include "Formant_extensions.h"
+#include "Pitch.h"
+#include "Table_extensions.h"
+
+#undef iam
+#define iam iam_LOOP
+
+FORM (DataModeler_speckle, L"DataModeler: Speckle", 0)
+	REAL (L"left X range", L"0.0")
+	REAL (L"right X range", L"0.0")
+	REAL (L"left Y range", L"0.0")
+	REAL (L"right Y range", L"0.0")
+	BOOLEAN (L"Draw error bars", 1)
+	REAL (L"Bar width (mm)", L"1.0")
+	REAL (L"Horizontal offset (mm)", L"0.0")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	long order = 6;
+	LOOP {
+		iam (DataModeler);
+		DataModeler_speckle (me, GRAPHICS, GET_REAL (L"left X range"), GET_REAL (L"right X range"),
+			GET_REAL (L"left Y range"), GET_REAL (L"right Y range"),
+			0, order + 1, GET_INTEGER (L"Draw error bars"), GET_REAL (L"Bar width"), GET_REAL (L"Horizontal offset"),
+			GET_INTEGER (L"Garnish"));
+	}
+END
+
+
+FORM (DataModeler_drawEstimatedTrack, L"DataModeler: Draw estimated track", 0)
+	REAL (L"left X range", L"0.0")
+	REAL (L"right X range", L"0.0")
+	REAL (L"left Y range", L"0.0")
+	REAL (L"right Y range", L"0.0")
+	INTEGER (L"Order of polynomials for estimation", L"3")
+	REAL (L"Horizontal offset (mm)", L"0.0")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	long order = GET_INTEGER (L"Order of polynomials for estimation");
+	REQUIRE (order >= 0, L"The order must be greater than or equal to zero.")
+	LOOP {
+		iam (DataModeler);
+		DataModeler_drawTrack (me, GRAPHICS, GET_REAL (L"left X range"), GET_REAL (L"right X range"),
+			GET_REAL (L"left Y range"), GET_REAL (L"right Y range"), 1, order + 1, GET_REAL (L"Horizontal offset"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+DIRECT (DataModeler_getNumberOfParameters)
+	LOOP {
+		iam (DataModeler);
+		Melder_information (Melder_integer (my numberOfParameters), L" (= number of parameters)");
+	}
+END
+
+DIRECT (DataModeler_getNumberOfFixedParameters)
+	LOOP {
+		iam (DataModeler);
+		Melder_information (Melder_integer (DataModeler_getNumberOfFixedParameters (me)), L" (= number of parameters)");
+	}
+END
+
+FORM (DataModeler_getParameterValue, L"DataModeler: Get parameter value", 0)
+	NATURAL (L"Parameter number", L"1")
+	OK
+DO
+	long iparameter = GET_INTEGER (L"Parameter number");
+	LOOP {
+		iam (DataModeler);
+		double parameter = DataModeler_getParameterValue (me, iparameter);
+		Melder_information (Melder_double (parameter), L" (= parameter[", Melder_integer (iparameter), L"])");
+	}
+END
+
+FORM (DataModeler_getParameterStatus, L"DataModeler: Get parameter status", 0)
+	NATURAL (L"Parameter number", L"1")
+	OK
+DO
+	long iparameter = GET_INTEGER (L"Parameter number");
+	LOOP {
+		iam (DataModeler);
+		int status = DataModeler_getParameterStatus (me, iparameter);
+		Melder_information (status == DataModeler_PARAMETER_FREE ? L"Free" : (status == DataModeler_PARAMETER_FIXED ? L"Fixed" :
+			L"Undefined"), L" (= parameter[", Melder_integer (iparameter), L"])");
+	}
+END
+
+FORM (DataModeler_getParameterStandardDeviation, L"DataModeler: Get parameter standard deviatio", 0)
+	NATURAL (L"Parameter number", L"1")
+	OK
+DO
+	long iparameter = GET_INTEGER (L"Parameter number");
+	LOOP {
+		iam (DataModeler);
+		double sigma = DataModeler_getParameterStandardDeviation (me, iparameter);
+		Melder_information (Melder_double (sigma), L" (= parameter[", Melder_integer (iparameter), L"])");
+	}
+END
+
+FORM (DataModeler_getVarianceOfParameters, L"DataModeler: Get variance of parameters", 0)
+	INTEGER (L"left Parameter range", L"0")
+	INTEGER (L"right Parameter range", L"0")
+	OK
+DO
+	long nofp;
+	LOOP {
+		iam (DataModeler);
+		double var = DataModeler_getVarianceOfParameters (me, GET_INTEGER (L"left Parameter range"), GET_INTEGER (L"right Parameter range"), &nofp);
+		Melder_information (Melder_double (var), L" (for ", Melder_integer(nofp), L" free parameters.)");
+	}
+END
+
+DIRECT (DataModeler_getNumberOfDataPoints)
+	LOOP {
+		iam (DataModeler);
+		Melder_information (Melder_integer (my numberOfDataPoints), L" (= number of data points)");
+	}
+END
+
+DIRECT (DataModeler_getNumberOfInvalidDataPoints)
+	LOOP {
+		iam (DataModeler);
+		Melder_information (Melder_integer (DataModeler_getNumberOfInvalidDataPoints (me)), L" (= number of invalid data points)");
+	}
+END
+
+FORM (DataModeler_getModelValueAtX, L"DataModeler: Get model value at x", 0)
+	REAL (L"X", L"0.1")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		double y = DataModeler_getModelValueAtX (me, GET_REAL (L"X"));
+		Melder_informationReal (y, NULL);
+	}
+END
+
+
+DIRECT (DataModeler_getResidualSumOfSquares)
+	LOOP {
+		long n;
+		iam (DataModeler);
+		double rss = DataModeler_getResidualSumOfSquares (me, &n);
+		Melder_information (Melder_double (rss), L"  (for ", Melder_integer (n), L" datapoints)");
+	}
+END
+
+DIRECT (DataModeler_getStandardDeviation)
+	LOOP {
+		iam (DataModeler);
+		double sigma = DataModeler_estimateSigmaY (me);
+		Melder_information (Melder_double (sigma), NULL);
+	}
+END
+
+FORM (DataModeler_getDataPointValue, L"DataModeler: Get data point value", 0)
+	NATURAL (L"Index", L"1")
+	OK
+DO
+	long index = GET_INTEGER (L"Index");
+	LOOP {
+		iam (DataModeler);
+		double value = DataModeler_getDataPointValue (me, index);
+		Melder_information (Melder_double (value), L" (= value at point ", Melder_integer (index), L")");
+	}
+END
+
+FORM (DataModeler_getDataPointSigma, L"DataModeler: Get data point sigma", 0)
+	NATURAL (L"Index", L"1")
+	OK
+DO
+	long index = GET_INTEGER (L"Index");
+	LOOP {
+		iam (DataModeler);
+		double sigma = DataModeler_getDataPointSigma (me, index);
+		Melder_information (Melder_double (sigma), L" (= sigma at point ", Melder_integer (index), L")");
+	}
+END
+
+FORM (DataModeler_getDataPointStatus, L"DataModeler: Get data point status", 0)
+	NATURAL (L"Index", L"1")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		int status = DataModeler_getDataPointStatus (me, GET_INTEGER (L"Index"));
+		Melder_information (status == DataModeler_DATA_INVALID ? L"Invalid" : L"Valid");
+	}
+END
+
+DIRECT (DataModeler_getCoefficientOfDetermination)
+	LOOP {
+		iam (DataModeler);
+		double rSquared = DataModeler_getCoefficientOfDetermination (me, NULL, NULL);
+		Melder_informationReal (rSquared, L" (= R^2)");
+	}
+END
+
+FORM (DataModeler_reportChiSquared, L"DataModeler: Report chi squared", 0)
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Sigma")
+		OPTION (L"Relative")
+		OPTION (L"Sqrt sigma")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		int useSigmaY = GET_INTEGER (L"Weigh data") - 1;
+		MelderInfo_open ();
+		MelderInfo_writeLine (L"Chi squared test:");
+		MelderInfo_writeLine (useSigmaY == DataModeler_DATA_WEIGH_EQUAL ? L"Standard deviation is estimated from the data." :
+			useSigmaY == DataModeler_DATA_WEIGH_SIGMA ? L"Sigmas are used as estimate for local standard deviations." : 
+			useSigmaY == DataModeler_DATA_WEIGH_RELATIVE ? L"1/Q's are used as estimate for local standard deviations." :
+			L"Sqrt sigmas are used as estimate for local standard deviations.");
+		double  ndf, probability, chisq = DataModeler_getChiSquaredQ (me, useSigmaY, &probability, &ndf);
+		MelderInfo_writeLine (L"Chi squared = ", Melder_double (chisq));
+		MelderInfo_writeLine (L"Probability = ", Melder_double (probability));
+		MelderInfo_writeLine (L"Number of degrees of freedom = ", Melder_double (ndf));
+		MelderInfo_close ();
+	}
+END
+
+DIRECT (DataModeler_getDegreesOfFreedom)
+	LOOP {
+		iam (DataModeler);
+		double dof = DataModeler_getDegreesOfFreedom (me);
+		Melder_informationReal (dof, L" (= degrees of freedom)");
+	}
+END
+
+FORM (DataModeler_setDataWeighing, L"DataModeler: Set data weighing", 0)
+	OPTIONMENU (L"Weigh data", 1)
+		OPTION (L"Equally")
+		OPTION (L"Sigma")
+		OPTION (L"Relative")
+		OPTION (L"Sqrt sigma")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setDataWeighing (me, GET_INTEGER (L"Weigh data") - 1);
+	}
+END
+
+FORM (DataModeler_setTolerance, L"DataModeler: Set tolerance", 0)
+	REAL (L"Tolerance", L"1e-5")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setTolerance (me, GET_REAL (L"Tolerance"));
+	}
+END
+
+FORM (DataModeler_setParameterValueFixed, L"DataModeler: Set parameter value fixed", 0)
+	NATURAL (L"Parameter number", L"1")
+	REAL (L"Value", L"0.0")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setParameterValueFixed (me, GET_INTEGER (L"Parameter number"), GET_REAL (L"Value"));
+	}
+END
+
+FORM (DataModeler_setParameterFree, L"DataModeler: Set parameter free", 0)
+	INTEGER (L"left Parameter range", L"0")
+	INTEGER (L"right Parameter range", L"0")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setParametersFree (me, GET_INTEGER (L"left Parameter range"), GET_INTEGER (L"right Parameter range"));
+	}
+END
+
+FORM (DataModeler_setParameterValuesToZero, L"DataModeler: Set parameter values to zero", 0)
+	REAL (L"Number of sigmas", L"1.0")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setParameterValuesToZero (me, GET_REAL (L"Number of sigmas"));
+	}
+END
+
+FORM (DataModeler_setDataPointStatus, L"DataModeler: Set data point status", 0)
+	NATURAL (L"Index", L"1")
+	OPTIONMENU (L"Status", 1)
+		OPTION (L"Valid")
+		OPTION (L"Invalid")
+	OK
+DO
+	int menustatus = GET_INTEGER (L"Status");
+	int status = menustatus == 2 ? DataModeler_DATA_INVALID : DataModeler_DATA_VALID;
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setDataPointStatus (me, GET_INTEGER (L"Index"), status);
+	}
+END
+
+FORM (DataModeler_setDataPointValue, L"DataModeler: Set data point value", 0)
+	NATURAL (L"Index", L"1")
+	REAL (L"Value", L"0.0")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setDataPointValue (me, GET_INTEGER (L"Index"), GET_REAL (L"Value"));
+	}
+END
+
+FORM (DataModeler_setDataPointSigma, L"DataModeler: Set data point sigma", 0)
+	NATURAL (L"Index", L"1")
+	REAL (L"Sigma", L"10.0")
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		DataModeler_setDataPointSigma (me, GET_INTEGER (L"Index"), GET_REAL (L"Sigma"));
+	}
+END
+
+DIRECT (DataModeler_fitModel)
+	LOOP {
+		iam (DataModeler);
+		DataModeler_fit (me);
+	}
+END
+
+DIRECT (DataModeler_to_Covariance_parameters)
+	LOOP {
+		iam (DataModeler);
+		autoCovariance thee = DataModeler_to_Covariance_parameters (me);
+		praat_new (thee.transfer(), my name);
+	}
+END
+
+FORM (DataModeler_to_Table_zscores, L"DataModeler: To Table (z-scores)", 0)
+	BOOLEAN (L"Use sigmas on y-values", 1)
+	OK
+DO
+	LOOP {
+		iam (DataModeler);
+		autoTable thee = DataModeler_to_Table_zscores (me, GET_INTEGER (L"Use sigmas on y-values"));
+		praat_new (thee.transfer(), my name, L"_z");
+	}
+END
+
+FORM (Formant_to_FormantModeler, L"Formant: To FormantModeler", 0)
+//double tmin, double tmax, long numberOfFormants, long numberOfParametersPerTrack
+	REAL (L"left Start time", L"0.0")
+	REAL (L"right End time", L"0.1")
+	NATURAL (L"Number of formants", L"3")
+	INTEGER (L"Order of polynomials", L"3")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	OK
+DO
+	long order = GET_INTEGER (L"Order of polynomials");
+	REQUIRE (order >= 0, L"The order must be greater than or equal to zero.")
+	LOOP {
+		iam (Formant);
+		autoFormantModeler thee = Formant_to_FormantModeler (me, GET_REAL (L"left Start time"), GET_REAL (L"right End time"),
+			GET_INTEGER (L"Number of formants"), order + 1, GET_INTEGER (L"Weigh data") - 1);
+		praat_new (thee.transfer(), my name, L"_o", Melder_integer (order));
+	}
+END
+
+FORM (Formants_getSmoothestInInterval, L"Formant: Get smoothest in interval", 0)
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	NATURAL (L"Number of formant tracks", L"4")
+	INTEGER (L"Order of polynomials", L"3")
+	LABEL (L"", L"Use bandwidths to model the formant tracks")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	LABEL (L"", L"Zero parameter values whose range include zero.")
+	REAL (L"Number of sigmas", L"1.0")
+	POSITIVE (L"Parameter variance power", L"2.5")
+	OK
+DO
+	autoCollection set = praat_getSelectedObjects ();
+	double tmin = GET_REAL (L"left Time range"), tmax = GET_REAL (L"right Time range");
+	long index = Formants_getSmoothestInInterval (set.peek(), tmin, tmax, GET_INTEGER (L"Number of formant tracks"),
+		GET_INTEGER (L"Order of polynomials") + 1, 
+		GET_INTEGER (L"Weigh data") - 1, GET_REAL (L"Number of sigmas"), GET_REAL (L"Parameter variance power"), 0, 1, 1, 1, 1, 1);
+	long iselected = 0;
+	LOOP {
+		iselected ++;
+		if (iselected != index) {
+			praat_deselect (IOBJECT);
+		}
+	}
+END
+
+FORM (Formants_extractSmoothestPart, L"Formants: Extract smoothest part", L"Formants: Extract smoothest part")
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	NATURAL (L"Number of formant tracks", L"4")
+	INTEGER (L"Order of polynomials", L"3")
+	LABEL (L"", L"Use bandwidths to model the formant tracks")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	LABEL (L"", L"Zero parameter values whose range include zero.")
+	REAL (L"Number of sigmas", L"1.0")
+	POSITIVE (L"Parameter variance power", L"2.5")
+	OK
+DO
+	autoCollection set = praat_getSelectedObjects ();
+	double tmin = GET_REAL (L"left Time range"), tmax = GET_REAL (L"right Time range");
+	long index = Formants_getSmoothestInInterval (set.peek(), tmin, tmax, GET_INTEGER (L"Number of formant tracks"),
+		GET_INTEGER (L"Order of polynomials") + 1,
+		GET_INTEGER (L"Weigh data") - 1, GET_REAL (L"Number of sigmas"), GET_REAL (L"Parameter variance power"), 0, 1, 1, 1, 1, 1);
+	// next code is necessary to get the Formant at postion index selected and to get its name
+	long iselected = 0;
+	Formant him = NULL;
+	LOOP {
+		iselected ++;
+		if (iselected != index) {
+			praat_deselect (IOBJECT);
+		} else {
+			him = static_cast<Formant> (OBJECT);
+		}
+	}
+	Melder_assert (him != NULL);
+	autoFormant thee = Formant_extractPart (him, tmin, tmax);
+	praat_new (thee.transfer(), his name, L"_part");
+END
+
+
+FORM (Formants_extractSmoothestPart_constrained, L"Formants: Extract smoothest part (constrained)", L"Formants: Extract smoothest part (constrained)...")
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	NATURAL (L"Number of formant tracks", L"4")
+	INTEGER (L"Order of polynomials", L"3")
+	LABEL (L"", L"Use bandwidths to model the formant tracks")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	LABEL (L"", L"Zero parameter values whose range include zero.")
+	REAL (L"Number of sigmas", L"1.0")
+	POSITIVE (L"Parameter variance power", L"2.5")
+	LABEL (L"", L"The constraints on the formants")
+	REAL (L"Minimum F1 (Hz)", L"100.0")
+	REAL (L"Maximum F1 (Hz)", L"1200.0")
+	REAL (L"Minimum F2 (Hz)", L"0.0")
+	POSITIVE (L"Maximum F2 (Hz)", L"5000.0")
+	POSITIVE (L"Minimum F3 (Hz)", L"1500.0")
+	OK
+DO
+	autoCollection set = praat_getSelectedObjects ();
+	double tmin = GET_REAL (L"left Time range"), tmax = GET_REAL (L"right Time range");
+	long index = Formants_getSmoothestInInterval (set.peek(), tmin, tmax, 
+		GET_INTEGER (L"Number of formant tracks"), 
+	 	GET_INTEGER (L"Order of polynomials") + 1, GET_INTEGER (L"Weigh data") - 1, 
+		GET_REAL (L"Number of sigmas"), GET_REAL (L"Parameter variance power"), 1, GET_REAL (L"Minimum F1"), GET_REAL (L"Maximum F1"),
+		GET_REAL (L"Minimum F2"), GET_REAL (L"Maximum F2"), GET_REAL (L"Minimum F3"));
+	// next code is necessary to get the Formant at postion index selected and to get its name
+	long iselected = 0;
+	Formant him = NULL;
+	LOOP {
+		iselected ++;
+		if (iselected != index) {
+			praat_deselect (IOBJECT);
+		} else {
+			him = static_cast<Formant> (OBJECT);
+		}
+	}
+	Melder_assert (him != NULL);
+	autoFormant thee = Formant_extractPart (him, tmin, tmax);
+	praat_new (thee.transfer(), his name, L"_part");
+END
+
+/********************** FormantModeler ******************************/
+
+FORM (FormantModeler_drawEstimatedTracks, L"FormantModeler: Draw estimated tracks", 0)
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	REAL (L"Maximum frequency (Hz)", L"5500.0")
+	NATURAL (L"left Formant range", L"1")
+	NATURAL (L"right Formant range", L"3")
+	INTEGER (L"Order of polynomials for estimation", L"3")
+	REAL (L"Horizontal offset (mm)", L"0.0")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	long order = GET_INTEGER (L"Order of polynomials for estimation");
+	REQUIRE (order >= 0, L"The order must be greater than or equal to zero.")
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_drawTracks (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), 
+			GET_REAL (L"Maximum frequency"), GET_INTEGER (L"left Formant range"), GET_INTEGER (L"right Formant range"), 1, order + 1, 
+			GET_REAL (L"Horizontal offset"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+FORM (FormantModeler_drawTracks, L"FormantModeler: Draw tracks", 0)
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	REAL (L"Maximum frequency (Hz)", L"5500.0")
+	NATURAL (L"left Formant range", L"1")
+	NATURAL (L"right Formant range", L"3")
+	REAL (L"Horizontal offset (mm)", L"0.0")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	long order = 6;
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_drawTracks (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), 
+			GET_REAL (L"Maximum frequency"), GET_INTEGER (L"left Formant range"), GET_INTEGER (L"right Formant range"),
+			0, order + 1, GET_REAL (L"Horizontal offset"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+FORM (FormantModeler_speckle, L"FormantModeler: Speckle", 0)
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	REAL (L"Maximum frequency (Hz)", L"5500.0")
+	NATURAL (L"left Formant range", L"1")
+	NATURAL (L"right Formant range", L"3")
+	BOOLEAN (L"Draw error bars", 1)
+	REAL (L"Bar width (mm)", L"1.0")
+	REAL (L"Horizontal offset (mm)", L"0.0")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	long order = 6;
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_speckle (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), 
+			GET_REAL (L"Maximum frequency"), GET_INTEGER (L"left Formant range"), GET_INTEGER (L"right Formant range"),
+			0, order + 1, GET_INTEGER (L"Draw error bars"), GET_REAL (L"Bar width"), GET_REAL (L"Horizontal offset"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+FORM (FormantModeler_drawOutliersMarked, L"FormantModeler: Draw outliers marked", 0)
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	REAL (L"Maximum frequency (Hz)", L"5500.0")
+	NATURAL (L"left Formant range", L"1")
+	NATURAL (L"right Formant range", L"3")
+	POSITIVE (L"Number of sigmas", L"3.0")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	WORD (L"Mark", L"o")
+	NATURAL (L"Mark font size", L"12")
+	REAL (L"Horizontal offset (mm)", L"0.0")
+	BOOLEAN (L"Garnish", 0)
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_drawOutliersMarked (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+			GET_REAL (L"Maximum frequency"), GET_INTEGER (L"left Formant range"), GET_INTEGER (L"right Formant range"),
+			GET_REAL (L"Number of sigmas"), GET_INTEGER (L"Weigh data") - 1, GET_STRING (L"Mark"),
+			GET_INTEGER (L"Mark font size"), GET_REAL (L"Horizontal offset"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+//(FormantModeler me, Graphics g, long iformant, long numberOfQuantiles, double numberOfSigmas, int labelSize, const wchar_t *label, int garnish)
+FORM (FormantModeler_normalProbabilityPlot, L"FormantModeler: Normal probability plot", 0)
+	NATURAL (L"Formant number", L"1")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	NATURAL (L"Number of quantiles", L"100")
+	REAL (L"Number of sigmas", L"0.0")
+	NATURAL (L"Label size", L"12")
+	WORD (L"Label", L"+")
+	BOOLEAN (L"Garnish", 1);
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_normalProbabilityPlot (me, GRAPHICS, GET_INTEGER (L"Formant number"), GET_INTEGER (L"Weigh data") - 1,
+			GET_INTEGER (L"Number of quantiles"),
+			GET_REAL (L"Number of sigmas"), GET_INTEGER (L"Label size"), GET_STRING (L"Label"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+FORM (FormantModeler_drawBasisFunction, L"FormantModeler: Draw basis function", 0)
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	REAL (L"left Amplitude range (Hz)", L"0.0")
+	REAL (L"right Amplitude range (Hz)", L"5500.0")
+	//long iterm, bool scaled, long numberOfPoints, int garnish
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Basis function", L"2")
+	BOOLEAN (L"Scale function with parameter value", 0)
+	NATURAL (L"Number of points", L"200")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_drawBasisFunction (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+			GET_REAL (L"left Amplitude range"), GET_REAL (L"right Amplitude range"),GET_INTEGER (L"Formant number"),
+ 			GET_INTEGER (L"Basis function"), GET_INTEGER (L"Scale function with parameter value"),
+			GET_INTEGER (L"Number of points"), GET_INTEGER (L"Garnish"));
+	}
+END
+
+FORM (FormantModeler_getModelValueAtTime, L"", 0)
+	NATURAL (L"Formant number", L"1")
+	REAL (L"Time (s)", L"0.1")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		double y = FormantModeler_getModelValueAtTime (me, GET_INTEGER (L"Formant number"), GET_REAL (L"Time"));
+		Melder_informationReal (y, L"Hertz");
+	}
+END
+
+FORM (FormantModeler_getDataPointValue, L"FormantModeler: Get data point value", 0)
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Index", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	long index = GET_INTEGER (L"Index");
+	LOOP {
+		iam (FormantModeler);
+		double value = FormantModeler_getDataPointValue (me, iformant, index);
+		Melder_information (Melder_double (value), L" (= value of point ", Melder_integer (index), L" in track F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getDataPointSigma, L"FormantModeler: Get data point sigma", 0)
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Index", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	long index = GET_INTEGER (L"Index");
+	LOOP {
+		iam (FormantModeler);
+		double value = FormantModeler_getDataPointSigma (me, iformant, index);
+		Melder_information (Melder_double (value), L" (= sigma of point ", Melder_integer (index), L" in track F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getDataPointStatus, L"FormantModeler: Get data point status", 0)
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Index", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	long index = GET_INTEGER (L"Index");
+	LOOP {
+		iam (FormantModeler);
+		int status = FormantModeler_getDataPointStatus (me, iformant, index);
+		Melder_information (status == DataModeler_DATA_INVALID ? L"Invalid" : L"Valid");
+	}
+END
+
+DIRECT (FormantModeler_getNumberOfTracks)
+	LOOP {
+		iam (FormantModeler);
+		double nop = FormantModeler_getNumberOfTracks (me);
+		Melder_information (Melder_integer (nop), L" (= number of formants)");
+	}
+END
+
+FORM (FormantModeler_getNumberOfParameters, L"FormantModeler: Get number of parameters", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		double nop = FormantModeler_getNumberOfParameters (me, iformant);
+		Melder_information (Melder_integer (nop), L" (= number of parameters for F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getNumberOfFixedParameters, L"FormantModeler: Get number of fixed parameters", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		double nop = FormantModeler_getNumberOfFixedParameters (me, iformant);
+		Melder_information (Melder_integer (nop), L" (= number of fixed parameters for F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getNumberOfDataPoints, L"FormantModeler: Get number of data points", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		double numberOfDataPoints = FormantModeler_getNumberOfDataPoints (me, iformant);
+		Melder_information (Melder_integer (numberOfDataPoints), L" (= number of data points for F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getNumberOfInvalidDataPoints, L"FormantModeler: Get number of invalid data points", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		double numberOfDataPoints = FormantModeler_getNumberOfInvalidDataPoints (me, iformant);
+		Melder_information (Melder_integer (numberOfDataPoints), L" (= number of invalid data points for F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getParameterValue, L"FormantModeler: Get parameter value", 0)
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Parameter number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number"), iparameter = GET_INTEGER (L"Parameter number");
+	LOOP {
+		iam (FormantModeler);
+		double parameter = FormantModeler_getParameterValue (me, iformant, iparameter);
+		Melder_information (Melder_double (parameter), L" (= parameter[", Melder_integer (iparameter), L"] for F", 
+			Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getParameterStatus, L"FormantModeler: Get parameter status", 0)
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Parameter number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number"), iparameter = GET_INTEGER (L"Parameter number");
+	LOOP {
+		iam (FormantModeler);
+		int status = FormantModeler_getParameterStatus (me, iformant, iparameter);
+		Melder_information (status == DataModeler_PARAMETER_FREE ? L"Free" : (status == DataModeler_PARAMETER_FIXED ? L"Fixed" :
+			L"Undefined"), L" (= parameter[", Melder_integer (iparameter), L"] for F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getParameterStandardDeviation, L"FormantModeler: Get parameter standard deviatio", 0)
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Parameter number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number"), iparameter = GET_INTEGER (L"Parameter number");
+	LOOP {
+		iam (FormantModeler);
+		double sigma = FormantModeler_getParameterStandardDeviation (me, iformant, iparameter);
+		Melder_information (Melder_double (sigma), L" (= parameter[", Melder_integer (iparameter), L"] for F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getVarianceOfParameters, L"FormantModeler: Get variance of parameters", 0)
+	INTEGER (L"left Formant range", L"0")
+	INTEGER (L"right Formant range", L"0")
+	INTEGER (L"left Parameter range", L"0")
+	INTEGER (L"right Parameter range", L"0")
+	OK
+DO
+	long nofp;
+	LOOP {
+		iam (FormantModeler);
+		double var = FormantModeler_getVarianceOfParameters (me, GET_INTEGER (L"left Formant range"), 
+			GET_INTEGER (L"right Formant range"), GET_INTEGER (L"left Parameter range"), GET_INTEGER (L"right Parameter range"), &nofp);
+		Melder_information (Melder_double (var), L" (for ", Melder_integer(nofp), L" free parameters.)");
+	}
+END
+
+FORM (FormantModeler_getCoefficientOfDetermination, L"FormantModeler: Get coefficient of determination", 0)
+	INTEGER (L"left Formant range", L"0")
+	INTEGER (L"right Formant range", L"0")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		double rSquared = FormantModeler_getCoefficientOfDetermination (me, GET_INTEGER (L"left Formant range"), 
+			GET_INTEGER (L"right Formant range"));
+		Melder_informationReal (rSquared, L" (= R^2)");
+	}
+END
+
+FORM (FormantModeler_getResidualSumOfSquares, L"FormantModeler: Get residual sum of squares", L"FormantModeler: Get residual sum of squares...")
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long n, iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		double rss = FormantModeler_getResidualSumOfSquares (me, iformant, &n);
+		Melder_information (Melder_double (rss), L" Hz^2,  (= RSS of F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getStandardDeviation, L"FormantModeler: Get formant standard deviation", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		double sigma = FormantModeler_getStandardDeviation (me, iformant);
+		Melder_information (Melder_double (sigma), L" Hz (= std. dev. of F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_reportChiSquared, L"FormantModeler: Report chi squared", 0)
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		long numberOfFormants = my datamodelers -> size;
+		int useSigmaY = GET_INTEGER (L"Weigh data") - 1;
+		double chisq = 0, ndf = 0, probability;
+		MelderInfo_open ();
+		MelderInfo_writeLine (L"Chi squared tests for individual models of each ", Melder_integer (numberOfFormants), L" formant track:");
+		MelderInfo_writeLine (useSigmaY == DataModeler_DATA_WEIGH_EQUAL ? L"Standard deviation is estimated from the data." :
+			useSigmaY == DataModeler_DATA_WEIGH_SIGMA ? L"\tBandwidths are used as estimate for local standard deviations." : 
+			useSigmaY == DataModeler_DATA_WEIGH_RELATIVE ? L"\t1/Q's are used as estimate for local standard deviations." :
+			L"\tSqrt bandwidths are used as estimate for local standard deviations.");
+		for (long iformant = 1; iformant <= numberOfFormants; iformant++) {
+			chisq = FormantModeler_getChiSquaredQ (me, iformant, iformant, useSigmaY, &probability, &ndf);
+			MelderInfo_writeLine (L"Formant track ", Melder_integer (iformant), L":");
+			MelderInfo_writeLine (L"\tChi squared (F", Melder_integer (iformant), L") = ", Melder_double (chisq));
+			MelderInfo_writeLine (L"\tProbability (F", Melder_integer (iformant), L") = ", Melder_double (probability));
+			MelderInfo_writeLine (L"\tNumber of degrees of freedom (F", Melder_integer (iformant), L") = ", Melder_double (ndf));
+		}
+		chisq = FormantModeler_getChiSquaredQ (me, 1, numberOfFormants, useSigmaY, &probability, &ndf);
+		MelderInfo_writeLine (L"Chi squared test for the complete model with ", Melder_integer (numberOfFormants), L" formants:");
+		MelderInfo_writeLine (L"\tChi squared = ", Melder_double (chisq));
+		MelderInfo_writeLine (L"\tProbability = ", Melder_double (probability));
+		MelderInfo_writeLine (L"\tNumber of degrees of freedom = ", Melder_double (ndf));
+		MelderInfo_close ();
+	}
+END
+
+FORM (FormantModeler_getDegreesOfFreedom, L"FormantModeler: Get degrees of freedom", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		double sigma = FormantModeler_getDegreesOfFreedom (me, iformant);
+		Melder_information (Melder_double (sigma), L" (= degrees of freedom of F", Melder_integer (iformant), L")");
+	}
+END
+
+FORM (FormantModeler_getSmoothnessValue, L"FormantModeler: Get smoothness value", 0)
+	INTEGER (L"left Formant range", L"0")
+	INTEGER (L"right Formant range", L"0")
+	INTEGER (L"Order of polynomials", L"3")
+	POSITIVE (L"Parameter variance power", L"2.5")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		double smoothness = FormantModeler_getSmoothnessValue (me, GET_INTEGER (L"left Formant range"), 
+			GET_INTEGER (L"right Formant range"), GET_INTEGER (L"Order of polynomials"), GET_REAL (L"Parameter variance power"));
+		Melder_information (Melder_double (smoothness), L" (= smoothness)");
+	}
+END
+
+FORM (FormantModeler_getAverageDistanceBetweenTracks, L"FormantModeler: Get average distance between tracks", 0)
+	NATURAL (L"Track 1", L"2")
+	NATURAL (L"Track 2", L"3")
+	OPTIONMENU (L"Type of data", 1)
+		OPTION (L"Data points")
+		OPTION (L"Modeled")
+	OK
+DO
+	long track1 = GET_INTEGER (L"Track 1"), track2 = GET_INTEGER (L"Track 2");
+	LOOP {
+		iam (FormantModeler);
+		double distance = FormantModeler_getAverageDistanceBetweenTracks (me, track1, track2, GET_INTEGER (L"Type of data") - 1);
+		Melder_information (Melder_double (distance), L" (= average |F", Melder_integer(track1), L" - F", Melder_integer (track2), L"|)");
+	}
+END
+
+FORM (FormantModeler_getFormantsConstraintsFactor, L"FormantModeler: Get formants constraints factor", 0)
+	REAL (L"Minimum F1 (Hz)", L"100.0")
+	REAL (L"Maximum F1 (Hz)", L"1200.0")
+	REAL (L"Minimum F2 (Hz)", L"0.0")
+	POSITIVE (L"Maximum F2 (Hz)", L"5000.0")
+	POSITIVE (L"Minimum F3 (Hz)", L"1500.0")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		double fc = FormantModeler_getFormantsConstraintsFactor (me, GET_REAL (L"Minimum F1"), GET_REAL (L"Maximum F1"),
+		GET_REAL (L"Minimum F2"), GET_REAL (L"Maximum F2"), GET_REAL (L"Minimum F3"));
+		Melder_information (Melder_double (fc), L" (= formants constraints factor)");
+	}
+END
+
+FORM (FormantModeler_setDataWeighing, L"FormantModeler: Set data weighing", 0)
+	INTEGER (L"left Formant range", L"0")
+	INTEGER (L"right Formant range", L"0")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setDataWeighing (me, GET_INTEGER (L"left Formant range"), GET_INTEGER (L"right Formant range"), 
+			GET_INTEGER (L"Weigh data") - 1);
+	}
+END
+
+FORM (FormantModeler_setTolerance, L"FormantModeler: Set tolerance", 0)
+	REAL (L"Tolerance", L"1e-5")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setTolerance (me, GET_REAL (L"Tolerance"));
+	}
+END
+
+FORM (FormantModeler_setParameterValueFixed, L"FormantModeler: Set parameter value fixed", 0)
+	NATURAL (L"Formant number", L"1")
+	NATURAL (L"Parameter number", L"1")
+	REAL (L"Value", L"0.0")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setParameterValueFixed (me, GET_INTEGER (L"Formant number"), GET_INTEGER (L"Parameter number"), GET_REAL (L"Value"));
+	}
+END
+
+
+FORM (FormantModeler_setParameterFree, L"FormantModeler: Set parameter free", 0)
+	INTEGER (L"left Formant range", L"0")
+	INTEGER (L"right Formant range", L"0")
+	INTEGER (L"left Parameter range", L"0")
+	INTEGER (L"right Parameter range", L"0")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setParametersFree (me, GET_INTEGER (L"left Formant range"), GET_INTEGER (L"right Formant range"),
+			GET_INTEGER (L"left Parameter range"), GET_INTEGER (L"right Parameter range"));
+	}
+END
+
+FORM (FormantModeler_setParameterValuesToZero, L"FormantModeler: Set parameter values to zero", 0)
+	INTEGER (L"left Formant range", L"0")
+	INTEGER (L"right Formant range", L"0")
+	REAL (L"Number of sigmas", L"1.0")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setParameterValuesToZero (me, GET_INTEGER (L"left Formant range"),  GET_INTEGER (L"right Formant range"),
+			 GET_REAL (L"Number of sigmas"));
+	}
+END
+
+FORM (FormantModeler_setDataPointValue, L"FormantModeler: Set data point value", 0)
+	NATURAL (L"Formant index", L"1")
+	NATURAL (L"Data index", L"1")
+	REAL (L"Value", L"1.0")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setDataPointValue (me, GET_INTEGER (L"Formant index"),  GET_INTEGER (L"Data index"),
+			 GET_REAL (L"Value"));
+	}
+END
+
+FORM (FormantModeler_setDataPointSigma, L"FormantModeler: Set data point sigma", 0)
+	NATURAL (L"Formant index", L"1")
+	NATURAL (L"Data index", L"1")
+	REAL (L"Sigma", L"10.0")
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setDataPointSigma (me, GET_INTEGER (L"Formant index"),  GET_INTEGER (L"Data index"),
+			 GET_REAL (L"Sigma"));
+	}
+END
+
+FORM (FormantModeler_setDataPointStatus, L"FormantModeler: Set data point status", 0)
+	NATURAL (L"Formant index", L"1")
+	NATURAL (L"Data index", L"1")
+	OPTIONMENU (L"Status", 1)
+		OPTION (L"Valid")
+		OPTION (L"Invalid")
+	OK
+DO
+	int menustatus = GET_INTEGER (L"Status");
+	int status = menustatus == 2 ? DataModeler_DATA_INVALID : DataModeler_DATA_VALID;
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_setDataPointStatus (me, GET_INTEGER (L"Formant index"),  GET_INTEGER (L"Data index"), status);
+	}
+END
+
+DIRECT (FormantModeler_fitModel)
+	LOOP {
+		iam (FormantModeler);
+		FormantModeler_fit (me);
+	}
+END
+
+FORM (FormantModeler_to_Covariance_parameters, L"", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		autoCovariance thee = FormantModeler_to_Covariance_parameters (me, iformant);
+		praat_new (thee.transfer(), my name, L"_", Melder_integer (iformant));
+	}
+END
+
+FORM (FormantModeler_extractDataModeler, L"FormantModeler: Extract DataModeler", 0)
+	NATURAL (L"Formant number", L"1")
+	OK
+DO
+	long iformant = GET_INTEGER (L"Formant number");
+	LOOP {
+		iam (FormantModeler);
+		autoDataModeler thee = FormantModeler_extractDataModeler (me, iformant);
+		praat_new (thee.transfer(), my name, L"_", Melder_integer (iformant));
+	}
+END
+
+FORM (FormantModeler_to_Table_zscores, L"", 0)
+	BOOLEAN (L"Bandwidths as standard deviation", 1)
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		autoTable thee = FormantModeler_to_Table_zscores (me, GET_INTEGER (L"Bandwidths as standard deviation"));
+		praat_new (thee.transfer(), my name, L"_z");
+	}
+END
+
+FORM (FormantModeler_processOutliers, L"", 0)
+	POSITIVE (L"Number of sigmas", L"3.0")
+	BOOLEAN (L"Bandwidths as standard deviation", 1)
+	OK
+DO
+	LOOP {
+		iam (FormantModeler);
+		autoFormantModeler thee = FormantModeler_processOutliers (me, GET_REAL (L"Number of sigmas"), GET_INTEGER (L"Bandwidths as standard deviation"));
+		praat_new (thee.transfer(), my name, L"_outliers");
+	}
+END
+
+/*************************** PitchModeler *************************************/
+
+FORM (Pitch_to_PitchModeler, L"Pitch: To PitchModeler", 0)
+	REAL (L"left Start time (s)", L"0.0")
+	REAL (L"right End time (s)", L"0.1")
+	INTEGER (L"Order of polynomials", L"2")
+	OK
+DO
+	LOOP {
+		iam (Pitch);
+		autoPitchModeler thee = Pitch_to_PitchModeler (me, GET_REAL (L"left Start time"), GET_REAL (L"right End time"), 
+			GET_INTEGER (L"Order of polynomials") + 1);
+		praat_new (thee.transfer(), my name);
+	}
+END
+
+FORM (PitchModeler_draw, L"PitchModeler: Draw", 0)
+	REAL (L"left Time range (s)", L"0.0")
+	REAL (L"right Time range (s)", L"0.0")
+	REAL (L"left Frequency range (Hz)", L"0.0")
+	REAL (L"right Frequency range (Hz)", L"500.0")
+	INTEGER (L"Order of polynomial for estimation", L"2")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (PitchModeler);
+		PitchModeler_draw (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), 
+			GET_REAL (L"left Frequency range"), GET_REAL (L"right Frequency range"), GET_INTEGER (L"Order of polynomial for estimation") + 1,  GET_INTEGER (L"Garnish"));
+	}
+END
+
+FORM (Sound_to_Formant_interval, L"Sound: To Formant (interval)", 0)
+	REAL (L"left Time range (s)", L"0.1")
+	REAL (L"right Time range (s)", L"0.15")
+	POSITIVE (L"Window length (s)", L"0.015")
+	POSITIVE (L"Time step (s)", L"0.0025")
+	POSITIVE (L"left Maximum frequency range (Hz)", L"4500.0")
+	POSITIVE (L"right Maximum frequency range (Hz)", L"6500.0")
+	NATURAL (L"Number of frequency steps", L"11")
+	POSITIVE (L"Pre-emphasis from (Hz)", L"50.0")
+	NATURAL (L"Number of formant tracks in model", L"4")
+	INTEGER (L"Order of polynomials", L"3")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	LABEL (L"", L"Make parameters that include zero in their confidence region zero")
+	REAL (L"Number of sigmas", L"1.0")
+	POSITIVE (L"Parameter variance power", L"2.5")
+	OK
+DO
+	LOOP {
+		iam (Sound);
+		double ceiling;
+		autoFormant formant = Sound_to_Formant_interval (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+			GET_REAL (L"Window length"), GET_REAL (L"Time step"), GET_REAL (L"left Maximum frequency range"), 
+			GET_REAL (L"right Maximum frequency range"), GET_INTEGER (L"Number of frequency steps"), 
+			GET_REAL (L"Pre-emphasis from"), 
+			GET_INTEGER (L"Number of formant tracks in model"), GET_INTEGER (L"Order of polynomials") + 1,
+			GET_INTEGER (L"Weigh data") - 1, GET_REAL (L"Number of sigmas"), GET_REAL (L"Parameter variance power"),
+			0, 1, 1, 1, 1, 1, &ceiling);
+		praat_new (formant.transfer(), my name, L"_", Melder_fixed (ceiling, 0));
+	}
+END
+
+FORM (Sound_to_Formant_interval_constrained, L"Sound: To Formant (interval, constrained)", 0)
+	REAL (L"left Time range (s)", L"0.1")
+	REAL (L"right Time range (s)", L"0.15")
+	POSITIVE (L"Window length (s)", L"0.015")
+	POSITIVE (L"Time step (s)", L"0.0025")
+	POSITIVE (L"left Maximum frequency range (Hz)", L"4500.0")
+	POSITIVE (L"right Maximum frequency range (Hz)", L"6500.0")
+	NATURAL (L"Number of frequency steps", L"11")
+	POSITIVE (L"Pre-emphasis from (Hz)", L"50.0")
+	NATURAL (L"Number of formant tracks in model", L"4")
+	INTEGER (L"Order of polynomials", L"3")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	LABEL (L"", L"Make parameters that include zero in their confidence region zero")
+	REAL (L"Number of sigmas", L"1.0")
+	POSITIVE (L"Parameter variance power", L"2.5")
+	LABEL (L"", L"Formant frequency constraints")
+	REAL (L"Minimum F1 (Hz)", L"100.0")
+	REAL (L"Maximum F1 (Hz)", L"1200.0")
+	REAL (L"Minimum F2 (Hz)", L"0.0")
+	POSITIVE (L"Maximum F2 (Hz)", L"5000.0")
+	POSITIVE (L"Minimum F3 (Hz)", L"1000.0")
+	OK
+DO
+	LOOP {
+		iam (Sound);
+		double ceiling;
+		autoFormant formant = Sound_to_Formant_interval (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+			GET_REAL (L"Window length"), GET_REAL (L"Time step"), GET_REAL (L"left Maximum frequency range"), 
+			GET_REAL (L"right Maximum frequency range"), GET_INTEGER (L"Number of frequency steps"),
+			GET_REAL (L"Pre-emphasis from"), GET_INTEGER (L"Number of formant tracks in model"), 
+			GET_INTEGER (L"Order of polynomials") + 1, GET_INTEGER (L"Weigh data") - 1, GET_REAL (L"Number of sigmas"),
+			GET_REAL (L"Parameter variance power"), 1,
+			GET_REAL (L"Minimum F1"), GET_REAL (L"Maximum F1"), GET_REAL (L"Minimum F2"), 
+			GET_REAL (L"Maximum F2"), GET_REAL (L"Minimum F3"), &ceiling);
+		praat_new (formant.transfer(), my name, L"_", Melder_fixed (ceiling, 0));
+	}
+END
+
+FORM (Sound_to_Formant_interval_constrained_robust, L"Sound: To Formant (interval, constrained, robust)", 0)
+	REAL (L"left Time range (s)", L"0.1")
+	REAL (L"right Time range (s)", L"0.15")
+	POSITIVE (L"Window length (s)", L"0.015")
+	POSITIVE (L"Time step (s)", L"0.0025")
+	POSITIVE (L"left Maximum frequency range (Hz)", L"4500.0")
+	POSITIVE (L"right Maximum frequency range (Hz)", L"6500.0")
+	NATURAL (L"Number of frequency steps", L"11")
+	POSITIVE (L"Pre-emphasis from (Hz)", L"50.0")
+	NATURAL (L"Number of formant tracks in model", L"4")
+	INTEGER (L"Order of polynomials", L"3")
+	OPTIONMENU (L"Weigh data", 2)
+		OPTION (L"Equally")
+		OPTION (L"Bandwidth")
+		OPTION (L"Bandwidth / frequency")
+		OPTION (L"Sqrt bandwidth")
+	LABEL (L"", L"Make parameters that include zero in their confidence region zero")
+	REAL (L"Number of sigmas", L"1.0")
+	POSITIVE (L"Parameter variance power", L"2.5")
+	LABEL (L"", L"Formant frequency constraints")
+	REAL (L"Minimum F1 (Hz)", L"100.0")
+	REAL (L"Maximum F1 (Hz)", L"1200.0")
+	REAL (L"Minimum F2 (Hz)", L"0.0")
+	POSITIVE (L"Maximum F2 (Hz)", L"5000.0")
+	POSITIVE (L"Minimum F3 (Hz)", L"1000.0")
+	OK
+DO
+	LOOP {
+		iam (Sound);
+		double ceiling;
+		autoFormant formant = Sound_to_Formant_interval_robust (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
+			GET_REAL (L"Window length"), GET_REAL (L"Time step"), GET_REAL (L"left Maximum frequency range"), 
+			GET_REAL (L"right Maximum frequency range"), GET_INTEGER (L"Number of frequency steps"),
+			GET_REAL (L"Pre-emphasis from"), GET_INTEGER (L"Number of formant tracks in model"), 
+			GET_INTEGER (L"Order of polynomials") + 1, GET_INTEGER (L"Weigh data") - 1, GET_REAL (L"Number of sigmas"),
+			GET_REAL (L"Parameter variance power"), 1,
+			GET_REAL (L"Minimum F1"), GET_REAL (L"Maximum F1"), GET_REAL (L"Minimum F2"), 
+			GET_REAL (L"Maximum F2"), GET_REAL (L"Minimum F3"), &ceiling);
+		praat_new (formant.transfer(), my name, L"_", Melder_fixed (ceiling, 0));
+	}
+END
+
+FORM (Table_to_DataModeler, L"", 0)
+	REAL (L"left X range", L"0.0")
+	REAL (L"right X range", L"0.0 (=auto)")
+	WORD (L"Column with X data", L"")
+	WORD (L"Column with Y data", L"")
+	WORD (L"Column with sigmas", L"")
+	OPTIONMENU (L"Model functions", 1)
+	OPTION (L"Legendre polynomials")
+	INTEGER (L"Maximum order", L"3")
+	OK
+DO
+	LOOP {
+		iam (Table);
+		long xcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Column with X data"));
+		long ycolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Column with Y data"));
+		long scolumn = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Column with sigmas"));
+		autoDataModeler thee = Table_to_DataModeler (me, GET_REAL (L"left X range"), GET_REAL (L"right X range"),
+			xcolumn, ycolumn, scolumn, GET_INTEGER (L"Maximum order") + 1, GET_INTEGER (L"Model functions"));
+		praat_new (thee.transfer(), my name);
+	}
+END
+
+void praat_DataModeler_init (void);
+void praat_DataModeler_init (void) {
+	Thing_recognizeClassesByName (classDataModeler, classFormantModeler, classPitchModeler, NULL);
+	
+	praat_addAction1 (classDataModeler, 0, L"Speckle...", 0, 0, DO_DataModeler_speckle);
+	praat_addAction1 (classDataModeler, 0, L"Draw estimated track...", 0, 0, DO_DataModeler_drawEstimatedTrack);
+
+	praat_addAction1 (classDataModeler, 1, L"Query -", 0, 0, 0);
+		praat_addAction1 (classDataModeler, 0, L"Get number of parameters", 0, 1, DO_DataModeler_getNumberOfParameters);
+		praat_addAction1 (classDataModeler, 0, L"Get number of fixed parameters", 0, 1, DO_DataModeler_getNumberOfFixedParameters);
+		praat_addAction1 (classDataModeler, 0, L"Get parameter value...", 0, 1, DO_DataModeler_getParameterValue);
+		praat_addAction1 (classDataModeler, 0, L"Get parameter status...", 0, 1, DO_DataModeler_getParameterStatus);
+		praat_addAction1 (classDataModeler, 0, L"Get parameter standard deviation...", 0, 1, DO_DataModeler_getParameterStandardDeviation);
+		praat_addAction1 (classDataModeler, 0, L"Get variance of parameters...", 0, 1, DO_DataModeler_getVarianceOfParameters);
+		praat_addAction1 (classDataModeler, 1, L"-- get data points info --", 0, 1, 0);
+		praat_addAction1 (classDataModeler, 0, L"Get model value at x...", 0, 1, DO_DataModeler_getModelValueAtX);
+		praat_addAction1 (classDataModeler, 0, L"Get number of data points...", 0, 1, DO_DataModeler_getNumberOfDataPoints);
+		praat_addAction1 (classDataModeler, 0, L"Get number of invalid data points...", 0, 1, DO_DataModeler_getNumberOfInvalidDataPoints);
+		praat_addAction1 (classDataModeler, 0, L"Get data point value...", 0, 1, DO_DataModeler_getDataPointValue);
+		praat_addAction1 (classDataModeler, 0, L"Get data point sigma...", 0, 1, DO_DataModeler_getDataPointSigma);
+		praat_addAction1 (classDataModeler, 0, L"Get data point status...", 0, 1, DO_DataModeler_getDataPointStatus);
+		praat_addAction1 (classDataModeler, 1, L"-- get statistics info --", 0, 1, 0);
+		
+		praat_addAction1 (classDataModeler, 0, L"Get residual sum of squares", 0, 1, DO_DataModeler_getResidualSumOfSquares);
+		praat_addAction1 (classDataModeler, 0, L"Get data standard deviation...", 0, 1, DO_DataModeler_getStandardDeviation);
+		praat_addAction1 (classDataModeler, 0, L"Get coefficient of determination", 0, 1, DO_DataModeler_getCoefficientOfDetermination);
+		praat_addAction1 (classDataModeler, 0, L"Report chi squared...", 0, 1, DO_DataModeler_reportChiSquared);
+		praat_addAction1 (classDataModeler, 0, L"Get degrees of freedom...", 0, 1, DO_DataModeler_getDegreesOfFreedom);
+
+	praat_addAction1 (classDataModeler, 1, L"Modify -", 0, 0, 0);
+		praat_addAction1 (classDataModeler, 0, L"Set data weighing...", 0, 1, DO_DataModeler_setDataWeighing);
+		praat_addAction1 (classDataModeler, 0, L"Set tolerance...", 0, 1, DO_DataModeler_setTolerance);
+		praat_addAction1 (classDataModeler, 1, L"-- set parameter values --", 0, 1, 0);
+		praat_addAction1 (classDataModeler, 0, L"Set parameter value fixed...", 0, 1, DO_DataModeler_setParameterValueFixed);
+		praat_addAction1 (classDataModeler, 0, L"Set parameter free...", 0, 1, DO_DataModeler_setParameterFree);
+		praat_addAction1 (classDataModeler, 0, L"Set parameter values to zero...", 0, 1, DO_DataModeler_setParameterValuesToZero);
+		praat_addAction1 (classDataModeler, 1, L"-- set data values --", 0, 1, 0);
+		praat_addAction1 (classDataModeler, 0, L"Set data point status...", 0, 1, DO_DataModeler_setDataPointStatus);
+		praat_addAction1 (classDataModeler, 0, L"Set data point value...", 0, 1, DO_DataModeler_setDataPointValue);
+		praat_addAction1 (classDataModeler, 0, L"Set data point sigma...", 0, 1, DO_DataModeler_setDataPointSigma);
+		
+	praat_addAction1 (classDataModeler, 0, L"Fit model", 0, 0, DO_DataModeler_fitModel);
+	
+	praat_addAction1 (classDataModeler, 0, L"To Covariance (parameters)...", 0, 0, DO_DataModeler_to_Covariance_parameters);
+	praat_addAction1 (classDataModeler, 0, L"To Table (z-scores)...", 0, 0, DO_DataModeler_to_Table_zscores);
+
+	praat_addAction1 (classFormant, 0, L"To FormantModeler...", L"To LPC...", 0, DO_Formant_to_FormantModeler);
+	praat_addAction1 (classFormant, 0, L"Select smoothest...", L"To LPC...", 0, DO_Formants_getSmoothestInInterval);
+	praat_addAction1 (classFormant, 0, L"Extract smoothest part...", 0, 0, DO_Formants_extractSmoothestPart);
+	praat_addAction1 (classFormant, 0, L"Extract smoothest part (constrained)...", 0, 0, DO_Formants_extractSmoothestPart_constrained);
+
+	praat_addAction1 (classFormantModeler, 0, L"Speckle...", 0, 0, DO_FormantModeler_speckle);
+	praat_addAction1 (classFormantModeler, 0, L"Draw tracks...", 0, 0, DO_FormantModeler_drawTracks);
+	praat_addAction1 (classFormantModeler, 0, L"Draw estimated tracks...", 0, 0, DO_FormantModeler_drawEstimatedTracks);
+	praat_addAction1 (classFormantModeler, 0, L"Draw outliers marked...", 0, 0, DO_FormantModeler_drawOutliersMarked);
+	praat_addAction1 (classFormantModeler, 0, L"Normal probability plot...", 0, 0, DO_FormantModeler_normalProbabilityPlot);
+	praat_addAction1 (classFormantModeler, 0, L"Draw basis function...", 0, 0, DO_FormantModeler_drawBasisFunction);
+	
+	praat_addAction1 (classFormantModeler, 1, L"Query -", 0, 0, 0);
+		praat_addAction1 (classFormantModeler, 0, L"Get number of tracks...", 0, 1, DO_FormantModeler_getNumberOfTracks);
+		praat_addAction1 (classFormantModeler, 1, L"-- get parameter info --", 0, 1, 0);
+		praat_addAction1 (classFormantModeler, 0, L"Get number of parameters...", 0, 1, DO_FormantModeler_getNumberOfParameters);
+		praat_addAction1 (classFormantModeler, 0, L"Get number of fixed parameters...", 0, 1, DO_FormantModeler_getNumberOfFixedParameters);
+		praat_addAction1 (classFormantModeler, 0, L"Get parameter value...", 0, 1, DO_FormantModeler_getParameterValue);
+		praat_addAction1 (classFormantModeler, 0, L"Get parameter status...", 0, 1, DO_FormantModeler_getParameterStatus);
+		praat_addAction1 (classFormantModeler, 0, L"Get parameter standard deviation...", 0, 1, DO_FormantModeler_getParameterStandardDeviation);
+		praat_addAction1 (classFormantModeler, 0, L"Get variance of parameters...", 0, 1, DO_FormantModeler_getVarianceOfParameters);
+		praat_addAction1 (classFormantModeler, 1, L"-- get data points info --", 0, 1, 0);
+		praat_addAction1 (classFormantModeler, 0, L"Get number of data points...", 0, 1, DO_FormantModeler_getNumberOfDataPoints);
+		praat_addAction1 (classFormantModeler, 0, L"Get number of invalid data points...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_FormantModeler_getNumberOfInvalidDataPoints);
+		praat_addAction1 (classFormantModeler, 0, L"Get model value at time...", 0, 1, DO_FormantModeler_getModelValueAtTime);
+		praat_addAction1 (classFormantModeler, 0, L"Get data point value...", 0, 1, DO_FormantModeler_getDataPointValue);
+		praat_addAction1 (classFormantModeler, 0, L"Get data point sigma...", 0, 1, DO_FormantModeler_getDataPointSigma);
+		praat_addAction1 (classFormantModeler, 0, L"Get data point status...", 0, 1, DO_FormantModeler_getDataPointStatus);
+
+		praat_addAction1 (classFormantModeler, 1, L"-- get statistics info --", 0, 1, 0);
+		praat_addAction1 (classFormantModeler, 0, L"Get residual sum of squares...", 0, 1, DO_FormantModeler_getResidualSumOfSquares);
+		praat_addAction1 (classFormantModeler, 0, L"Get formant standard deviation...", 0, 1, DO_FormantModeler_getStandardDeviation);
+		praat_addAction1 (classFormantModeler, 0, L"Get coefficient of determination...", 0, 1, DO_FormantModeler_getCoefficientOfDetermination);
+		praat_addAction1 (classFormantModeler, 0, L"Report chi squared...", 0, 1, DO_FormantModeler_reportChiSquared);
+		praat_addAction1 (classFormantModeler, 0, L"Get degrees of freedom...", 0, 1, DO_FormantModeler_getDegreesOfFreedom);
+		praat_addAction1 (classFormantModeler, 0, L"Get smoothness value...", 0, 1, DO_FormantModeler_getSmoothnessValue);
+		praat_addAction1 (classFormantModeler, 0, L"Get average distance between tracks...", 0, 1, DO_FormantModeler_getAverageDistanceBetweenTracks);
+		praat_addAction1 (classFormantModeler, 0, L"Get formants constraints factor...", 0, 1, DO_FormantModeler_getFormantsConstraintsFactor);
+
+	praat_addAction1 (classFormantModeler, 1, L"Modify -", 0, 0, 0);
+		praat_addAction1 (classFormantModeler, 0, L"Set data weighing...", 0, 1, DO_FormantModeler_setDataWeighing);
+		praat_addAction1 (classFormantModeler, 0, L"Set tolerance...", 0, 1, DO_FormantModeler_setTolerance);
+		praat_addAction1 (classFormantModeler, 1, L"-- set parameter values --", 0, 1, 0);
+		praat_addAction1 (classFormantModeler, 0, L"Set parameter value fixed...", 0, 1, DO_FormantModeler_setParameterValueFixed);
+		praat_addAction1 (classFormantModeler, 0, L"Set parameter free...", 0, 1, DO_FormantModeler_setParameterFree);
+		praat_addAction1 (classFormantModeler, 0, L"Set parameter values to zero...", 0, 1, DO_FormantModeler_setParameterValuesToZero);
+		praat_addAction1 (classFormantModeler, 1, L"-- set data points --", 0, 1, 0);
+		praat_addAction1 (classFormantModeler, 0, L"Set data point value...", 0, 1, DO_FormantModeler_setDataPointValue);
+		praat_addAction1 (classFormantModeler, 0, L"Set data point sigma...", 0, 1, DO_FormantModeler_setDataPointSigma);
+		praat_addAction1 (classFormantModeler, 0, L"Set data point status...", 0, 1, DO_FormantModeler_setDataPointStatus);
+			
+	praat_addAction1 (classFormantModeler, 0, L"Fit model", 0, 0, DO_FormantModeler_fitModel);
+	
+	praat_addAction1 (classFormantModeler, 0, L"To Covariance (parameters)...", 0, 0, DO_FormantModeler_to_Covariance_parameters);
+	praat_addAction1 (classFormantModeler, 0, L"To Table (z-scores)...", 0, 0, DO_FormantModeler_to_Table_zscores);
+	praat_addAction1 (classFormantModeler, 0, L"To FormantModeler (process outliers)...", 0, 0, DO_FormantModeler_processOutliers);
+	praat_addAction1 (classFormantModeler, 0, L"Extract DataModeler...", 0, 0, DO_FormantModeler_extractDataModeler);
+	
+	praat_addAction1 (classPitch, 0, L"To PitchModeler...", L"To PointProcess", praat_HIDDEN, DO_Pitch_to_PitchModeler);
+
+	praat_addAction1 (classPitchModeler, 0, L"Draw...", 0, 0, DO_PitchModeler_draw);
+
+	praat_addAction1 (classSound, 0, L"To Formant (interval)...", L"To Formant (burg)...", 1, DO_Sound_to_Formant_interval);
+	praat_addAction1 (classSound, 0, L"To Formant (interval, constrained)...", L"To Formant (interval)...", 1, DO_Sound_to_Formant_interval_constrained);
+	praat_addAction1 (classSound, 0, L"To Formant (interval, constrained, robust)...", L"To Formant (interval, constrained)...", 1, DO_Sound_to_Formant_interval_constrained_robust);
+	praat_addAction1 (classTable, 0, L"To DataModeler...", L"To logistic regression...", 1, DO_Table_to_DataModeler);
+}
+
+/* End of file praat_DataModeler_init.c */
diff --git a/dwtools/praat_David_init.cpp b/dwtools/praat_David_init.cpp
index ff8e7bb..01f03b1 100644
--- a/dwtools/praat_David_init.cpp
+++ b/dwtools/praat_David_init.cpp
@@ -1,6 +1,6 @@
 /* praat_David_init.cpp
  *
- * Copyright (C) 1993-2013 David Weenink
+ * Copyright (C) 1993-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -88,6 +88,7 @@
 #include "FileInMemory.h"
 #include "Formula.h"
 #include "FormantGridEditor.h"
+#include "DataModeler.h"
 #include "FormantGrid_extensions.h"
 #include "Intensity_extensions.h"
 #include "IntensityTierEditor.h"
@@ -3290,6 +3291,18 @@ static void print_means (Table me) {
 	}
 }
 
+FORM (Table_getNumberOfRowsWhere, L"", 0)
+	LABEL (L"", L"Count only rows where the following condition holds:")
+	TEXTFIELD (L"Formula", L"1; self$[\"gender\"]=\"M\"")
+	OK
+DO
+	LOOP {
+		iam (Table);
+		long numberOfRows = Table_getNumberOfRowsWhere (me, GET_STRING (L"Formula"), interpreter);
+		Melder_information (Melder_integer (numberOfRows));
+	}
+END
+
 FORM (Table_reportOneWayAnova, L"Table: Report one-way anova",  L"Table: Report one-way anova...")
 	SENTENCE (L"Column with data", L"F0")
 	SENTENCE (L"Factor", L"Vowel")
@@ -4889,9 +4902,9 @@ END
 
 /******************** Sound ****************************************/
 
-static void Sound_create_addCommonFields (void *dia) {
+static void Sound_create_addCommonFields (void *dia, const wchar_t *endTime) {
 	REAL (L"Starting time (s)", L"0.0")
-	REAL (L"Finishing time (s)", L"0.1")
+	REAL (L"Finishing time (s)", endTime)
 	POSITIVE (L"Sampling frequency (Hz)", L"44100.0")
 }
 
@@ -4995,7 +5008,7 @@ END
 
 FORM (Sound_createFromGammaTone, L"Create a gammatone", L"Create Sound from gammatone...")
 	WORD (L"Name", L"gammatone")
-	Sound_create_addCommonFields (dia);
+	Sound_create_addCommonFields (dia, L"0.1");
 	INTEGER (L"Gamma", L"4")
 	POSITIVE (L"Frequency (Hz)", L"1000.0")
 	REAL (L"Bandwidth (Hz)", L"150.0")
@@ -5026,7 +5039,7 @@ END
 
 FORM (Sound_createFromShepardTone, L"Create a Shepard tone", L"Create Sound from Shepard tone...")
 	WORD (L"Name", L"shepardTone")
-	Sound_create_addCommonFields (dia);
+	Sound_create_addCommonFields (dia, L"1.0");
 	POSITIVE (L"Lowest frequency (Hz)", L"4.863")
 	NATURAL (L"Number of components", L"10")
 	REAL (L"Frequency change (semitones/s)", L"4.0")
@@ -5082,7 +5095,8 @@ DO
     LOOP {
         iam (Sound);
         if (ichannel > my ny) {
-            Melder_throw (me, " there is no channel 6. Sound in channel not played.");
+            Melder_throw (me, ": there is no channel ", Melder_integer(ichannel), ". Sound has only ", Melder_integer (my ny), " channel", 
+				  (my ny > 1 ? "s." : "."));
         }
         autoSound thee = Sound_extractChannel (me, ichannel);
         Sound_play (thee.peek(), 0, 0);
@@ -6315,7 +6329,7 @@ DO
 END
 
 FORM (Table_boxPlots, L"Table: Box plots", 0)
-	WORD (L"Data column", L"")
+	WORD (L"Data columns", L"")
 	WORD (L"Factor column", L"")
 	REAL (L"left Vertical range", L"0.0")
 	REAL (L"right Vertical range", L"0.0")
@@ -6328,14 +6342,13 @@ DO
 	int garnish = GET_INTEGER (L"Garnish");
 	LOOP {
 		iam (Table);
-		long dataColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Data column"));
 		long factorColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Factor column"));
-		Table_boxPlots (me, GRAPHICS, dataColumn, factorColumn, ymin, ymax, garnish);
+		Table_boxPlotsWhere (me, GRAPHICS, GET_STRING (L"Data columns"), factorColumn, ymin, ymax, garnish, L"1", interpreter);
 	}
 END
 
-FORM (Table_boxPlotsWhere, L"Table: Box plots where", 0)
-	WORD (L"Data column", L"")
+FORM (Table_boxPlotsWhere, L"Table: Box plots where", L"Table: Box plots where...")
+	SENTENCE (L"Data columns", L"")
 	WORD (L"Factor column", L"")
 	REAL (L"left Vertical range", L"0.0")
 	REAL (L"right Vertical range", L"0.0")
@@ -6348,12 +6361,11 @@ DO
 	double ymin = GET_REAL (L"left Vertical range");
 	double ymax = GET_REAL (L"right Vertical range");
 	int garnish = GET_INTEGER (L"Garnish");
+	wchar_t *dataColumns = GET_STRING (L"Data columns");
 	LOOP {
 		iam (Table);
-		long dataColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Data column"));
 		long factorColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Factor column"));
-		autoTable thee = Table_extractRowsWhere (me, GET_STRING (L"Formula"), interpreter);
-		Table_boxPlots (thee.peek(), GRAPHICS, dataColumn, factorColumn, ymin, ymax, garnish);
+		Table_boxPlotsWhere (me, GRAPHICS, dataColumns, factorColumn, ymin, ymax, garnish, GET_STRING (L"Formula"), interpreter);
 	}
 END
 
@@ -6383,6 +6395,60 @@ DO
 	}
 END
 
+FORM (Table_drawEllipses, L"Table: Draw ellipses", 0)
+	WORD (L"Horizontal column", L"F2")
+	REAL (L"left Horizontal range", L"0.0")
+	REAL (L"right Horizontal range", L"0.0 (= auto)")
+	WORD (L"Vertical column", L"F1")
+	REAL (L"left Vertical range", L"0.0")
+	REAL (L"right Vertical range", L"0.0 (= auto)")
+	WORD (L"Factor column", L"Vowel")
+	POSITIVE (L"Number of sigmas", L"1.0")
+	NATURAL (L"Font size", L"12")
+	BOOLEAN (L"Garnish", 1)
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (Table);
+		long xcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Horizontal column"));
+		long ycolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Vertical column"));
+		long factorcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Factor column"));
+		Table_drawEllipsesWhere (me, GRAPHICS, xcolumn, ycolumn, factorcolumn, GET_REAL (L"left Horizontal range"), 
+			 GET_REAL (L"right Horizontal range"), GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
+			 GET_REAL (L"Number of sigmas"), GET_INTEGER (L"Font size"), GET_INTEGER (L"Garnish"), L"1", interpreter);
+	}
+END
+
+FORM (Table_drawEllipsesWhere, L"Table: Draw ellipses where", 0)
+	WORD (L"Horizontal column", L"F2")
+	REAL (L"left Horizontal range", L"0.0")
+	REAL (L"right Horizontal range", L"0.0 (= auto)")
+	WORD (L"Vertical column", L"F1")
+	REAL (L"left Vertical range", L"0.0")
+	REAL (L"right Vertical range", L"0.0 (= auto)")
+	WORD (L"Factor column", L"Vowel")
+	POSITIVE (L"Number of sigmas", L"1.0")
+	NATURAL (L"Font size", L"12")
+	BOOLEAN (L"Garnish", 1)
+	LABEL (L"", L"Use only data in rows where the following condition holds:")
+	TEXTFIELD (L"Formula", L"1; self$[\"gender\"]=\"male\"")
+	
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (Table);
+		long xcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Horizontal column"));
+		long ycolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Vertical column"));
+		long factorcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Factor column"));
+		Table_drawEllipsesWhere (me, GRAPHICS, xcolumn, ycolumn, factorcolumn, GET_REAL (L"left Horizontal range"), 
+			 GET_REAL (L"right Horizontal range"), GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
+			 GET_REAL (L"Number of sigmas"), GET_INTEGER (L"Font size"), GET_INTEGER (L"Garnish"), GET_STRING (L"Formula"), interpreter);
+	}
+END
+
+
 FORM (Table_normalProbabilityPlot, L"Table: Normal probability plot", L"Table: Normal probability plot...")
 	WORD (L"Column", L"")
 	NATURAL (L"Number of quantiles", L"100")
@@ -6479,6 +6545,49 @@ DO
 	}
 END
 
+FORM (Table_lagPlot, L"Table: lag plot",0)
+	WORD (L"Data column", L"errors")
+	NATURAL (L"Lag", L"1")
+	REAL (L"left Horizontal and vertical range", L"0.0")
+	REAL (L"right Horizontal and vertical range", L"0.0")
+	NATURAL (L"Label size", L"12")
+	WORD (L"Label", L"+")
+	BOOLEAN (L"Garnish", 1);
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (Table);
+		long dataColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Data column"));
+		Table_lagPlotWhere (me, GRAPHICS, dataColumn, GET_INTEGER (L"Lag"), GET_REAL (L"left Horizontal and vertical range"),
+			GET_REAL (L"right Horizontal and vertical range"), GET_STRING (L"Label"), GET_INTEGER (L"Label size"),
+			GET_INTEGER (L"Garnish"), L"1", interpreter);
+	}
+END
+
+
+FORM (Table_lagPlotWhere, L"Table: lag plot where",0)
+	WORD (L"Data column", L"errors")
+	NATURAL (L"Lag", L"1")
+	REAL (L"left Horizontal and vertical range", L"0.0")
+	REAL (L"right Horizontal and vertical range", L"0.0")
+	NATURAL (L"Label size", L"12")
+	WORD (L"Label", L"+")
+	BOOLEAN (L"Garnish", 1);
+	LABEL (L"", L"Use only data in rows where the following condition holds:")
+	TEXTFIELD (L"Formula", L"1; self$[\"gender\"]=\"male\"")
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (Table);
+		long dataColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Data column"));
+		Table_lagPlotWhere (me, GRAPHICS, dataColumn, GET_INTEGER (L"Lag"), GET_REAL (L"left Horizontal and vertical range"),
+			GET_REAL (L"right Horizontal and vertical range"), GET_STRING (L"Label"), GET_INTEGER (L"Label size"),
+			GET_INTEGER (L"Garnish"), GET_STRING (L"Formula"), interpreter);
+	}
+END
+
 FORM (Table_distributionPlot, L"Table: Distribution plot", 0)
 	WORD (L"Data column", L"data")
 	REAL (L"Minimum value", L"0.0")
@@ -6495,8 +6604,8 @@ DO
 		iam (Table);
 		long dataColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Data column"));
 		Table_distributionPlotWhere (me, GRAPHICS, dataColumn, GET_REAL (L"Minimum value"), GET_REAL (L"Maximum value"),
-			GET_INTEGER (L"Number of bins"), GET_REAL (L"Minimum frequency"), GET_REAL (L"Maximum frequency"), GET_INTEGER (L"Garnish"),
-			L"1", interpreter);
+			GET_INTEGER (L"Number of bins"), GET_REAL (L"Minimum frequency"), GET_REAL (L"Maximum frequency"), 
+			GET_INTEGER (L"Garnish"), L"1", interpreter);
 	}
 END
 
@@ -6523,17 +6632,15 @@ DO
 	}
 END
 
-FORM (Table_scatterPlotWithConfidenceIntervals, L"Table: Scatter plot (confidence intervals)", L"")
-	NATURAL (L"Horizontal axis column", L"1")
+FORM (Table_horizontalErrorBarsPlot, L"Table: Horizontal error bars plot", L"Table: Horizontal error bars plot...")
+	WORD (L"Horizontal column", L"x")
 	REAL (L"left Horizontal range", L"0.0")
 	REAL (L"right Horizontal range", L"0.0")
-	INTEGER (L"left Horizontal confidence interval column", L"3")
-	INTEGER (L"right Horizontal confidence interval column", L"4")
-	NATURAL (L"Vertical axis column", L"2")
+	WORD (L"Vertical column", L"y")
 	REAL (L"left Vertical range", L"0.0")
 	REAL (L"right Vertical range", L"0.0")
-	INTEGER (L"left Vertical confidence interval column", L"5")
-	INTEGER (L"right Vertical confidence interval column", L"6")
+	WORD (L"Lower error value column", L"")
+	WORD (L"Upper error value column", L"")
 	REAL (L"Bar size (mm)", L"1.0")
 	BOOLEAN (L"Garnish", 1);
 	OK
@@ -6541,18 +6648,108 @@ DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (Table);
-		Table_scatterPlotWithConfidenceIntervals (me, GRAPHICS,
-		GET_INTEGER (L"Horizontal axis column"), GET_INTEGER (L"Vertical axis column"),
-		GET_REAL (L"left Horizontal range"), GET_REAL (L"right Horizontal range"),
-		GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
-		GET_INTEGER (L"left Horizontal confidence interval column"), GET_INTEGER (L"right Horizontal confidence interval column"),
-		GET_INTEGER (L"left Vertical confidence interval column"), GET_INTEGER (L"right Vertical confidence interval column"),
-		GET_REAL (L"Bar size"), GET_INTEGER (L"Garnish"));
+		long xcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Horizontal column"));
+		long ycolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Vertical column"));
+		long xl = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Lower error value column"));
+		long xu = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Upper error value column"));
+		
+		Table_horizontalErrorBarsPlotWhere (me, GRAPHICS, xcolumn, ycolumn,
+			GET_REAL (L"left Horizontal range"), GET_REAL (L"right Horizontal range"),
+			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
+			xl, xu, GET_REAL (L"Bar size"), GET_INTEGER (L"Garnish"), L"1", interpreter);
+	}
+END
+
+FORM (Table_horizontalErrorBarsPlotWhere, L"Table: Horizontal error bars plot where", L"Table: Horizontal error bars plot where...")
+	WORD (L"Horizontal column", L"")
+	REAL (L"left Horizontal range", L"0.0")
+	REAL (L"right Horizontal range", L"0.0")
+	WORD (L"Vertical column", L"")
+	REAL (L"left Vertical range", L"0.0")
+	REAL (L"right Vertical range", L"0.0")
+	WORD (L"Lower error value column", L"")
+	WORD (L"Upper error value column", L"")
+	REAL (L"Bar size (mm)", L"1.0")
+	BOOLEAN (L"Garnish", 1);
+	LABEL (L"", L"Use only data in rows where the following condition holds:")
+	TEXTFIELD (L"Formula", L"1; self$[\"gender\"]=\"male\"")
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (Table);
+		long xcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Horizontal column"));
+		long ycolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Vertical column"));
+		long xl = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Lower error value column"));
+		long xu = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Upper error value column"));
+		
+		Table_horizontalErrorBarsPlotWhere (me, GRAPHICS, xcolumn, ycolumn,
+			GET_REAL (L"left Horizontal range"), GET_REAL (L"right Horizontal range"),
+			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
+			xl, xu, GET_REAL (L"Bar size"), GET_INTEGER (L"Garnish"), GET_STRING (L"Formula"), interpreter);
+	}
+END
+
+FORM (Table_verticalErrorBarsPlot, L"Table: Vertical error bars plot", L"Table: Vertical error bars plot...")
+	WORD (L"Horizontal column", L"")
+	REAL (L"left Horizontal range", L"0.0")
+	REAL (L"right Horizontal range", L"0.0")
+	WORD (L"Vertical column", L"")
+	REAL (L"left Vertical range", L"0.0")
+	REAL (L"right Vertical range", L"0.0")
+	WORD (L"Lower error value column", L"")
+	WORD (L"Upper error value column", L"")
+	REAL (L"Bar size (mm)", L"1.0")
+	BOOLEAN (L"Garnish", 1);
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (Table);
+		long xcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Horizontal column"));
+		long ycolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Vertical column"));
+		long yl = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Lower error value column"));
+		long yu = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Upper error value column"));
+		
+		Table_verticalErrorBarsPlotWhere (me, GRAPHICS, xcolumn, ycolumn,
+			GET_REAL (L"left Horizontal range"), GET_REAL (L"right Horizontal range"),
+			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
+			yl, yu, GET_REAL (L"Bar size"), GET_INTEGER (L"Garnish"), L"1", interpreter);
+	}
+END
+
+FORM (Table_verticalErrorBarsPlotWhere, L"Table: Vertical error bars plot where", L"Table: Vertical error bars plot where...")
+	WORD (L"Horizontal column", L"")
+	REAL (L"left Horizontal range", L"0.0")
+	REAL (L"right Horizontal range", L"0.0")
+	WORD (L"Vertical column", L"")
+	REAL (L"left Vertical range", L"0.0")
+	REAL (L"right Vertical range", L"0.0")
+	WORD (L"Lower error value column", L"")
+	WORD (L"Upper error value column", L"")
+	REAL (L"Bar size (mm)", L"1.0")
+	BOOLEAN (L"Garnish", 1);
+	LABEL (L"", L"Use only data in rows where the following condition holds:")
+	TEXTFIELD (L"Formula", L"1; self$[\"gender\"]=\"male\"")
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (Table);
+		long xcolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Horizontal column"));
+		long ycolumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Vertical column"));
+		long yl = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Lower error value column"));
+		long yu = Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Upper error value column"));
+		
+		Table_verticalErrorBarsPlotWhere (me, GRAPHICS, xcolumn, ycolumn,
+			GET_REAL (L"left Horizontal range"), GET_REAL (L"right Horizontal range"),
+			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
+			yl, yu, GET_REAL (L"Bar size"), GET_INTEGER (L"Garnish"), GET_STRING (L"Formula"), interpreter);
 	}
 END
 
 FORM (Table_extractRowsWhere, L"Table: Extract rows where", 0)
-	LABEL (L"", L"Extractm rows where the following condition holds:")
+	LABEL (L"", L"Extract rows where the following condition holds:")
 	TEXTFIELD (L"Formula", L"1; self$[\"gender\"]=\"male\"")
 	OK
 DO
@@ -6563,6 +6760,25 @@ DO
 	}
 END
 
+FORM (Table_extractRowsMahalanobisWhere, L"Table: Extract rows where (mahalanobis)", 0)
+	SENTENCE (L"Extract all rows where columns...", L"")
+	RADIO_ENUM (L"...have a mahalanobis distance...", kMelder_number, GREATER_THAN)
+	REAL (L"...the number", L"2.0")
+	WORD (L"Factor column", L"")
+	LABEL (L"", L"Process only rows where the following condition holds:")
+	TEXTFIELD (L"Formula", L"1; self$[\"gender\"]=\"male\"")
+	OK
+DO
+	double numberOfSigmas = GET_REAL (L"...the number");
+	LOOP {
+		iam (Table);
+		autoTable thee = Table_extractMahalanobisWhere(me, GET_STRING (L"Extract all rows where columns..."), 
+		   GET_STRING (L"Factor column"), numberOfSigmas, GET_ENUM (kMelder_number, L"...have a mahalanobis distance..."), 
+		   GET_STRING (L"Formula"), interpreter);
+		praat_new (thee.transfer(), my name, L"_mahalanobis");
+	}
+END
+
 /******************* TableOfReal ****************************/
 
 DIRECT (New_CreateIrisDataset)
@@ -8002,27 +8218,36 @@ void praat_uvafon_David_init () {
 	praat_addAction1 (classSVD, 0, L"Extract left singular vectors", 0, 0, DO_SVD_extractLeftSingularVectors);
 	praat_addAction1 (classSVD, 0, L"Extract right singular vectors", 0, 0, DO_SVD_extractRightSingularVectors);
 	praat_addAction1 (classSVD, 0, L"Extract singular values", 0, 0, DO_SVD_extractSingularValues);
-
-		praat_addAction1 (classTable, 0, L"Box plots...", L"Draw ellipse (standard deviation)...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_boxPlots);
+		praat_addAction1 (classTable, 0, L"Draw ellipses...", L"Draw ellipse (standard deviation)...", praat_DEPTH_1, DO_Table_drawEllipses);
+		praat_addAction1 (classTable, 0, L"Box plots...", L"Draw ellipses...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_boxPlots);
 		praat_addAction1 (classTable, 0, L"Normal probability plot...", L"Box plots...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_normalProbabilityPlot);
 		praat_addAction1 (classTable, 0, L"Quantile-quantile plot...", L"Normal probability plot...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_quantileQuantilePlot);
 		praat_addAction1 (classTable, 0, L"Quantile-quantile plot (between levels)...", L"Quantile-quantile plot...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_quantileQuantilePlot_betweenLevels);
-		praat_addAction1 (classTable, 0, L"Scatter plot (ci)...", 0, praat_DEPTH_1, DO_Table_scatterPlotWithConfidenceIntervals);
+		praat_addAction1 (classTable, 0, L"Lag plot...", L"Quantile-quantile plot (between levels)...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_lagPlot);
+		praat_addAction1 (classTable, 0, L"Horizontal error bars plot...", L"Scatter plot (mark)...", praat_DEPTH_1, DO_Table_horizontalErrorBarsPlot);
+		praat_addAction1 (classTable, 0, L"Vertical error bars plot...", L"Scatter plot (mark)...", praat_DEPTH_1, DO_Table_verticalErrorBarsPlot);
 		praat_addAction1 (classTable, 0, L"Distribution plot...", L"Quantile-quantile plot...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_distributionPlot);
-		praat_addAction1 (classTable, 1, L"Draw where -",  L"Quantile-quantile plot (between levels)...", 1 , 0);
-			praat_addAction1 (classTable, 0, L"Scatter plot where...", L"Draw where -", 2, DO_Table_scatterPlotWhere);
+		praat_addAction1 (classTable, 1, L"Draw where",  L"Lag plot...", 1 , 0);
+			praat_addAction1 (classTable, 0, L"Scatter plot where...", L"Draw where", 2, DO_Table_scatterPlotWhere);
 			praat_addAction1 (classTable, 0, L"Scatter plot where (mark)...", L"Scatter plot where...", 2, DO_Table_scatterPlotMarkWhere);
+			praat_addAction1 (classTable, 0, L"Horizontal error bars plot where...", L"Scatter plot where (mark)...", praat_DEPTH_2, DO_Table_horizontalErrorBarsPlotWhere);
+			praat_addAction1 (classTable, 0, L"Vertical error bars plot where...", L"Scatter plot where (mark)...", praat_DEPTH_2, DO_Table_verticalErrorBarsPlotWhere);
+			
 			praat_addAction1 (classTable, 0, L"Distribution plot where...", L"Scatter plot where (mark)...", 2, DO_Table_distributionPlotWhere);
 			praat_addAction1 (classTable, 0, L"Draw ellipse where (standard deviation)...", L"Distribution plot where...", 2, DO_Table_drawEllipseWhere);
 			praat_addAction1 (classTable, 0, L"Box plots where...", L"Draw ellipse where (standard deviation)...", 2, DO_Table_boxPlotsWhere);
 			praat_addAction1 (classTable, 0, L"Normal probability plot where...", L"Box plots where...", 2, DO_Table_normalProbabilityPlotWhere);
 			praat_addAction1 (classTable, 0, L"Bar plot where...", L"Normal probability plot where...", 2, DO_Table_barPlotWhere);
 			praat_addAction1 (classTable, 0, L"Line graph where...", L"Bar plot where...", 2, DO_Table_LineGraphWhere);
+			praat_addAction1 (classTable, 0, L"Lag plot where...", L"Line graph where...", 2, DO_Table_lagPlotWhere);
+			praat_addAction1 (classTable, 0, L"Draw ellipses where...", L"Lag plot where...", 2, DO_Table_drawEllipsesWhere);
 
+	praat_addAction1 (classTable, 1, L"Get number of rows where...", L"Get number of rows", praat_DEPTH_1 | praat_HIDDEN,	DO_Table_getNumberOfRowsWhere);
 	praat_addAction1 (classTable, 1, L"Report one-way anova...", L"Report group difference (Wilcoxon rank sum)...", praat_DEPTH_1 | praat_HIDDEN,	DO_Table_reportOneWayAnova);
 	praat_addAction1 (classTable, 1, L"Report one-way Kruskal-Wallis...", L"Report one-way anova...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_reportOneWayKruskalWallis);
 	praat_addAction1 (classTable, 1, L"Report two-way anova...", L"Report one-way Kruskal-Wallis...", praat_DEPTH_1 | praat_HIDDEN, DO_Table_reportTwoWayAnova);
 	praat_addAction1 (classTable, 1, L"Extract rows where...", L"Extract rows where column (text)...", praat_DEPTH_1, DO_Table_extractRowsWhere);
+	praat_addAction1 (classTable, 1, L"Extract rows where (mahalanobis)...", L"Extract rows where...", praat_DEPTH_1| praat_HIDDEN, DO_Table_extractRowsMahalanobisWhere);
 
 	praat_addAction1 (classTable, 0, L"To KlattTable", 0, praat_HIDDEN, DO_Table_to_KlattTable);
 	praat_addAction1 (classTable, 1, L"Get median absolute deviation...", L"Get standard deviation...", 1, DO_Table_getMedianAbsoluteDeviation);
diff --git a/dwtools/praat_KlattGrid_init.cpp b/dwtools/praat_KlattGrid_init.cpp
index 1980cb1..802a7ac 100644
--- a/dwtools/praat_KlattGrid_init.cpp
+++ b/dwtools/praat_KlattGrid_init.cpp
@@ -1,6 +1,6 @@
 /* praat_KlattGrid_init.cpp
  *
- * Copyright (C) 2009-2011 David Weenink
+ * Copyright (C) 2009-2014 David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -239,7 +239,6 @@ DIRECT (KlattGrid_edit##Name##FormantGrid) \
 		const wchar_t *id_and_name = Melder_wcscat (Melder_integer (ID), L". ", formant_names[formantType], L"formant grid"); \
 		autoKlattGrid_formantGridEditor editor = KlattGrid_formantGridEditor_create (id_and_name, me, formantType); \
 		praat_installEditor (editor.transfer(), IOBJECT); \
-		Melder_free (id_and_name); \
 	} \
 END
 
@@ -268,7 +267,6 @@ DO \
 		const wchar_t *id_and_name = Melder_wcscat (Melder_integer (ID), L". ", formant_names[formantType], L"formant amplitude tier"); \
 		autoKlattGrid_decibelTierEditor editor = KlattGrid_decibelTierEditor_create (id_and_name, me, (RealTier) (*amp)->item[formantNumber]); \
 		praat_installEditor (editor.transfer(), IOBJECT); \
-		Melder_free (id_and_name); \
 	} \
 END
 
@@ -416,6 +414,51 @@ DO \
 	} \
 END
 
+#define KlattGrid_ADD_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
+FORM (KlattGrid_add##Name##FormantFrequencyAndBandwidthTiers, L"KlattGrid: Add " #namef "ormant", 0) \
+	INTEGER (L"Position", L"0 (=at end)") \
+	OK \
+DO \
+	LOOP { iam (KlattGrid); \
+		KlattGrid_addFormantFrequencyAndBandwidthTiers (me, formantType, GET_INTEGER (L"Position")); \
+		praat_dataChanged (me); \
+	} \
+END
+
+#define KlattGrid_REMOVE_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
+FORM (KlattGrid_remove##Name##FormantFrequencyAndBandwidthTiers, L"KlattGrid: Remove " #namef "ormant", 0) \
+	INTEGER (L"Position", L"0 (=do nothing)") \
+	OK \
+DO \
+	LOOP { iam (KlattGrid); \
+		KlattGrid_removeFormantFrequencyAndBandwidthTiers (me, formantType, GET_INTEGER (L"Position")); \
+		praat_dataChanged (me); \
+	} \
+END
+
+#define KlattGrid_ADD_FORMANT_AMPLITUDETIER(Name,namef,formantType) \
+FORM (KlattGrid_add##Name##FormantAmplitudeTier, L"KlattGrid: Add " #namef "ormant amplitude tier", 0) \
+	INTEGER (L"Position", L"0 (=at end)") \
+	OK \
+DO \
+	LOOP { iam (KlattGrid); \
+		KlattGrid_addFormantAmplitudeTier (me, formantType, GET_INTEGER (L"Position")); \
+		praat_dataChanged (me); \
+	} \
+END
+
+#define KlattGrid_REMOVE_FORMANT_AMPLITUDETIER(Name,namef,formantType) \
+FORM (KlattGrid_remove##Name##FormantAmplitudeTier, L"KlattGrid: Remove " #namef "ormant amplitude tier", 0) \
+	INTEGER (L"Position", L"0 (=do nothing)") \
+	OK \
+DO \
+	LOOP { iam (KlattGrid); \
+		KlattGrid_removeFormant (me, formantType, GET_INTEGER (L"Position")); \
+		praat_dataChanged (me); \
+	} \
+END
+
+
 #define KlattGrid_FORMULA_ADD_REMOVE_FBA(Name,namef,formantType) \
 KlattGrid_FORMULA_FORMANT_FBA_VALUE (Name, namef, Frequencies, frequencies, L"if row = 2 then self + 200 else self fi", formantType, L" ") \
 KlattGrid_FORMULA_FORMANT_FBA_VALUE (Name, namef, Bandwidths, bandwidths, L"self / 10 ; 10% of frequency", formantType, L"Warning: self is formant frequency.") \
@@ -426,7 +469,12 @@ KlattGrid_REMOVE_FBA_VALUE (Name, namef, Formant, Frequency, frequency, formantT
 KlattGrid_REMOVE_FBA_VALUE (Name, namef, Bandwidth, Bandwidth, bandwidth, formantType) \
 KlattGrid_REMOVE_FBA_VALUE (Name, namef, Amplitude, Amplitude, amplitude, formantType) \
 KlattGrid_ADD_FORMANT(Name,namef,formantType) \
-KlattGrid_REMOVE_FORMANT(Name,namef,formantType)
+KlattGrid_ADD_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
+KlattGrid_REMOVE_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
+KlattGrid_REMOVE_FORMANT_AMPLITUDETIER(Name,namef,formantType) \
+KlattGrid_REMOVE_FORMANT(Name,namef,formantType) \
+KlattGrid_ADD_FORMANT_AMPLITUDETIER(Name,namef,formantType)
+
 
 #define KlattGrid_FORMULA_ADD_REMOVE_FB(Name,namef,formantType) \
 KlattGrid_FORMULA_FORMANT_FBA_VALUE (Name, namef, Frequencies, frequencies, L"if row = 2 then self + 200 else self fi",formantType, L" ") \
@@ -436,6 +484,8 @@ KlattGrid_ADD_FBA_VALUE (Name, namef, Bandwidth, Bandwidth, bandwidth, formantTy
 KlattGrid_REMOVE_FBA_VALUE (Name, namef, Formant, Frequency, frequency, formantType) \
 KlattGrid_REMOVE_FBA_VALUE (Name, namef, Bandwidth, Bandwidth, bandwidth, formantType) \
 KlattGrid_ADD_FORMANT(Name,namef,formantType) \
+KlattGrid_ADD_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
+KlattGrid_REMOVE_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
 KlattGrid_REMOVE_FORMANT(Name,namef,formantType)
 
 #define KlattGrid_FORMULA_ADD_REMOVE_FB_DELTA(Name,namef,formantType) \
@@ -445,6 +495,8 @@ KlattGrid_ADD_FBA_VALUE (Name, namef, Formant,Frequency, frequency, formantType,
 KlattGrid_ADD_FBA_VALUE (Name, namef, Bandwidth, Bandwidth, bandwidth, formantType,  L"-50.0", (Hz), (value!=NUMundefined), L"Bandwidth must be defined.") \
 KlattGrid_REMOVE_FBA_VALUE (Name, namef, Formant, Frequency, frequency, formantType) \
 KlattGrid_REMOVE_FBA_VALUE (Name, namef, Bandwidth, Bandwidth, bandwidth, formantType) \
+KlattGrid_ADD_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
+KlattGrid_REMOVE_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
 KlattGrid_ADD_FORMANT(Name,namef,formantType) \
 KlattGrid_REMOVE_FORMANT(Name,namef,formantType)
 
@@ -692,7 +744,7 @@ DO
 	long gridType = GET_INTEGER (L"Formant type");
 	LOOP {
 		iam (KlattGrid);
-		KlattGrid_addFormantAndBandwidthTier (me, gridType, GET_INTEGER (L"Position"));
+		KlattGrid_addFormantFrequencyAndBandwidthTiers (me, gridType, GET_INTEGER (L"Position"));
 		praat_dataChanged (me);
 	}
 END
@@ -1018,27 +1070,27 @@ void praat_KlattGrid_init () {
 	praat_addAction1 (classKlattGrid, 1, L"Get delta formant at time...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_getDeltaFormantAtTime);
 	praat_addAction1 (classKlattGrid, 1, L"Get delta bandwidth at time...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_getDeltaBandwidthAtTime);
 
-#define KlattGRID_GET_FORMANT_FB_VALUES_ACTION(Name,namef) \
-	praat_addAction1 (classKlattGrid, 1, L"Get " #namef "ormant frequency at time...", 0, 1, DO_KlattGrid_get##Name##FormantFrequencyAtTime); \
-	praat_addAction1 (classKlattGrid, 1, L"Get " #namef "ormant bandwidth at time...", 0, 1, DO_KlattGrid_get##Name##FormantBandwidthAtTime);
+#define KlattGRID_GET_FORMANT_FB_VALUES_ACTION(Name, formantname) \
+	praat_addAction1 (classKlattGrid, 1, L"Get " #formantname " frequency at time...", 0, 1, DO_KlattGrid_get##Name##FormantFrequencyAtTime); \
+	praat_addAction1 (classKlattGrid, 1, L"Get " #formantname " bandwidth at time...", 0, 1, DO_KlattGrid_get##Name##FormantBandwidthAtTime);
 
-#define KlattGRID_GET_FORMANT_A_VALUES_ACTION(Name,name) \
-	praat_addAction1 (classKlattGrid, 1, L"Get " #name " formant amplitude at time...", 0, 1, DO_KlattGrid_get##Name##FormantAmplitudeAtTime); \
+#define KlattGRID_GET_FORMANT_A_VALUES_ACTION(Name,formantname) \
+	praat_addAction1 (classKlattGrid, 1, L"Get " #formantname " amplitude at time...", 0, 1, DO_KlattGrid_get##Name##FormantAmplitudeAtTime); \
 
-	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Oral, oral f)
-	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Oral, oral)
-	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Nasal, nasal f)
-	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Nasal, nasal)
-	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (NasalAnti, nasal antif)
+	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Oral, oral formant)
+	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Oral, oral formant)
+	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Nasal, nasal formant)
+	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Nasal, nasal formant)
+	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (NasalAnti, nasal antiformant)
 
 	praat_addAction1 (classKlattGrid, 1, L"-- query delta characteristics", 0, 1, 0);
-	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Delta, delta f)
-	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Tracheal, tracheal f)
-	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Tracheal, tracheal)
-	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (TrachealAnti, tracheal antif)
+	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Delta, delta formant)
+	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Tracheal, tracheal formant)
+	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Tracheal, tracheal formant)
+	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (TrachealAnti, tracheal antiformant)
 	praat_addAction1 (classKlattGrid, 1, L"-- query frication characteristics", 0, 1, 0);
-	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Frication, frication f)
-	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Frication, frication)
+	KlattGRID_GET_FORMANT_FB_VALUES_ACTION (Frication, frication formant)
+	KlattGRID_GET_FORMANT_A_VALUES_ACTION (Frication, frication formant)
 
 #undef KlattGRID_GET_FORMANT_A_VALUES_ACTION
 #undef KlattGRID_GET_FORMANT_A_VALUES_ACTION
@@ -1077,33 +1129,39 @@ void praat_KlattGrid_init () {
 
 	praat_addAction1 (classKlattGrid, 0, L"Modify vocal tract -", 0, 0, 0);
 
-#define KlattGrid_MODIFY_ACTION_FBA(Name,namef) \
-	praat_addAction1 (classKlattGrid, 0, L"Formula (" #namef "ormant frequencies)...", 0, 1, DO_KlattGrid_formula##Name##FormantFrequencies); \
-	praat_addAction1 (classKlattGrid, 0, L"Formula (" #namef "ormant bandwidths)...", 0, 1, DO_KlattGrid_formula##Name##FormantBandwidths); \
-	praat_addAction1 (classKlattGrid, 0, L"Add " #namef "ormant frequency point...", 0, 1, DO_KlattGrid_add##Name##FormantFrequencyPoint); \
-	praat_addAction1 (classKlattGrid, 0, L"Add " #namef "ormant bandwidth point...", 0, 1, DO_KlattGrid_add##Name##FormantBandwidthPoint); \
-	praat_addAction1 (classKlattGrid, 0, L"Add " #namef "ormant amplitude point...", 0, 1, DO_KlattGrid_add##Name##FormantAmplitudePoint); \
-	praat_addAction1 (classKlattGrid, 0, L"Remove " #namef "ormant frequency points...", 0, 1, DO_KlattGrid_remove##Name##FormantFrequencyPoints); \
-	praat_addAction1 (classKlattGrid, 0, L"Remove " #namef "ormant bandwidth points...", 0, 1, DO_KlattGrid_remove##Name##FormantBandwidthPoints); \
-	praat_addAction1 (classKlattGrid, 0, L"Remove " #namef "ormant amplitude points...", 0, 1, DO_KlattGrid_remove##Name##FormantAmplitudePoints); \
-	praat_addAction1 (classKlattGrid, 0, L"Add " #namef "ormant...", 0, 1, DO_KlattGrid_add##Name##Formant); \
-	praat_addAction1 (classKlattGrid, 0, L"Remove " #namef "ormant...", 0, 1, DO_KlattGrid_remove##Name##Formant);
-
-#define KlattGrid_MODIFY_ACTION_FB(Name,namef) \
-	praat_addAction1 (classKlattGrid, 0, L"Formula (" #namef "ormant frequencies)...", 0, 1, DO_KlattGrid_formula##Name##FormantFrequencies); \
-	praat_addAction1 (classKlattGrid, 0, L"Formula (" #namef "ormant bandwidths)...", 0, 1, DO_KlattGrid_formula##Name##FormantBandwidths); \
-	praat_addAction1 (classKlattGrid, 0, L"Add " #namef "ormant frequency point...", 0, 1, DO_KlattGrid_add##Name##FormantFrequencyPoint); \
-	praat_addAction1 (classKlattGrid, 0, L"Add " #namef "ormant bandwidth point...", 0, 1, DO_KlattGrid_add##Name##FormantBandwidthPoint); \
-	praat_addAction1 (classKlattGrid, 0, L"Remove " #namef "ormant frequency points...", 0, 1, DO_KlattGrid_remove##Name##FormantFrequencyPoints); \
-	praat_addAction1 (classKlattGrid, 0, L"Remove " #namef "ormant bandwidth points...", 0, 1, DO_KlattGrid_remove##Name##FormantBandwidthPoints); \
-	praat_addAction1 (classKlattGrid, 0, L"Add " #namef "ormant...", 0, 1, DO_KlattGrid_add##Name##Formant); \
-	praat_addAction1 (classKlattGrid, 0, L"Remove " #namef "ormant...", 0, 1, DO_KlattGrid_remove##Name##Formant);
-
-	KlattGrid_MODIFY_ACTION_FBA (Oral, oral f)
+#define KlattGrid_MODIFY_ACTIONS_FBA(Name,formantname) \
+	praat_addAction1 (classKlattGrid, 0, L"Formula (" #formantname " frequencies)...", 0, 1, DO_KlattGrid_formula##Name##FormantFrequencies); \
+	praat_addAction1 (classKlattGrid, 0, L"Formula (" #formantname " bandwidths)...", 0, 1, DO_KlattGrid_formula##Name##FormantBandwidths); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " frequency point...", 0, 1, DO_KlattGrid_add##Name##FormantFrequencyPoint); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " bandwidth point...", 0, 1, DO_KlattGrid_add##Name##FormantBandwidthPoint); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " amplitude point...", 0, 1, DO_KlattGrid_add##Name##FormantAmplitudePoint); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " frequency points...", 0, 1, DO_KlattGrid_remove##Name##FormantFrequencyPoints); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " bandwidth points...", 0, 1, DO_KlattGrid_remove##Name##FormantBandwidthPoints); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " amplitude points...", 0, 1, DO_KlattGrid_remove##Name##FormantAmplitudePoints); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " frequency and bandwidth tiers...", 0, 1, DO_KlattGrid_add##Name##FormantFrequencyAndBandwidthTiers); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " frequency and bandwidth tiers...", 0, 1, DO_KlattGrid_remove##Name##FormantFrequencyAndBandwidthTiers); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " amplitude tier...", 0, 1, DO_KlattGrid_add##Name##FormantAmplitudeTier); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " amplitude tier...", 0, 1, DO_KlattGrid_remove##Name##FormantAmplitudeTier); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname "...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_add##Name##Formant); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname "...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_remove##Name##Formant);
+
+#define KlattGrid_MODIFY_ACTIONS_FB(Name,formantname) \
+	praat_addAction1 (classKlattGrid, 0, L"Formula (" #formantname " frequencies)...", 0, 1, DO_KlattGrid_formula##Name##FormantFrequencies); \
+	praat_addAction1 (classKlattGrid, 0, L"Formula (" #formantname " bandwidths)...", 0, 1, DO_KlattGrid_formula##Name##FormantBandwidths); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " frequency point...", 0, 1, DO_KlattGrid_add##Name##FormantFrequencyPoint); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " bandwidth point...", 0, 1, DO_KlattGrid_add##Name##FormantBandwidthPoint); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " frequency points...", 0, 1, DO_KlattGrid_remove##Name##FormantFrequencyPoints); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " bandwidth points...", 0, 1, DO_KlattGrid_remove##Name##FormantBandwidthPoints); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname " frequency and bandwidth tiers...", 0, 1, DO_KlattGrid_add##Name##FormantFrequencyAndBandwidthTiers); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname " frequency and bandwidth tiers...", 0, 1, DO_KlattGrid_remove##Name##FormantFrequencyAndBandwidthTiers); \
+	praat_addAction1 (classKlattGrid, 0, L"Add " #formantname "...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_add##Name##Formant); \
+	praat_addAction1 (classKlattGrid, 0, L"Remove " #formantname "...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_remove##Name##Formant);
+
+	KlattGrid_MODIFY_ACTIONS_FBA (Oral, oral formant)
 	praat_addAction1 (classKlattGrid, 0, L"-- oral modify separator --", 0, 1, 0);
-	KlattGrid_MODIFY_ACTION_FBA (Nasal, nasal f)
+	KlattGrid_MODIFY_ACTIONS_FBA (Nasal, nasal formant)
 	praat_addAction1 (classKlattGrid, 0, L"-- nasal modify separator --", 0, 1, 0);
-	KlattGrid_MODIFY_ACTION_FB (NasalAnti, nasal antif)
+	KlattGrid_MODIFY_ACTIONS_FB (NasalAnti, nasal antiformant)
 
 	praat_addAction1 (classKlattGrid, 0, L"Formula (frequencies)...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_formula_frequencies);
 	praat_addAction1 (classKlattGrid, 0, L"Formula (bandwidths)...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_formula_bandwidths);
@@ -1114,11 +1172,11 @@ void praat_KlattGrid_init () {
 	praat_addAction1 (classKlattGrid, 0, L"Remove bandwidth points between...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_removeBandwidthPoints);
 	praat_addAction1 (classKlattGrid, 0, L"Remove amplitude points between...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_removeAmplitudePoints);
 	praat_addAction1 (classKlattGrid, 0, L"Modify coupling - ", 0, 0, 0);
-	KlattGrid_MODIFY_ACTION_FB (Delta, delta f)
+	KlattGrid_MODIFY_ACTIONS_FB (Delta, delta formant)
 	praat_addAction1 (classKlattGrid, 0, L"-- delta modify separator --", 0, 1, 0);
-	KlattGrid_MODIFY_ACTION_FBA (Tracheal, tracheal f)
+	KlattGrid_MODIFY_ACTIONS_FBA (Tracheal, tracheal formant)
 	praat_addAction1 (classKlattGrid, 0, L"-- nasal modify separator --", 0, 1, 0);
-	KlattGrid_MODIFY_ACTION_FB (TrachealAnti, tracheal antif)
+	KlattGrid_MODIFY_ACTIONS_FB (TrachealAnti, tracheal antiformant)
 
 	praat_addAction1 (classKlattGrid, 0, L"Add delta formant point...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_addDeltaFormantPoint);
 	praat_addAction1 (classKlattGrid, 0, L"Add delta bandwidth point...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_addDeltaBandwidthPoint);
@@ -1126,7 +1184,7 @@ void praat_KlattGrid_init () {
 	praat_addAction1 (classKlattGrid, 0, L"Remove delta bandwidth points between...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_KlattGrid_removeDeltaBandwidthPoints);
 
 	praat_addAction1 (classKlattGrid, 0, L"Modify frication -", 0, 0, 0);
-	KlattGrid_MODIFY_ACTION_FBA (Frication, frication f)
+	KlattGrid_MODIFY_ACTIONS_FBA (Frication, frication formant)
 	praat_addAction1 (classKlattGrid, 0, L"-- frication modify separator --", 0, 1, 0);
 
 	praat_addAction1 (classKlattGrid, 0, L"Add frication bypass point...", 0, 1, DO_KlattGrid_addFricationBypassPoint);
@@ -1233,4 +1291,4 @@ void praat_KlattGrid_init () {
 }
 
 
-/* End of file praat_KlattGrid_init.cpp */
\ No newline at end of file
+/* End of file praat_KlattGrid_init.cpp */
diff --git a/external/flac/READ_ME.TXT b/external/flac/READ_ME.TXT
index 360b364..9ccb292 100644
--- a/external/flac/READ_ME.TXT
+++ b/external/flac/READ_ME.TXT
@@ -1,5 +1,5 @@
 Praats/external/flac/READ_ME.TXT
-Paul Boersma, May 13, 2008
+Paul Boersma, 26 December 2008
 This file describes the adaptations to the FLAC 1.2.1 sources
 that are needed to make them compatible with Praat.
 
@@ -14,7 +14,10 @@ compiler message).
 
 For MinGW we need to change in flac_share_alloc.h:
 
-#if !defined _MSC_VER && !defined __EMX__
#include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */
#endif
+#if !defined _MSC_VER && !defined __EMX__
+#include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */
+#endif
+
 
 For win32 on Metrowerks CodeWarrior we do a
 #if defined (_WIN32) && ! defined (off_t)
@@ -42,3 +45,19 @@ the following is added to flac_FLAC_formant.h:
 	#include "melder.h"
 	#define fopen(filename,mode)  _wfopen (Melder_peekWcsToUtf16 (Melder_peekUtf8ToWcs (filename)), L"" mode)
 #endif
+
+There were two mistakes in FLAC__MD%Final () in flac_md5.c:
+	memset(ctx, 0, sizeof(ctx));	/* In case it's sensitive */
+	if(0 != ctx->internal_buf) {
+		free(ctx->internal_buf);
+		ctx->internal_buf = 0;
+		ctx->capacity = 0;
+	}
+should be
+	if(0 != ctx->internal_buf) {   // test before clearing!
+		free(ctx->internal_buf);
+		ctx->internal_buf = 0;
+		ctx->capacity = 0;
+	}
+	memset(ctx, 0, sizeof(*ctx));	// clear the whole structure, not just the first four or eight bytes!
+
diff --git a/external/flac/flac_md5.c b/external/flac/flac_md5.c
index 58a21fa..2f9f24d 100644
--- a/external/flac/flac_md5.c
+++ b/external/flac/flac_md5.c
@@ -264,12 +264,22 @@ void FLAC__MD5Final(FLAC__byte digest[16], FLAC__MD5Context *ctx)
 
 	byteSwap(ctx->buf, 4);
 	memcpy(digest, ctx->buf, 16);
-	memset(ctx, 0, sizeof(ctx));	/* In case it's sensitive */
-	if(0 != ctx->internal_buf) {
-		free(ctx->internal_buf);
-		ctx->internal_buf = 0;
-		ctx->capacity = 0;
-	}
+	#if 0
+		memset(ctx, 0, sizeof(ctx));	/* In case it's sensitive */
+		if(0 != ctx->internal_buf) {
+			free(ctx->internal_buf);
+			ctx->internal_buf = 0;
+			ctx->capacity = 0;
+		}
+	#else
+		// ppgb BUGFIX: found two mistakes, hence:   // 20131226
+		if(0 != ctx->internal_buf) {
+			free(ctx->internal_buf);
+			ctx->internal_buf = 0;
+			ctx->capacity = 0;
+		}
+		memset(ctx, 0, sizeof(*ctx));	/* In case it's sensitive */
+	#endif
 }
 
 /*
diff --git a/external/glpk/glplpx02.c b/external/glpk/glplpx02.c
index 3e6ca41..0bf3f5c 100644
--- a/external/glpk/glplpx02.c
+++ b/external/glpk/glplpx02.c
@@ -1,3 +1,4 @@
+#pragma GCC diagnostic ignored "-Wall"
 /* glplpx02.c */
 
 /***********************************************************************
diff --git a/external/portaudio/Makefile b/external/portaudio/Makefile
index 7e2bc03..a8065f7 100644
--- a/external/portaudio/Makefile
+++ b/external/portaudio/Makefile
@@ -1,9 +1,9 @@
 # Makefile of the library "external/portaudio"
-# Paul Boersma, 14 January 2012
+# Paul Boersma, 14 February 2014
 
 include ../../makefile.defs
 
-OBJECTS = pa_skeleton.o \
+OBJECTS = \
 	pa_unix_hostapis.o pa_unix_util.o pa_linux_alsa.o \
 	pa_win_hostapis.o pa_win_util.o pa_win_wmme.o pa_win_waveformat.o \
 	pa_front.o pa_debugprint.o pa_cpuload.o \
diff --git a/external/portaudio/READ_ME.TXT b/external/portaudio/READ_ME.TXT
index 14c6705..dbd25c2 100644
--- a/external/portaudio/READ_ME.TXT
+++ b/external/portaudio/READ_ME.TXT
@@ -1,22 +1,35 @@
 Praats/external/portaudio/READ_ME.TXT
-Paul Boersma, 1 September 2013
-This file describes the adaptations to the PortAudio v19 sources (2007/11)
+Paul Boersma, 14 February 2014
+This file describes the adaptations to the PortAudio v19 sources (2014/01)
 that are needed to make them compatible with Praat.
 
-Deleted lines in pa_***_hostapis.c.
+Deleted many lines in pa_***_hostapis.c.
 
-At the top of pa_win_*.c:
-#undef UNICODE
+(At the top of pa_win_*.c: #undef UNICODE)
 
-In pa_win_mme.c, remove the #ifndef/endif from:
-#ifndef __MWERKS__
-#include <malloc.h>
-#include <memory.h>
-#endif /* __MWERKS__ */
+Duplicate pa_unix_util.c to pa_mac_util.c, but only for allocation and time routines.
 
-In pa_mac_core.c:
-#include <Components.h>
+Remove the hard-coded definition of SIZEOF_LONG from pa_types.h and instead use <stdint.h> to define PaInt32 and the like.
 
-Duplicate pa_unix_util.c to pa_mac_util.c, but only for allocation and time routines.
+Around pa_linux_alsa.c, do
+#if defined (UNIX) && defined (ALSA)
+...
+#endif
+
+Around pa_win_*.c, do
+#ifdef _WIN32
+...
+#endif
+
+Around pa_unix_util.c, do
+#if defined (UNIX)
+...
+#endif
+
+Add prototype for
+PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex );
+in pa_linux_als.h
 
-Remove the hard-coded definition of SIZEOF_LONG from pa_types.h and instead use <stdint.h> to define PaInt32 and the like. Correct the associated error in pa_dither.c: SIZEOF_LONG -> sizeof(long).
\ No newline at end of file
+Insert
+void
+in declaration and definition of PaUnix_InitializeThreading.
diff --git a/external/portaudio/pa_allocation.h b/external/portaudio/pa_allocation.h
index b265b01..811dd72 100644
--- a/external/portaudio/pa_allocation.h
+++ b/external/portaudio/pa_allocation.h
@@ -1,12 +1,12 @@
 #ifndef PA_ALLOCATION_H
 #define PA_ALLOCATION_H
 /*
- * $Id: pa_allocation.h 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_allocation.h 1339 2008-02-15 07:50:33Z rossb $
  * Portable Audio I/O Library allocation context header
  * memory allocation context for tracking allocation groups
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ * Copyright (c) 1999-2008 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -43,12 +43,12 @@
  @ingroup common_src
 
  @brief Allocation Group prototypes. An Allocation Group makes it easy to
- allocate multiple blocks of memory and free them all simultanously.
+ allocate multiple blocks of memory and free them all at once.
  
  An allocation group is useful for keeping track of multiple blocks
  of memory which are allocated at the same time (such as during initialization)
  and need to be deallocated at the same time. The allocation group maintains
- a list of allocated blocks, and can deallocate them all simultaneously which
+ a list of allocated blocks, and can free all allocations at once. This
  can be usefull for cleaning up after a partially initialized object fails.
 
  The allocation group implementation is built on top of the lower
diff --git a/external/portaudio/pa_asio.cpp b/external/portaudio/pa_asio.cpp
new file mode 100644
index 0000000..f3a16a7
--- /dev/null
+++ b/external/portaudio/pa_asio.cpp
@@ -0,0 +1,4251 @@
+/*
+ * $Id: pa_asio.cpp 1890 2013-05-02 01:06:01Z rbencina $
+ * Portable Audio I/O Library for ASIO Drivers
+ *
+ * Author: Stephane Letz
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 2000-2002 Stephane Letz, Phil Burk, Ross Bencina
+ * Blocking i/o implementation by Sven Fischer, Institute of Hearing
+ * Technology and Audiology (www.hoertechnik-audiologie.de)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+/* Modification History
+
+        08-03-01 First version : Stephane Letz
+        08-06-01 Tweaks for PC, use C++, buffer allocation, Float32 to Int32 conversion : Phil Burk
+        08-20-01 More conversion, PA_StreamTime, Pa_GetHostError : Stephane Letz
+        08-21-01 PaUInt8 bug correction, implementation of ASIOSTFloat32LSB and ASIOSTFloat32MSB native formats : Stephane Letz
+        08-24-01 MAX_INT32_FP hack, another Uint8 fix : Stephane and Phil
+        08-27-01 Implementation of hostBufferSize < userBufferSize case, better management of the ouput buffer when
+                 the stream is stopped : Stephane Letz
+        08-28-01 Check the stream pointer for null in bufferSwitchTimeInfo, correct bug in bufferSwitchTimeInfo when
+                 the stream is stopped : Stephane Letz
+        10-12-01 Correct the PaHost_CalcNumHostBuffers function: computes FramesPerHostBuffer to be the lowest that
+                 respect requested FramesPerUserBuffer and userBuffersPerHostBuffer : Stephane Letz
+        10-26-01 Management of hostBufferSize and userBufferSize of any size : Stephane Letz
+        10-27-01 Improve calculus of hostBufferSize to be multiple or divisor of userBufferSize if possible : Stephane and Phil
+        10-29-01 Change MAX_INT32_FP to (2147483520.0f) to prevent roundup to 0x80000000 : Phil Burk
+        10-31-01 Clear the ouput buffer and user buffers in PaHost_StartOutput, correct bug in GetFirstMultiple : Stephane Letz
+        11-06-01 Rename functions : Stephane Letz
+        11-08-01 New Pa_ASIO_Adaptor_Init function to init Callback adpatation variables, cleanup of Pa_ASIO_Callback_Input: Stephane Letz
+        11-29-01 Break apart device loading to debug random failure in Pa_ASIO_QueryDeviceInfo ; Phil Burk
+        01-03-02 Desallocate all resources in PaHost_Term for cases where Pa_CloseStream is not called properly :  Stephane Letz
+        02-01-02 Cleanup, test of multiple-stream opening : Stephane Letz
+        19-02-02 New Pa_ASIO_loadDriver that calls CoInitialize on each thread on Windows : Stephane Letz
+        09-04-02 Correct error code management in PaHost_Term, removes various compiler warning : Stephane Letz
+        12-04-02 Add Mac includes for <Devices.h> and <Timer.h> : Phil Burk
+        13-04-02 Removes another compiler warning : Stephane Letz
+        30-04-02 Pa_ASIO_QueryDeviceInfo bug correction, memory allocation checking, better error handling : D Viens, P Burk, S Letz
+        12-06-02 Rehashed into new multi-api infrastructure, added support for all ASIO sample formats : Ross Bencina
+        18-06-02 Added pa_asio.h, PaAsio_GetAvailableLatencyValues() : Ross B.
+        21-06-02 Added SelectHostBufferSize() which selects host buffer size based on user latency parameters : Ross Bencina
+        ** NOTE  maintanance history is now stored in CVS **
+*/
+
+/** @file
+    @ingroup hostapi_src
+
+    Note that specific support for paInputUnderflow, paOutputOverflow and
+    paNeverDropInput is not necessary or possible with this driver due to the
+    synchronous full duplex double-buffered architecture of ASIO.
+*/
+
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+//#include <values.h>
+#include <new>
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#include "portaudio.h"
+#include "pa_asio.h"
+#include "pa_util.h"
+#include "pa_allocation.h"
+#include "pa_hostapi.h"
+#include "pa_stream.h"
+#include "pa_cpuload.h"
+#include "pa_process.h"
+#include "pa_debugprint.h"
+#include "pa_ringbuffer.h"
+
+#include "pa_win_coinitialize.h"
+
+/* This version of pa_asio.cpp is currently only targetted at Win32,
+   It would require a few tweaks to work with pre-OS X Macintosh.
+   To make configuration easier, we define WIN32 here to make sure
+   that the ASIO SDK knows this is Win32.
+*/
+#ifndef WIN32
+#define WIN32
+#endif
+
+#include "asiosys.h"
+#include "asio.h"
+#include "asiodrivers.h"
+#include "iasiothiscallresolver.h"
+
+/*
+#if MAC
+#include <Devices.h>
+#include <Timer.h>
+#include <Math64.h>
+#else
+*/
+/*
+#include <math.h>
+#include <windows.h>
+#include <mmsystem.h>
+*/
+/*
+#endif
+*/
+
+
+/* winmm.lib is needed for timeGetTime() (this is in winmm.a if you're using gcc) */
+#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
+#pragma comment(lib, "winmm.lib")
+#endif
+
+
+/* external reference to ASIO SDK's asioDrivers.
+
+ This is a bit messy because we want to explicitly manage 
+ allocation/deallocation of this structure, but some layers of the SDK 
+ which we currently use (eg the implementation in asio.cpp) still
+ use this global version.
+
+ For now we keep it in sync with our local instance in the host
+ API representation structure, but later we should be able to remove
+ all dependence on it.
+*/
+extern AsioDrivers* asioDrivers;
+
+
+/* We are trying to be compatible with CARBON but this has not been thoroughly tested. */
+/* not tested at all since new V19 code was introduced. */
+#define CARBON_COMPATIBLE  (0)
+
+
+/* prototypes for functions declared in this file */
+
+extern "C" PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex );
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                           PaStream** s,
+                           const PaStreamParameters *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           double sampleRate,
+                           unsigned long framesPerBuffer,
+                           PaStreamFlags streamFlags,
+                           PaStreamCallback *streamCallback,
+                           void *userData );
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate );
+static PaError CloseStream( PaStream* stream );
+static PaError StartStream( PaStream *stream );
+static PaError StopStream( PaStream *stream );
+static PaError AbortStream( PaStream *stream );
+static PaError IsStreamStopped( PaStream *s );
+static PaError IsStreamActive( PaStream *stream );
+static PaTime GetStreamTime( PaStream *stream );
+static double GetStreamCpuLoad( PaStream* stream );
+static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames );
+static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames );
+static signed long GetStreamReadAvailable( PaStream* stream );
+static signed long GetStreamWriteAvailable( PaStream* stream );
+
+/* Blocking i/o callback function. */
+static int BlockingIoPaCallback(const void                     *inputBuffer    ,
+                                      void                     *outputBuffer   ,
+                                      unsigned long             framesPerBuffer,
+                                const PaStreamCallbackTimeInfo *timeInfo       ,
+                                      PaStreamCallbackFlags     statusFlags    ,
+                                      void                     *userData       );
+
+/* our ASIO callback functions */
+
+static void bufferSwitch(long index, ASIOBool processNow);
+static ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow);
+static void sampleRateChanged(ASIOSampleRate sRate);
+static long asioMessages(long selector, long value, void* message, double* opt);
+
+static ASIOCallbacks asioCallbacks_ =
+    { bufferSwitch, sampleRateChanged, asioMessages, bufferSwitchTimeInfo };
+
+
+#define PA_ASIO_SET_LAST_HOST_ERROR( errorCode, errorText ) \
+    PaUtil_SetLastHostErrorInfo( paASIO, errorCode, errorText )
+
+
+static void PaAsio_SetLastSystemError( DWORD errorCode )
+{
+    LPVOID lpMsgBuf;
+    FormatMessage(
+        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+        NULL,
+        errorCode,
+        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+        (LPTSTR) &lpMsgBuf,
+        0,
+        NULL
+    );
+    PaUtil_SetLastHostErrorInfo( paASIO, errorCode, (const char*)lpMsgBuf );
+    LocalFree( lpMsgBuf );
+}
+
+#define PA_ASIO_SET_LAST_SYSTEM_ERROR( errorCode ) \
+    PaAsio_SetLastSystemError( errorCode )
+
+
+static const char* PaAsio_GetAsioErrorText( ASIOError asioError )
+{
+    const char *result;
+
+    switch( asioError ){
+        case ASE_OK:
+        case ASE_SUCCESS:           result = "Success"; break;
+        case ASE_NotPresent:        result = "Hardware input or output is not present or available"; break;
+        case ASE_HWMalfunction:     result = "Hardware is malfunctioning"; break;
+        case ASE_InvalidParameter:  result = "Input parameter invalid"; break;
+        case ASE_InvalidMode:       result = "Hardware is in a bad mode or used in a bad mode"; break;
+        case ASE_SPNotAdvancing:    result = "Hardware is not running when sample position is inquired"; break;
+        case ASE_NoClock:           result = "Sample clock or rate cannot be determined or is not present"; break;
+        case ASE_NoMemory:          result = "Not enough memory for completing the request"; break;
+        default:                    result = "Unknown ASIO error"; break;
+    }
+
+    return result;
+}
+
+
+#define PA_ASIO_SET_LAST_ASIO_ERROR( asioError ) \
+    PaUtil_SetLastHostErrorInfo( paASIO, asioError, PaAsio_GetAsioErrorText( asioError ) )
+
+
+
+
+// Atomic increment and decrement operations
+#if MAC
+    /* need to be implemented on Mac */
+    inline long PaAsio_AtomicIncrement(volatile long* v) {return ++(*const_cast<long*>(v));}
+    inline long PaAsio_AtomicDecrement(volatile long* v) {return --(*const_cast<long*>(v));}
+#elif WINDOWS
+    inline long PaAsio_AtomicIncrement(volatile long* v) {return InterlockedIncrement(const_cast<long*>(v));}
+    inline long PaAsio_AtomicDecrement(volatile long* v) {return InterlockedDecrement(const_cast<long*>(v));}
+#endif
+
+
+
+typedef struct PaAsioDriverInfo
+{
+    ASIODriverInfo asioDriverInfo;
+    long inputChannelCount, outputChannelCount;
+    long bufferMinSize, bufferMaxSize, bufferPreferredSize, bufferGranularity;
+    bool postOutput;
+}
+PaAsioDriverInfo;
+
+
+/* PaAsioHostApiRepresentation - host api datastructure specific to this implementation */
+
+typedef struct
+{
+    PaUtilHostApiRepresentation inheritedHostApiRep;
+    PaUtilStreamInterface callbackStreamInterface;
+    PaUtilStreamInterface blockingStreamInterface;
+
+    PaUtilAllocationGroup *allocations;
+
+    PaWinUtilComInitializationResult comInitializationResult;
+
+    AsioDrivers *asioDrivers;
+    void *systemSpecific;
+    
+    /* the ASIO C API only allows one ASIO driver to be open at a time,
+        so we keep track of whether we have the driver open here, and
+        use this information to return errors from OpenStream if the
+        driver is already open.
+
+        openAsioDeviceIndex will be PaNoDevice if there is no device open
+        and a valid pa_asio (not global) device index otherwise.
+
+        openAsioDriverInfo is populated with the driver info for the
+        currently open device (if any)
+    */
+    PaDeviceIndex openAsioDeviceIndex;
+    PaAsioDriverInfo openAsioDriverInfo;
+}
+PaAsioHostApiRepresentation;
+
+
+/*
+    Retrieve <driverCount> driver names from ASIO, returned in a char**
+    allocated in <group>.
+*/
+static char **GetAsioDriverNames( PaAsioHostApiRepresentation *asioHostApi, PaUtilAllocationGroup *group, long driverCount )
+{
+    char **result = 0;
+    int i;
+
+    result =(char**)PaUtil_GroupAllocateMemory(
+            group, sizeof(char*) * driverCount );
+    if( !result )
+        goto error;
+
+    result[0] = (char*)PaUtil_GroupAllocateMemory(
+            group, 32 * driverCount );
+    if( !result[0] )
+        goto error;
+
+    for( i=0; i<driverCount; ++i )
+        result[i] = result[0] + (32 * i);
+
+    asioHostApi->asioDrivers->getDriverNames( result, driverCount );
+
+error:
+    return result;
+}
+
+
+static PaSampleFormat AsioSampleTypeToPaNativeSampleFormat(ASIOSampleType type)
+{
+    switch (type) {
+        case ASIOSTInt16MSB:
+        case ASIOSTInt16LSB:
+                return paInt16;
+
+        case ASIOSTFloat32MSB:
+        case ASIOSTFloat32LSB:
+        case ASIOSTFloat64MSB:
+        case ASIOSTFloat64LSB:
+                return paFloat32;
+
+        case ASIOSTInt32MSB:
+        case ASIOSTInt32LSB:
+        case ASIOSTInt32MSB16:
+        case ASIOSTInt32LSB16:
+        case ASIOSTInt32MSB18:
+        case ASIOSTInt32MSB20:
+        case ASIOSTInt32MSB24:
+        case ASIOSTInt32LSB18:
+        case ASIOSTInt32LSB20:
+        case ASIOSTInt32LSB24:
+                return paInt32;
+
+        case ASIOSTInt24MSB:
+        case ASIOSTInt24LSB:
+                return paInt24;
+
+        default:
+                return paCustomFormat;
+    }
+}
+
+void AsioSampleTypeLOG(ASIOSampleType type)
+{
+    switch (type) {
+        case ASIOSTInt16MSB:  PA_DEBUG(("ASIOSTInt16MSB\n"));  break;
+        case ASIOSTInt16LSB:  PA_DEBUG(("ASIOSTInt16LSB\n"));  break;
+        case ASIOSTFloat32MSB:PA_DEBUG(("ASIOSTFloat32MSB\n"));break;
+        case ASIOSTFloat32LSB:PA_DEBUG(("ASIOSTFloat32LSB\n"));break;
+        case ASIOSTFloat64MSB:PA_DEBUG(("ASIOSTFloat64MSB\n"));break;
+        case ASIOSTFloat64LSB:PA_DEBUG(("ASIOSTFloat64LSB\n"));break;
+        case ASIOSTInt32MSB:  PA_DEBUG(("ASIOSTInt32MSB\n"));  break;
+        case ASIOSTInt32LSB:  PA_DEBUG(("ASIOSTInt32LSB\n"));  break;
+        case ASIOSTInt32MSB16:PA_DEBUG(("ASIOSTInt32MSB16\n"));break;
+        case ASIOSTInt32LSB16:PA_DEBUG(("ASIOSTInt32LSB16\n"));break;
+        case ASIOSTInt32MSB18:PA_DEBUG(("ASIOSTInt32MSB18\n"));break;
+        case ASIOSTInt32MSB20:PA_DEBUG(("ASIOSTInt32MSB20\n"));break;
+        case ASIOSTInt32MSB24:PA_DEBUG(("ASIOSTInt32MSB24\n"));break;
+        case ASIOSTInt32LSB18:PA_DEBUG(("ASIOSTInt32LSB18\n"));break;
+        case ASIOSTInt32LSB20:PA_DEBUG(("ASIOSTInt32LSB20\n"));break;
+        case ASIOSTInt32LSB24:PA_DEBUG(("ASIOSTInt32LSB24\n"));break;
+        case ASIOSTInt24MSB:  PA_DEBUG(("ASIOSTInt24MSB\n"));  break;
+        case ASIOSTInt24LSB:  PA_DEBUG(("ASIOSTInt24LSB\n"));  break;
+        default:              PA_DEBUG(("Custom Format%d\n",type));break;
+
+    }
+}
+
+static int BytesPerAsioSample( ASIOSampleType sampleType )
+{
+    switch (sampleType) {
+        case ASIOSTInt16MSB:
+        case ASIOSTInt16LSB:
+            return 2;
+
+        case ASIOSTFloat64MSB:
+        case ASIOSTFloat64LSB:
+            return 8;
+
+        case ASIOSTFloat32MSB:
+        case ASIOSTFloat32LSB:
+        case ASIOSTInt32MSB:
+        case ASIOSTInt32LSB:
+        case ASIOSTInt32MSB16:
+        case ASIOSTInt32LSB16:
+        case ASIOSTInt32MSB18:
+        case ASIOSTInt32MSB20:
+        case ASIOSTInt32MSB24:
+        case ASIOSTInt32LSB18:
+        case ASIOSTInt32LSB20:
+        case ASIOSTInt32LSB24:
+            return 4;
+
+        case ASIOSTInt24MSB:
+        case ASIOSTInt24LSB:
+            return 3;
+
+        default:
+            return 0;
+    }
+}
+
+
+static void Swap16( void *buffer, long shift, long count )
+{
+    unsigned short *p = (unsigned short*)buffer;
+    unsigned short temp;
+    (void) shift; /* unused parameter */
+
+    while( count-- )
+    {
+        temp = *p;
+        *p++ = (unsigned short)((temp<<8) | (temp>>8));
+    }
+}
+
+static void Swap24( void *buffer, long shift, long count )
+{
+    unsigned char *p = (unsigned char*)buffer;
+    unsigned char temp;
+    (void) shift; /* unused parameter */
+
+    while( count-- )
+    {
+        temp = *p;
+        *p = *(p+2);
+        *(p+2) = temp;
+        p += 3;
+    }
+}
+
+#define PA_SWAP32_( x ) ((x>>24) | ((x>>8)&0xFF00) | ((x<<8)&0xFF0000) | (x<<24));
+
+static void Swap32( void *buffer, long shift, long count )
+{
+    unsigned long *p = (unsigned long*)buffer;
+    unsigned long temp;
+    (void) shift; /* unused parameter */
+
+    while( count-- )
+    {
+        temp = *p;
+        *p++ = PA_SWAP32_( temp);
+    }
+}
+
+static void SwapShiftLeft32( void *buffer, long shift, long count )
+{
+    unsigned long *p = (unsigned long*)buffer;
+    unsigned long temp;
+
+    while( count-- )
+    {
+        temp = *p;
+        temp = PA_SWAP32_( temp);
+        *p++ = temp << shift;
+    }
+}
+
+static void ShiftRightSwap32( void *buffer, long shift, long count )
+{
+    unsigned long *p = (unsigned long*)buffer;
+    unsigned long temp;
+
+    while( count-- )
+    {
+        temp = *p >> shift;
+        *p++ = PA_SWAP32_( temp);
+    }
+}
+
+static void ShiftLeft32( void *buffer, long shift, long count )
+{
+    unsigned long *p = (unsigned long*)buffer;
+    unsigned long temp;
+
+    while( count-- )
+    {
+        temp = *p;
+        *p++ = temp << shift;
+    }
+}
+
+static void ShiftRight32( void *buffer, long shift, long count )
+{
+    unsigned long *p = (unsigned long*)buffer;
+    unsigned long temp;
+
+    while( count-- )
+    {
+        temp = *p;
+        *p++ = temp >> shift;
+    }
+}
+
+#define PA_SWAP_( x, y ) temp=x; x = y; y = temp;
+
+static void Swap64ConvertFloat64ToFloat32( void *buffer, long shift, long count )
+{
+    double *in = (double*)buffer;
+    float *out = (float*)buffer;
+    unsigned char *p;
+    unsigned char temp;
+    (void) shift; /* unused parameter */
+
+    while( count-- )
+    {
+        p = (unsigned char*)in;
+        PA_SWAP_( p[0], p[7] );
+        PA_SWAP_( p[1], p[6] );
+        PA_SWAP_( p[2], p[5] );
+        PA_SWAP_( p[3], p[4] );
+
+        *out++ = (float) (*in++);
+    }
+}
+
+static void ConvertFloat64ToFloat32( void *buffer, long shift, long count )
+{
+    double *in = (double*)buffer;
+    float *out = (float*)buffer;
+    (void) shift; /* unused parameter */
+
+    while( count-- )
+        *out++ = (float) (*in++);
+}
+
+static void ConvertFloat32ToFloat64Swap64( void *buffer, long shift, long count )
+{
+    float *in = ((float*)buffer) + (count-1);
+    double *out = ((double*)buffer) + (count-1);
+    unsigned char *p;
+    unsigned char temp;
+    (void) shift; /* unused parameter */
+
+    while( count-- )
+    {
+        *out = *in--;
+
+        p = (unsigned char*)out;
+        PA_SWAP_( p[0], p[7] );
+        PA_SWAP_( p[1], p[6] );
+        PA_SWAP_( p[2], p[5] );
+        PA_SWAP_( p[3], p[4] );
+
+        out--;
+    }
+}
+
+static void ConvertFloat32ToFloat64( void *buffer, long shift, long count )
+{
+    float *in = ((float*)buffer) + (count-1);
+    double *out = ((double*)buffer) + (count-1);
+    (void) shift; /* unused parameter */
+
+    while( count-- )
+        *out-- = *in--;
+}
+
+#ifdef MAC
+#define PA_MSB_IS_NATIVE_
+#undef PA_LSB_IS_NATIVE_
+#endif
+
+#ifdef WINDOWS
+#undef PA_MSB_IS_NATIVE_
+#define PA_LSB_IS_NATIVE_
+#endif
+
+typedef void PaAsioBufferConverter( void *, long, long );
+
+static void SelectAsioToPaConverter( ASIOSampleType type, PaAsioBufferConverter **converter, long *shift )
+{
+    *shift = 0;
+    *converter = 0;
+
+    switch (type) {
+        case ASIOSTInt16MSB:
+            /* dest: paInt16, no conversion necessary, possible byte swap*/
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap16;
+            #endif
+            break;
+        case ASIOSTInt16LSB:
+            /* dest: paInt16, no conversion necessary, possible byte swap*/
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap16;
+            #endif
+            break;
+        case ASIOSTFloat32MSB:
+            /* dest: paFloat32, no conversion necessary, possible byte swap*/
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTFloat32LSB:
+            /* dest: paFloat32, no conversion necessary, possible byte swap*/
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTFloat64MSB:
+            /* dest: paFloat32, in-place conversion to/from float32, possible byte swap*/
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap64ConvertFloat64ToFloat32;
+            #else
+                *converter = ConvertFloat64ToFloat32;
+            #endif
+            break;
+        case ASIOSTFloat64LSB:
+            /* dest: paFloat32, in-place conversion to/from float32, possible byte swap*/
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap64ConvertFloat64ToFloat32;
+            #else
+                *converter = ConvertFloat64ToFloat32;
+            #endif
+            break;
+        case ASIOSTInt32MSB:
+            /* dest: paInt32, no conversion necessary, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTInt32LSB:
+            /* dest: paInt32, no conversion necessary, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTInt32MSB16:
+            /* dest: paInt32, 16 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 16;
+            break;
+        case ASIOSTInt32MSB18:
+            /* dest: paInt32, 14 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 14;
+            break;
+        case ASIOSTInt32MSB20:
+            /* dest: paInt32, 12 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 12;
+            break;
+        case ASIOSTInt32MSB24:
+            /* dest: paInt32, 8 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 8;
+            break;
+        case ASIOSTInt32LSB16:
+            /* dest: paInt32, 16 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 16;
+            break;
+        case ASIOSTInt32LSB18:
+            /* dest: paInt32, 14 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 14;
+            break;
+        case ASIOSTInt32LSB20:
+            /* dest: paInt32, 12 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 12;
+            break;
+        case ASIOSTInt32LSB24:
+            /* dest: paInt32, 8 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = SwapShiftLeft32;
+            #else
+                *converter = ShiftLeft32;
+            #endif
+            *shift = 8;
+            break;
+        case ASIOSTInt24MSB:
+            /* dest: paInt24, no conversion necessary, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap24;
+            #endif
+            break;
+        case ASIOSTInt24LSB:
+            /* dest: paInt24, no conversion necessary, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap24;
+            #endif
+            break;
+    }
+}
+
+
+static void SelectPaToAsioConverter( ASIOSampleType type, PaAsioBufferConverter **converter, long *shift )
+{
+    *shift = 0;
+    *converter = 0;
+
+    switch (type) {
+        case ASIOSTInt16MSB:
+            /* src: paInt16, no conversion necessary, possible byte swap*/
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap16;
+            #endif
+            break;
+        case ASIOSTInt16LSB:
+            /* src: paInt16, no conversion necessary, possible byte swap*/
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap16;
+            #endif
+            break;
+        case ASIOSTFloat32MSB:
+            /* src: paFloat32, no conversion necessary, possible byte swap*/
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTFloat32LSB:
+            /* src: paFloat32, no conversion necessary, possible byte swap*/
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTFloat64MSB:
+            /* src: paFloat32, in-place conversion to/from float32, possible byte swap*/
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = ConvertFloat32ToFloat64Swap64;
+            #else
+                *converter = ConvertFloat32ToFloat64;
+            #endif
+            break;
+        case ASIOSTFloat64LSB:
+            /* src: paFloat32, in-place conversion to/from float32, possible byte swap*/
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = ConvertFloat32ToFloat64Swap64;
+            #else
+                *converter = ConvertFloat32ToFloat64;
+            #endif
+            break;
+        case ASIOSTInt32MSB:
+            /* src: paInt32, no conversion necessary, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTInt32LSB:
+            /* src: paInt32, no conversion necessary, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap32;
+            #endif
+            break;
+        case ASIOSTInt32MSB16:
+            /* src: paInt32, 16 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 16;
+            break;
+        case ASIOSTInt32MSB18:
+            /* src: paInt32, 14 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 14;
+            break;
+        case ASIOSTInt32MSB20:
+            /* src: paInt32, 12 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 12;
+            break;
+        case ASIOSTInt32MSB24:
+            /* src: paInt32, 8 bit shift, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 8;
+            break;
+        case ASIOSTInt32LSB16:
+            /* src: paInt32, 16 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 16;
+            break;
+        case ASIOSTInt32LSB18:
+            /* src: paInt32, 14 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 14;
+            break;
+        case ASIOSTInt32LSB20:
+            /* src: paInt32, 12 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 12;
+            break;
+        case ASIOSTInt32LSB24:
+            /* src: paInt32, 8 bit shift, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = ShiftRightSwap32;
+            #else
+                *converter = ShiftRight32;
+            #endif
+            *shift = 8;
+            break;
+        case ASIOSTInt24MSB:
+            /* src: paInt24, no conversion necessary, possible byte swap */
+            #ifdef PA_LSB_IS_NATIVE_
+                *converter = Swap24;
+            #endif
+            break;
+        case ASIOSTInt24LSB:
+            /* src: paInt24, no conversion necessary, possible byte swap */
+            #ifdef PA_MSB_IS_NATIVE_
+                *converter = Swap24;
+            #endif
+            break;
+    }
+}
+
+
+typedef struct PaAsioDeviceInfo
+{
+    PaDeviceInfo commonDeviceInfo;
+    long minBufferSize;
+    long maxBufferSize;
+    long preferredBufferSize;
+    long bufferGranularity;
+
+    ASIOChannelInfo *asioChannelInfos;
+}
+PaAsioDeviceInfo;
+
+
+PaError PaAsio_GetAvailableBufferSizes( PaDeviceIndex device,
+        long *minBufferSizeFrames, long *maxBufferSizeFrames, long *preferredBufferSizeFrames, long *granularity )
+{
+    PaError result;
+    PaUtilHostApiRepresentation *hostApi;
+    PaDeviceIndex hostApiDevice;
+
+    result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO );
+
+    if( result == paNoError )
+    {
+        result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi );
+
+        if( result == paNoError )
+        {
+            PaAsioDeviceInfo *asioDeviceInfo =
+                    (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice];
+
+            *minBufferSizeFrames = asioDeviceInfo->minBufferSize;
+            *maxBufferSizeFrames = asioDeviceInfo->maxBufferSize;
+            *preferredBufferSizeFrames = asioDeviceInfo->preferredBufferSize;
+            *granularity = asioDeviceInfo->bufferGranularity;
+        }
+    }
+
+    return result;
+}
+
+/* Unload whatever we loaded in LoadAsioDriver().
+*/
+static void UnloadAsioDriver( void )
+{
+	ASIOExit();
+}
+
+/*
+    load the asio driver named by <driverName> and return statistics about
+    the driver in info. If no error occurred, the driver will remain open
+    and must be closed by the called by calling UnloadAsioDriver() - if an error
+    is returned the driver will already be unloaded.
+*/
+static PaError LoadAsioDriver( PaAsioHostApiRepresentation *asioHostApi, const char *driverName,
+        PaAsioDriverInfo *driverInfo, void *systemSpecific )
+{
+    PaError result = paNoError;
+    ASIOError asioError;
+    int asioIsInitialized = 0;
+
+    if( !asioHostApi->asioDrivers->loadDriver( const_cast<char*>(driverName) ) )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_HOST_ERROR( 0, "Failed to load ASIO driver" );
+        goto error;
+    }
+
+    memset( &driverInfo->asioDriverInfo, 0, sizeof(ASIODriverInfo) );
+    driverInfo->asioDriverInfo.asioVersion = 2;
+    driverInfo->asioDriverInfo.sysRef = systemSpecific;
+    if( (asioError = ASIOInit( &driverInfo->asioDriverInfo )) != ASE_OK )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        goto error;
+    }
+    else
+    {
+        asioIsInitialized = 1;
+    }
+
+    if( (asioError = ASIOGetChannels(&driverInfo->inputChannelCount,
+            &driverInfo->outputChannelCount)) != ASE_OK )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        goto error;
+    }
+
+    if( (asioError = ASIOGetBufferSize(&driverInfo->bufferMinSize,
+            &driverInfo->bufferMaxSize, &driverInfo->bufferPreferredSize,
+            &driverInfo->bufferGranularity)) != ASE_OK )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        goto error;
+    }
+
+    if( ASIOOutputReady() == ASE_OK )
+        driverInfo->postOutput = true;
+    else
+        driverInfo->postOutput = false;
+
+    return result;
+
+error:
+    if( asioIsInitialized )
+	{
+		ASIOExit();
+	}
+
+    return result;
+}
+
+
+#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_     13   /* must be the same number of elements as in the array below */
+static ASIOSampleRate defaultSampleRateSearchOrder_[]
+     = {44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0,
+        192000.0, 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 };
+
+
+static PaError InitPaDeviceInfoFromAsioDriver( PaAsioHostApiRepresentation *asioHostApi, 
+        const char *driverName, int driverIndex,
+        PaDeviceInfo *deviceInfo, PaAsioDeviceInfo *asioDeviceInfo )
+{
+    PaError result = paNoError;
+
+    /* Due to the headless design of the ASIO API, drivers are free to write over data given to them (like M-Audio
+       drivers f.i.). This is an attempt to overcome that. */
+    union _tag_local {
+        PaAsioDriverInfo info;
+        char _padding[4096];
+    } paAsioDriver;
+
+    asioDeviceInfo->asioChannelInfos = 0; /* we check this below to handle error cleanup */
+
+    result = LoadAsioDriver( asioHostApi, driverName, &paAsioDriver.info, asioHostApi->systemSpecific );
+    if( result == paNoError )
+    {
+        PA_DEBUG(("PaAsio_Initialize: drv:%d name = %s\n",  driverIndex,deviceInfo->name));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d inputChannels       = %d\n", driverIndex, paAsioDriver.info.inputChannelCount));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d outputChannels      = %d\n", driverIndex, paAsioDriver.info.outputChannelCount));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMinSize       = %d\n", driverIndex, paAsioDriver.info.bufferMinSize));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMaxSize       = %d\n", driverIndex, paAsioDriver.info.bufferMaxSize));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d bufferPreferredSize = %d\n", driverIndex, paAsioDriver.info.bufferPreferredSize));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d bufferGranularity   = %d\n", driverIndex, paAsioDriver.info.bufferGranularity));
+
+        deviceInfo->maxInputChannels  = paAsioDriver.info.inputChannelCount;
+        deviceInfo->maxOutputChannels = paAsioDriver.info.outputChannelCount;
+
+        deviceInfo->defaultSampleRate = 0.;
+        bool foundDefaultSampleRate = false;
+        for( int j=0; j < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++j )
+        {
+            ASIOError asioError = ASIOCanSampleRate( defaultSampleRateSearchOrder_[j] );
+            if( asioError != ASE_NoClock && asioError != ASE_NotPresent )
+            {
+                deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[j];
+                foundDefaultSampleRate = true;
+                break;
+            }
+        }
+
+        PA_DEBUG(("PaAsio_Initialize: drv:%d defaultSampleRate = %f\n", driverIndex, deviceInfo->defaultSampleRate));
+
+        if( foundDefaultSampleRate ){
+
+            /* calculate default latency values from bufferPreferredSize
+                for default low latency, and bufferMaxSize
+                for default high latency.
+                use the default sample rate to convert from samples to
+                seconds. Without knowing what sample rate the user will
+                use this is the best we can do.
+            */
+
+            double defaultLowLatency =
+                    paAsioDriver.info.bufferPreferredSize / deviceInfo->defaultSampleRate;
+
+            deviceInfo->defaultLowInputLatency = defaultLowLatency;
+            deviceInfo->defaultLowOutputLatency = defaultLowLatency;
+
+            double defaultHighLatency =
+                    paAsioDriver.info.bufferMaxSize / deviceInfo->defaultSampleRate;
+
+            if( defaultHighLatency < defaultLowLatency )
+                defaultHighLatency = defaultLowLatency; /* just in case the driver returns something strange */ 
+                    
+            deviceInfo->defaultHighInputLatency = defaultHighLatency;
+            deviceInfo->defaultHighOutputLatency = defaultHighLatency;
+            
+        }else{
+
+            deviceInfo->defaultLowInputLatency = 0.;
+            deviceInfo->defaultLowOutputLatency = 0.;
+            deviceInfo->defaultHighInputLatency = 0.;
+            deviceInfo->defaultHighOutputLatency = 0.;
+        }
+
+        PA_DEBUG(("PaAsio_Initialize: drv:%d defaultLowInputLatency = %f\n", driverIndex, deviceInfo->defaultLowInputLatency));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d defaultLowOutputLatency = %f\n", driverIndex, deviceInfo->defaultLowOutputLatency));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d defaultHighInputLatency = %f\n", driverIndex, deviceInfo->defaultHighInputLatency));
+        PA_DEBUG(("PaAsio_Initialize: drv:%d defaultHighOutputLatency = %f\n", driverIndex, deviceInfo->defaultHighOutputLatency));
+
+        asioDeviceInfo->minBufferSize = paAsioDriver.info.bufferMinSize;
+        asioDeviceInfo->maxBufferSize = paAsioDriver.info.bufferMaxSize;
+        asioDeviceInfo->preferredBufferSize = paAsioDriver.info.bufferPreferredSize;
+        asioDeviceInfo->bufferGranularity = paAsioDriver.info.bufferGranularity;
+
+
+        asioDeviceInfo->asioChannelInfos = (ASIOChannelInfo*)PaUtil_GroupAllocateMemory(
+                asioHostApi->allocations,
+                sizeof(ASIOChannelInfo) * (deviceInfo->maxInputChannels
+                        + deviceInfo->maxOutputChannels) );
+        if( !asioDeviceInfo->asioChannelInfos )
+        {
+            result = paInsufficientMemory;
+            goto error_unload;
+        }
+
+        int a;
+
+        for( a=0; a < deviceInfo->maxInputChannels; ++a ){
+            asioDeviceInfo->asioChannelInfos[a].channel = a;
+            asioDeviceInfo->asioChannelInfos[a].isInput = ASIOTrue;
+            ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[a] );
+            if( asioError != ASE_OK )
+            {
+                result = paUnanticipatedHostError;
+                PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+                goto error_unload;
+            }
+        }
+
+        for( a=0; a < deviceInfo->maxOutputChannels; ++a ){
+            int b = deviceInfo->maxInputChannels + a;
+            asioDeviceInfo->asioChannelInfos[b].channel = a;
+            asioDeviceInfo->asioChannelInfos[b].isInput = ASIOFalse;
+            ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[b] );
+            if( asioError != ASE_OK )
+            {
+                result = paUnanticipatedHostError;
+                PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+                goto error_unload;
+            }
+        }
+
+        /* unload the driver */
+        UnloadAsioDriver();
+    }
+
+    return result;
+
+error_unload:
+    UnloadAsioDriver();
+
+    if( asioDeviceInfo->asioChannelInfos ){
+        PaUtil_GroupFreeMemory( asioHostApi->allocations, asioDeviceInfo->asioChannelInfos );
+        asioDeviceInfo->asioChannelInfos = 0;
+    }
+
+    return result;
+}
+
+
+/* we look up IsDebuggerPresent at runtime incase it isn't present (on Win95 for example) */
+typedef BOOL (WINAPI *IsDebuggerPresentPtr)(VOID);
+IsDebuggerPresentPtr IsDebuggerPresent_ = 0;
+//FARPROC IsDebuggerPresent_ = 0; // this is the current way to do it apparently according to davidv
+
+PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
+{
+    PaError result = paNoError;
+    int i, driverCount;
+    PaAsioHostApiRepresentation *asioHostApi;
+    PaAsioDeviceInfo *deviceInfoArray;
+    char **names;
+    asioHostApi = (PaAsioHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaAsioHostApiRepresentation) );
+    if( !asioHostApi )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    memset( asioHostApi, 0, sizeof(PaAsioHostApiRepresentation) ); /* ensure all fields are zeroed. especially asioHostApi->allocations */
+
+    /*
+        We initialize COM ourselves here and uninitialize it in Terminate().
+        This should be the only COM initialization needed in this module.
+
+        The ASIO SDK may also initialize COM but since we want to reduce dependency
+        on the ASIO SDK we manage COM initialization ourselves.
+
+        There used to be code that initialized COM in other situations
+        such as when creating a Stream. This made PA work when calling Pa_CreateStream
+        from a non-main thread. However we currently consider initialization 
+        of COM in non-main threads to be the caller's responsibility.
+    */
+    result = PaWinUtil_CoInitialize( paASIO, &asioHostApi->comInitializationResult );
+    if( result != paNoError )
+    {
+        goto error;
+    }
+
+    asioHostApi->asioDrivers = 0; /* avoid surprises in our error handler below */
+
+    asioHostApi->allocations = PaUtil_CreateAllocationGroup();
+    if( !asioHostApi->allocations )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    /* Allocate the AsioDrivers() driver list (class from ASIO SDK) */
+    try
+    {
+        asioHostApi->asioDrivers = new AsioDrivers(); /* invokes CoInitialize(0) in AsioDriverList::AsioDriverList */
+    } 
+    catch (std::bad_alloc)
+    {
+        asioHostApi->asioDrivers = 0;
+    }
+    /* some implementations of new (ie MSVC, see http://support.microsoft.com/?kbid=167733)
+       don't throw std::bad_alloc, so we also explicitly test for a null return. */
+    if( asioHostApi->asioDrivers == 0 )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    asioDrivers = asioHostApi->asioDrivers; /* keep SDK global in sync until we stop depending on it */
+
+    asioHostApi->systemSpecific = 0;
+    asioHostApi->openAsioDeviceIndex = paNoDevice;
+
+    *hostApi = &asioHostApi->inheritedHostApiRep;
+    (*hostApi)->info.structVersion = 1;
+
+    (*hostApi)->info.type = paASIO;
+    (*hostApi)->info.name = "ASIO";
+    (*hostApi)->info.deviceCount = 0;
+
+    #ifdef WINDOWS
+        /* use desktop window as system specific ptr */
+        asioHostApi->systemSpecific = GetDesktopWindow();
+    #endif
+
+    /* driverCount is the number of installed drivers - not necessarily
+        the number of installed physical devices. */
+    #if MAC
+        driverCount = asioHostApi->asioDrivers->getNumFragments();
+    #elif WINDOWS
+        driverCount = asioHostApi->asioDrivers->asioGetNumDev();
+    #endif
+
+    if( driverCount > 0 )
+    {
+        names = GetAsioDriverNames( asioHostApi, asioHostApi->allocations, driverCount );
+        if( !names )
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+
+        /* allocate enough space for all drivers, even if some aren't installed */
+
+        (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
+                asioHostApi->allocations, sizeof(PaDeviceInfo*) * driverCount );
+        if( !(*hostApi)->deviceInfos )
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+        /* allocate all device info structs in a contiguous block */
+        deviceInfoArray = (PaAsioDeviceInfo*)PaUtil_GroupAllocateMemory(
+                asioHostApi->allocations, sizeof(PaAsioDeviceInfo) * driverCount );
+        if( !deviceInfoArray )
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+        IsDebuggerPresent_ = (IsDebuggerPresentPtr)GetProcAddress( LoadLibraryA( "Kernel32.dll" ), "IsDebuggerPresent" );
+
+        for( i=0; i < driverCount; ++i )
+        {
+            PA_DEBUG(("ASIO names[%d]:%s\n",i,names[i]));
+
+            // Since portaudio opens ALL ASIO drivers, and no one else does that,
+            // we face fact that some drivers were not meant for it, drivers which act
+            // like shells on top of REAL drivers, for instance.
+            // so we get duplicate handles, locks and other problems.
+            // so lets NOT try to load any such wrappers. 
+            // The ones i [davidv] know of so far are:
+
+            if (   strcmp (names[i],"ASIO DirectX Full Duplex Driver") == 0
+                || strcmp (names[i],"ASIO Multimedia Driver")          == 0
+                || strncmp(names[i],"Premiere",8)                      == 0   //"Premiere Elements Windows Sound 1.0"
+                || strncmp(names[i],"Adobe",5)                         == 0   //"Adobe Default Windows Sound 1.5"
+               )
+            {
+                PA_DEBUG(("BLACKLISTED!!!\n"));
+                continue;
+            }
+
+
+            if( IsDebuggerPresent_ && IsDebuggerPresent_() )  
+            {
+                /* ASIO Digidesign Driver uses PACE copy protection which quits out
+                   if a debugger is running. So we don't load it if a debugger is running. */
+                if( strcmp(names[i], "ASIO Digidesign Driver") == 0 )  
+                {
+                    PA_DEBUG(("BLACKLISTED!!! ASIO Digidesign Driver would quit the debugger\n"));  
+                    continue;
+                }  
+            }  
+
+
+            /* Attempt to init device info from the asio driver... */
+            {
+                PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ];
+                PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo;
+
+                deviceInfo->structVersion = 2;
+                deviceInfo->hostApi = hostApiIndex;
+
+                deviceInfo->name = names[i];
+
+                if( InitPaDeviceInfoFromAsioDriver( asioHostApi, names[i], i, deviceInfo, asioDeviceInfo ) == paNoError )
+                {
+                    (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo;
+                    ++(*hostApi)->info.deviceCount;
+                }
+				else
+				{
+                    PA_DEBUG(("Skipping ASIO device:%s\n",names[i]));
+                    continue;
+                }
+            }
+        }
+    }
+
+    if( (*hostApi)->info.deviceCount > 0 )
+    {
+        (*hostApi)->info.defaultInputDevice = 0;
+        (*hostApi)->info.defaultOutputDevice = 0;
+    }
+    else
+    {
+        (*hostApi)->info.defaultInputDevice = paNoDevice;
+        (*hostApi)->info.defaultOutputDevice = paNoDevice;
+    }
+
+
+    (*hostApi)->Terminate = Terminate;
+    (*hostApi)->OpenStream = OpenStream;
+    (*hostApi)->IsFormatSupported = IsFormatSupported;
+
+    PaUtil_InitializeStreamInterface( &asioHostApi->callbackStreamInterface, CloseStream, StartStream,
+                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, GetStreamCpuLoad,
+                                      PaUtil_DummyRead, PaUtil_DummyWrite,
+                                      PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
+
+    PaUtil_InitializeStreamInterface( &asioHostApi->blockingStreamInterface, CloseStream, StartStream,
+                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, PaUtil_DummyGetCpuLoad,
+                                      ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
+
+    return result;
+
+error:
+    if( asioHostApi )
+    {
+        if( asioHostApi->allocations )
+        {
+            PaUtil_FreeAllAllocations( asioHostApi->allocations );
+            PaUtil_DestroyAllocationGroup( asioHostApi->allocations );
+        }
+
+        delete asioHostApi->asioDrivers;
+        asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */
+
+        PaWinUtil_CoUninitialize( paASIO, &asioHostApi->comInitializationResult );
+
+        PaUtil_FreeMemory( asioHostApi );
+    }
+
+    return result;
+}
+
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+    PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi;
+
+    /*
+        IMPLEMENT ME:
+            - clean up any resources not handled by the allocation group (need to review if there are any)
+    */
+
+    if( asioHostApi->allocations )
+    {
+        PaUtil_FreeAllAllocations( asioHostApi->allocations );
+        PaUtil_DestroyAllocationGroup( asioHostApi->allocations );
+    }
+
+    delete asioHostApi->asioDrivers;
+    asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */
+
+    PaWinUtil_CoUninitialize( paASIO, &asioHostApi->comInitializationResult );
+
+    PaUtil_FreeMemory( asioHostApi );
+}
+
+
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate )
+{
+    PaError result = paNoError;
+    PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi;
+    PaAsioDriverInfo *driverInfo = &asioHostApi->openAsioDriverInfo;
+    int inputChannelCount, outputChannelCount;
+    PaSampleFormat inputSampleFormat, outputSampleFormat;
+    PaDeviceIndex asioDeviceIndex;                                  
+    ASIOError asioError;
+    
+    if( inputParameters && outputParameters )
+    {
+        /* full duplex ASIO stream must use the same device for input and output */
+
+        if( inputParameters->device != outputParameters->device )
+            return paBadIODeviceCombination;
+    }
+    
+    if( inputParameters )
+    {
+        inputChannelCount = inputParameters->channelCount;
+        inputSampleFormat = inputParameters->sampleFormat;
+
+        /* all standard sample formats are supported by the buffer adapter,
+            this implementation doesn't support any custom sample formats */
+        if( inputSampleFormat & paCustomFormat )
+            return paSampleFormatNotSupported;
+            
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        asioDeviceIndex = inputParameters->device;
+
+        /* validate inputStreamInfo */
+        /** @todo do more validation here */
+        // if( inputParameters->hostApiSpecificStreamInfo )
+        //    return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+    }
+    else
+    {
+        inputChannelCount = 0;
+    }
+
+    if( outputParameters )
+    {
+        outputChannelCount = outputParameters->channelCount;
+        outputSampleFormat = outputParameters->sampleFormat;
+
+        /* all standard sample formats are supported by the buffer adapter,
+            this implementation doesn't support any custom sample formats */
+        if( outputSampleFormat & paCustomFormat )
+            return paSampleFormatNotSupported;
+            
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        asioDeviceIndex = outputParameters->device;
+
+        /* validate outputStreamInfo */
+        /** @todo do more validation here */
+        // if( outputParameters->hostApiSpecificStreamInfo )
+        //    return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+    }
+    else
+    {
+        outputChannelCount = 0;
+    }
+
+
+
+    /* if an ASIO device is open we can only get format information for the currently open device */
+
+    if( asioHostApi->openAsioDeviceIndex != paNoDevice 
+            && asioHostApi->openAsioDeviceIndex != asioDeviceIndex )
+    {
+        return paDeviceUnavailable;
+    }
+
+
+    /* NOTE: we load the driver and use its current settings
+        rather than the ones in our device info structure which may be stale */
+
+    /* open the device if it's not already open */
+    if( asioHostApi->openAsioDeviceIndex == paNoDevice )
+    {
+        result = LoadAsioDriver( asioHostApi, asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name,
+                driverInfo, asioHostApi->systemSpecific );
+        if( result != paNoError )
+            return result;
+    }
+
+    /* check that input device can support inputChannelCount */
+    if( inputChannelCount > 0 )
+    {
+        if( inputChannelCount > driverInfo->inputChannelCount )
+        {
+            result = paInvalidChannelCount;
+            goto done;
+        }
+    }
+
+    /* check that output device can support outputChannelCount */
+    if( outputChannelCount )
+    {
+        if( outputChannelCount > driverInfo->outputChannelCount )
+        {
+            result = paInvalidChannelCount;
+            goto done;
+        }
+    }
+    
+    /* query for sample rate support */
+    asioError = ASIOCanSampleRate( sampleRate );
+    if( asioError == ASE_NoClock || asioError == ASE_NotPresent )
+    {
+        result = paInvalidSampleRate;
+        goto done;
+    }
+
+done:
+    /* close the device if it wasn't already open */
+    if( asioHostApi->openAsioDeviceIndex == paNoDevice )
+    {
+        UnloadAsioDriver(); /* not sure if we should check for errors here */
+    }
+
+    if( result == paNoError )
+        return paFormatIsSupported;
+    else
+        return result;
+}
+
+
+
+/** A data structure specifically for storing blocking i/o related data. */
+typedef struct PaAsioStreamBlockingState
+{
+    int stopFlag; /**< Flag indicating that block processing is to be stopped. */
+
+    unsigned long writeBuffersRequested; /**< The number of available output buffers, requested by the #WriteStream() function. */
+    unsigned long readFramesRequested;   /**< The number of available input frames, requested by the #ReadStream() function. */
+
+    int writeBuffersRequestedFlag; /**< Flag to indicate that #WriteStream() has requested more output buffers to be available. */
+    int readFramesRequestedFlag;   /**< Flag to indicate that #ReadStream() requires more input frames to be available. */
+
+    HANDLE writeBuffersReadyEvent; /**< Event to signal that requested output buffers are available. */
+    HANDLE readFramesReadyEvent;   /**< Event to signal that requested input frames are available. */
+
+    void *writeRingBufferData; /**< The actual ring buffer memory, used by the output ring buffer. */
+    void *readRingBufferData;  /**< The actual ring buffer memory, used by the input ring buffer. */
+
+    PaUtilRingBuffer writeRingBuffer; /**< Frame-aligned blocking i/o ring buffer to store output data (interleaved user format). */
+    PaUtilRingBuffer readRingBuffer;  /**< Frame-aligned blocking i/o ring buffer to store input data (interleaved user format). */
+
+    long writeRingBufferInitialFrames; /**< The initial number of silent frames within the output ring buffer. */
+
+    const void **writeStreamBuffer; /**< Temp buffer, used by #WriteStream() for handling non-interleaved data. */
+    void **readStreamBuffer; /**< Temp buffer, used by #ReadStream() for handling non-interleaved data. */
+
+    PaUtilBufferProcessor bufferProcessor; /**< Buffer processor, used to handle the blocking i/o ring buffers. */
+
+    int outputUnderflowFlag; /**< Flag to signal an output underflow from within the callback function. */
+    int inputOverflowFlag; /**< Flag to signal an input overflow from within the callback function. */
+}
+PaAsioStreamBlockingState;
+
+
+
+/* PaAsioStream - a stream data structure specifically for this implementation */
+
+typedef struct PaAsioStream
+{
+    PaUtilStreamRepresentation streamRepresentation;
+    PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+    PaUtilBufferProcessor bufferProcessor;
+
+    PaAsioHostApiRepresentation *asioHostApi;
+    unsigned long framesPerHostCallback;
+
+    /* ASIO driver info  - these may not be needed for the life of the stream,
+        but store them here until we work out how format conversion is going
+        to work. */
+
+    ASIOBufferInfo *asioBufferInfos;
+    ASIOChannelInfo *asioChannelInfos;
+    long asioInputLatencyFrames, asioOutputLatencyFrames; // actual latencies returned by asio
+
+    long inputChannelCount, outputChannelCount;
+    bool postOutput;
+
+    void **bufferPtrs; /* this is carved up for inputBufferPtrs and outputBufferPtrs */
+    void **inputBufferPtrs[2];
+    void **outputBufferPtrs[2];
+
+    PaAsioBufferConverter *inputBufferConverter;
+    long inputShift;
+    PaAsioBufferConverter *outputBufferConverter;
+    long outputShift;
+
+    volatile bool stopProcessing;
+    int stopPlayoutCount;
+    HANDLE completedBuffersPlayedEvent;
+
+    bool streamFinishedCallbackCalled;
+    int isStopped;
+    volatile int isActive;
+    volatile bool zeroOutput; /* all future calls to the callback will output silence */
+
+    volatile long reenterCount;
+    volatile long reenterError;
+
+    PaStreamCallbackFlags callbackFlags;
+
+    PaAsioStreamBlockingState *blockingState; /**< Blocking i/o data struct, or NULL when using callback interface. */
+}
+PaAsioStream;
+
+static PaAsioStream *theAsioStream = 0; /* due to ASIO sdk limitations there can be only one stream */
+
+
+static void ZeroOutputBuffers( PaAsioStream *stream, long index )
+{
+    int i;
+
+    for( i=0; i < stream->outputChannelCount; ++i )
+    {
+        void *buffer = stream->asioBufferInfos[ i + stream->inputChannelCount ].buffers[index];
+
+        int bytesPerSample = BytesPerAsioSample( stream->asioChannelInfos[ i + stream->inputChannelCount ].type );
+
+        memset( buffer, 0, stream->framesPerHostCallback * bytesPerSample );
+    }
+}
+
+
+/* return the next power of two >= x. 
+   Returns the input parameter if it is already a power of two. 
+   http://stackoverflow.com/questions/364985/algorithm-for-finding-the-smallest-power-of-two-thats-greater-or-equal-to-a-giv 
+*/
+static unsigned long NextPowerOfTwo( unsigned long x )
+{
+    --x;
+    x |= x >> 1;
+    x |= x >> 2;
+    x |= x >> 4;
+    x |= x >> 8;
+    x |= x >> 16;
+    /* If you needed to deal with numbers > 2^32 the following would be needed. 
+       For latencies, we don't deal with values this large. 
+     x |= x >> 16;
+    */
+
+    return x + 1;
+}
+
+
+static unsigned long SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer( 
+        unsigned long targetBufferingLatencyFrames, PaAsioDriverInfo *driverInfo )
+{
+	/* Choose a host buffer size based only on targetBufferingLatencyFrames and the 
+	   device's supported buffer sizes. Always returns a valid value.
+	*/
+
+	unsigned long result;
+
+	if( targetBufferingLatencyFrames <= (unsigned long)driverInfo->bufferMinSize )
+    {
+        result = driverInfo->bufferMinSize;
+    }
+    else if( targetBufferingLatencyFrames >= (unsigned long)driverInfo->bufferMaxSize )
+    {
+        result = driverInfo->bufferMaxSize;
+    }
+    else
+    {
+		if( driverInfo->bufferGranularity == 0 ) /* single fixed host buffer size */
+        {
+            /* The documentation states that bufferGranularity should be zero 
+               when bufferMinSize, bufferMaxSize and bufferPreferredSize are the 
+               same. We assume that is the case.
+            */
+
+            result = driverInfo->bufferPreferredSize;
+        }
+		else if( driverInfo->bufferGranularity == -1 ) /* power-of-two */
+        {
+		    /* We assume bufferMinSize and bufferMaxSize are powers of two. */
+
+            result = NextPowerOfTwo( targetBufferingLatencyFrames );
+
+            if( result < (unsigned long)driverInfo->bufferMinSize )
+                result = driverInfo->bufferMinSize;
+
+            if( result > (unsigned long)driverInfo->bufferMaxSize )
+                result = driverInfo->bufferMaxSize;
+        }
+        else /* modulo bufferGranularity */
+        {
+            /* round up to the next multiple of granularity */
+            unsigned long n = (targetBufferingLatencyFrames + driverInfo->bufferGranularity - 1) 
+                    / driverInfo->bufferGranularity;
+            
+            result = n * driverInfo->bufferGranularity;
+
+            if( result < (unsigned long)driverInfo->bufferMinSize )
+                result = driverInfo->bufferMinSize;
+
+            if( result > (unsigned long)driverInfo->bufferMaxSize )
+                result = driverInfo->bufferMaxSize;
+        }
+    }
+
+	return result;
+}
+
+
+static unsigned long SelectHostBufferSizeForSpecifiedUserFramesPerBuffer( 
+        unsigned long targetBufferingLatencyFrames, unsigned long userFramesPerBuffer,
+        PaAsioDriverInfo *driverInfo )
+{
+	/* Select a host buffer size conforming to targetBufferingLatencyFrames 
+	   and the device's supported buffer sizes.
+	   The return value will always be a multiple of userFramesPerBuffer. 
+	   If a valid buffer size can not be found the function returns 0.
+
+	   The current implementation uses a simple iterative search for clarity.
+	   Feel free to suggest a closed form solution.
+	*/
+	unsigned long result = 0;
+
+	assert( userFramesPerBuffer != 0 );
+	
+	if( driverInfo->bufferGranularity == 0 ) /* single fixed host buffer size */
+    {
+        /* The documentation states that bufferGranularity should be zero 
+           when bufferMinSize, bufferMaxSize and bufferPreferredSize are the 
+           same. We assume that is the case.
+        */
+
+		if( (driverInfo->bufferPreferredSize % userFramesPerBuffer) == 0 )
+			result = driverInfo->bufferPreferredSize;
+    }
+	else if( driverInfo->bufferGranularity == -1 ) /* power-of-two */
+    {
+		/* We assume bufferMinSize and bufferMaxSize are powers of two. */
+
+        /* Search all powers of two in the range [bufferMinSize,bufferMaxSize] 
+           for multiples of userFramesPerBuffer. We prefer the first multiple
+           that is equal or greater than targetBufferingLatencyFrames, or  
+           failing that, the largest multiple less than 
+           targetBufferingLatencyFrames.
+        */
+        unsigned long x = (unsigned long)driverInfo->bufferMinSize; 
+		do {
+			if( (x % userFramesPerBuffer) == 0 )
+			{
+                /* any multiple of userFramesPerBuffer is acceptable */
+				result = x;
+				if( result >= targetBufferingLatencyFrames )
+					break; /* stop. a value >= to targetBufferingLatencyFrames is ideal. */
+			}
+
+			x *= 2;
+		} while( x <= (unsigned long)driverInfo->bufferMaxSize );
+    }
+    else /* modulo granularity */
+    {
+		/* We assume bufferMinSize is a multiple of bufferGranularity. */
+
+        /* Search all multiples of bufferGranularity in the range 
+           [bufferMinSize,bufferMaxSize] for multiples of userFramesPerBuffer. 
+           We prefer the first multiple that is equal or greater than 
+           targetBufferingLatencyFrames, or failing that, the largest multiple  
+           less than targetBufferingLatencyFrames.
+        */
+		unsigned long x = (unsigned long)driverInfo->bufferMinSize; 
+		do {
+			if( (x % userFramesPerBuffer) == 0 )
+			{
+                /* any multiple of userFramesPerBuffer is acceptable */
+				result = x;
+				if( result >= targetBufferingLatencyFrames )
+					break; /* stop. a value >= to targetBufferingLatencyFrames is ideal. */
+			}
+
+			x += driverInfo->bufferGranularity;
+		} while( x <= (unsigned long)driverInfo->bufferMaxSize );
+    }
+
+	return result;
+}
+
+
+static unsigned long SelectHostBufferSize( 
+        unsigned long targetBufferingLatencyFrames, 
+        unsigned long userFramesPerBuffer, PaAsioDriverInfo *driverInfo )
+{
+    unsigned long result = 0;
+
+    /* We select a host buffer size based on the following requirements 
+       (in priority order):
+
+        1. The host buffer size must be permissible according to the ASIO 
+           driverInfo buffer size constraints (min, max, granularity or 
+           powers-of-two).
+
+        2. If the user specifies a non-zero framesPerBuffer parameter 
+           (userFramesPerBuffer here) the host buffer should be a multiple of 
+           this (subject to the constraints in (1) above).
+
+           [NOTE: Where no permissible host buffer size is a multiple of 
+           userFramesPerBuffer, we choose a value as if userFramesPerBuffer were 
+           zero (i.e. we ignore it). This strategy is open for review ~ perhaps 
+           there are still "more optimal" buffer sizes related to 
+           userFramesPerBuffer that we could use.]
+
+        3. The host buffer size should be greater than or equal to 
+           targetBufferingLatencyFrames, subject to (1) and (2) above. Where it 
+           is not possible to select a host buffer size equal or greater than 
+           targetBufferingLatencyFrames, the highest buffer size conforming to  
+           (1) and (2) should be chosen.
+    */
+
+	if( userFramesPerBuffer != 0 )
+	{
+		/* userFramesPerBuffer is specified, try to find a buffer size that's 
+           a multiple of it */
+		result = SelectHostBufferSizeForSpecifiedUserFramesPerBuffer( 
+                targetBufferingLatencyFrames, userFramesPerBuffer, driverInfo );
+	}
+
+	if( result == 0 )
+	{
+		/* either userFramesPerBuffer was not specified, or we couldn't find a 
+           host buffer size that is a multiple of it. Select a host buffer size 
+           according to targetBufferingLatencyFrames and the ASIO driverInfo 
+           buffer size constraints.
+	     */
+		result = SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer( 
+                targetBufferingLatencyFrames, driverInfo );
+	}
+
+	return result;
+}
+
+
+/* returns channelSelectors if present */
+
+static PaError ValidateAsioSpecificStreamInfo(
+        const PaStreamParameters *streamParameters,
+        const PaAsioStreamInfo *streamInfo,
+        int deviceChannelCount,
+        int **channelSelectors )
+{
+    if( streamInfo )
+    {
+        if( streamInfo->size != sizeof( PaAsioStreamInfo )
+                || streamInfo->version != 1 )
+        {
+            return paIncompatibleHostApiSpecificStreamInfo;
+        }
+
+        if( streamInfo->flags & paAsioUseChannelSelectors )
+            *channelSelectors = streamInfo->channelSelectors;
+
+        if( !(*channelSelectors) )
+            return paIncompatibleHostApiSpecificStreamInfo;
+
+        for( int i=0; i < streamParameters->channelCount; ++i ){
+             if( (*channelSelectors)[i] < 0
+                    || (*channelSelectors)[i] >= deviceChannelCount ){
+                return paInvalidChannelCount;
+             }           
+        }
+    }
+
+    return paNoError;
+}
+
+
+static bool IsUsingExternalClockSource()
+{
+    bool result = false;
+    ASIOError asioError;
+    ASIOClockSource clocks[32];
+    long numSources=32;
+
+    /* davidv: listing ASIO Clock sources. there is an ongoing investigation by
+       me about whether or not to call ASIOSetSampleRate if an external Clock is
+       used. A few drivers expected different things here */
+    
+    asioError = ASIOGetClockSources(clocks, &numSources);
+    if( asioError != ASE_OK ){
+        PA_DEBUG(("ERROR: ASIOGetClockSources: %s\n", PaAsio_GetAsioErrorText(asioError) ));
+    }else{
+        PA_DEBUG(("INFO ASIOGetClockSources listing %d clocks\n", numSources ));
+        for (int i=0;i<numSources;++i){
+            PA_DEBUG(("ASIOClockSource%d %s current:%d\n", i, clocks[i].name, clocks[i].isCurrentSource ));
+           
+            if (clocks[i].isCurrentSource)
+                result = true;
+        }
+    }
+
+    return result;
+}
+
+
+static PaError ValidateAndSetSampleRate( double sampleRate )
+{
+    PaError result = paNoError;
+    ASIOError asioError;
+
+    // check that the device supports the requested sample rate 
+
+    asioError = ASIOCanSampleRate( sampleRate );
+    PA_DEBUG(("ASIOCanSampleRate(%f):%d\n", sampleRate, asioError ));
+
+    if( asioError != ASE_OK )
+    {
+        result = paInvalidSampleRate;
+        PA_DEBUG(("ERROR: ASIOCanSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) ));
+        goto error;
+    }
+
+    // retrieve the current sample rate, we only change to the requested
+    // sample rate if the device is not already in that rate.
+
+    ASIOSampleRate oldRate;
+    asioError = ASIOGetSampleRate(&oldRate);
+    if( asioError != ASE_OK )
+    {
+        result = paInvalidSampleRate;
+        PA_DEBUG(("ERROR: ASIOGetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) ));
+        goto error;
+    }
+    PA_DEBUG(("ASIOGetSampleRate:%f\n",oldRate));
+
+    if (oldRate != sampleRate){
+        /* Set sample rate */
+
+        PA_DEBUG(("before ASIOSetSampleRate(%f)\n",sampleRate));
+
+        /*
+            If you have problems with some drivers when externally clocked, 
+            try switching on the following line and commenting out the one after it.
+            See IsUsingExternalClockSource() for more info.
+        */
+        //if( IsUsingExternalClockSource() ){
+        if( false ){
+            asioError = ASIOSetSampleRate( 0 );
+        }else{
+            asioError = ASIOSetSampleRate( sampleRate );
+        }
+        if( asioError != ASE_OK )
+        {
+            result = paInvalidSampleRate;
+            PA_DEBUG(("ERROR: ASIOSetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) ));
+            goto error;
+        }
+        PA_DEBUG(("after ASIOSetSampleRate(%f)\n",sampleRate));
+    }
+    else
+    {
+        PA_DEBUG(("No Need to change SR\n"));
+    }
+
+error:
+    return result;
+}
+
+
+/* see pa_hostapi.h for a list of validity guarantees made about OpenStream  parameters */
+
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                           PaStream** s,
+                           const PaStreamParameters *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           double sampleRate,
+                           unsigned long framesPerBuffer,
+                           PaStreamFlags streamFlags,
+                           PaStreamCallback *streamCallback,
+                           void *userData )
+{
+    PaError result = paNoError;
+    PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi;
+    PaAsioStream *stream = 0;
+    PaAsioStreamInfo *inputStreamInfo, *outputStreamInfo;
+    unsigned long framesPerHostBuffer;
+    int inputChannelCount, outputChannelCount;
+    PaSampleFormat inputSampleFormat, outputSampleFormat;
+    PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+    unsigned long suggestedInputLatencyFrames;
+    unsigned long suggestedOutputLatencyFrames;
+    PaDeviceIndex asioDeviceIndex;
+    ASIOError asioError;
+    int asioIsInitialized = 0;
+    int asioBuffersCreated = 0;
+    int completedBuffersPlayedEventInited = 0;
+    int i;
+    PaAsioDriverInfo *driverInfo;
+    int *inputChannelSelectors = 0;
+    int *outputChannelSelectors = 0;
+
+    /* Are we using blocking i/o interface? */
+    int usingBlockingIo = ( !streamCallback ) ? TRUE : FALSE;
+    /* Blocking i/o stuff */
+    long lBlockingBufferSize     = 0; /* Desired ring buffer size in samples. */
+    long lBlockingBufferSizePow2 = 0; /* Power-of-2 rounded ring buffer size. */
+    long lBytesPerFrame          = 0; /* Number of bytes per input/output frame. */
+    int blockingWriteBuffersReadyEventInitialized = 0; /* Event init flag. */
+    int blockingReadFramesReadyEventInitialized   = 0; /* Event init flag. */
+
+    int callbackBufferProcessorInited = FALSE;
+    int blockingBufferProcessorInited = FALSE;
+
+    /* unless we move to using lower level ASIO calls, we can only have
+        one device open at a time */
+    if( asioHostApi->openAsioDeviceIndex != paNoDevice )
+    {
+        PA_DEBUG(("OpenStream paDeviceUnavailable\n"));
+        return paDeviceUnavailable;
+    }
+
+    assert( theAsioStream == 0 );
+
+    if( inputParameters && outputParameters )
+    {
+        /* full duplex ASIO stream must use the same device for input and output */
+
+        if( inputParameters->device != outputParameters->device )
+        {
+            PA_DEBUG(("OpenStream paBadIODeviceCombination\n"));
+            return paBadIODeviceCombination;
+        }
+    }
+
+    if( inputParameters )
+    {
+        inputChannelCount = inputParameters->channelCount;
+        inputSampleFormat = inputParameters->sampleFormat;
+        suggestedInputLatencyFrames = (unsigned long)((inputParameters->suggestedLatency * sampleRate)+0.5f);
+
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        asioDeviceIndex = inputParameters->device;
+
+        PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex];
+
+        /* validate hostApiSpecificStreamInfo */
+        inputStreamInfo = (PaAsioStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
+        result = ValidateAsioSpecificStreamInfo( inputParameters, inputStreamInfo,
+            asioDeviceInfo->commonDeviceInfo.maxInputChannels,
+            &inputChannelSelectors
+        );
+        if( result != paNoError ) return result;
+    }
+    else
+    {
+        inputChannelCount = 0;
+        inputSampleFormat = 0;
+        suggestedInputLatencyFrames = 0;
+    }
+
+    if( outputParameters )
+    {
+        outputChannelCount = outputParameters->channelCount;
+        outputSampleFormat = outputParameters->sampleFormat;
+        suggestedOutputLatencyFrames = (unsigned long)((outputParameters->suggestedLatency * sampleRate)+0.5f);
+
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        asioDeviceIndex = outputParameters->device;
+
+        PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex];
+
+        /* validate hostApiSpecificStreamInfo */
+        outputStreamInfo = (PaAsioStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
+        result = ValidateAsioSpecificStreamInfo( outputParameters, outputStreamInfo,
+            asioDeviceInfo->commonDeviceInfo.maxOutputChannels,
+            &outputChannelSelectors
+        );
+        if( result != paNoError ) return result;
+    }
+    else
+    {
+        outputChannelCount = 0;
+        outputSampleFormat = 0;
+        suggestedOutputLatencyFrames = 0;
+    }
+
+    driverInfo = &asioHostApi->openAsioDriverInfo;
+
+    /* NOTE: we load the driver and use its current settings
+        rather than the ones in our device info structure which may be stale */
+
+    result = LoadAsioDriver( asioHostApi, asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name,
+            driverInfo, asioHostApi->systemSpecific );
+    if( result == paNoError )
+        asioIsInitialized = 1;
+    else{
+        PA_DEBUG(("OpenStream ERROR1 - LoadAsioDriver returned %d\n", result));
+        goto error;
+    }
+
+    /* check that input device can support inputChannelCount */
+    if( inputChannelCount > 0 )
+    {
+        if( inputChannelCount > driverInfo->inputChannelCount )
+        {
+            result = paInvalidChannelCount;
+            PA_DEBUG(("OpenStream ERROR2\n"));
+            goto error;
+        }
+    }
+
+    /* check that output device can support outputChannelCount */
+    if( outputChannelCount )
+    {
+        if( outputChannelCount > driverInfo->outputChannelCount )
+        {
+            result = paInvalidChannelCount;
+            PA_DEBUG(("OpenStream ERROR3\n"));
+            goto error;
+        }
+    }
+
+    result = ValidateAndSetSampleRate( sampleRate );
+    if( result != paNoError )
+        goto error;
+
+    /*
+        IMPLEMENT ME:
+            - if a full duplex stream is requested, check that the combination
+                of input and output parameters is supported
+    */
+
+    /* validate platform specific flags */
+    if( (streamFlags & paPlatformSpecificFlags) != 0 ){
+        PA_DEBUG(("OpenStream invalid flags!!\n"));
+        return paInvalidFlag; /* unexpected platform specific flag */
+    }
+
+
+    stream = (PaAsioStream*)PaUtil_AllocateMemory( sizeof(PaAsioStream) );
+    if( !stream )
+    {
+        result = paInsufficientMemory;
+        PA_DEBUG(("OpenStream ERROR5\n"));
+        goto error;
+    }
+    stream->blockingState = NULL; /* Blocking i/o not initialized, yet. */
+
+
+    stream->completedBuffersPlayedEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
+    if( stream->completedBuffersPlayedEvent == NULL )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+        PA_DEBUG(("OpenStream ERROR6\n"));
+        goto error;
+    }
+    completedBuffersPlayedEventInited = 1;
+
+
+    stream->asioBufferInfos = 0; /* for deallocation in error */
+    stream->asioChannelInfos = 0; /* for deallocation in error */
+    stream->bufferPtrs = 0; /* for deallocation in error */
+
+    /* Using blocking i/o interface... */
+    if( usingBlockingIo )
+    {
+        /* Blocking i/o is implemented by running callback mode, using a special blocking i/o callback. */
+        streamCallback = BlockingIoPaCallback; /* Setup PA to use the ASIO blocking i/o callback. */
+        userData       = &theAsioStream;       /* The callback user data will be the PA ASIO stream. */
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &asioHostApi->blockingStreamInterface, streamCallback, userData );
+    }
+    else /* Using callback interface... */
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &asioHostApi->callbackStreamInterface, streamCallback, userData );
+    }
+
+
+    PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+
+
+    stream->asioBufferInfos = (ASIOBufferInfo*)PaUtil_AllocateMemory(
+            sizeof(ASIOBufferInfo) * (inputChannelCount + outputChannelCount) );
+    if( !stream->asioBufferInfos )
+    {
+        result = paInsufficientMemory;
+        PA_DEBUG(("OpenStream ERROR7\n"));
+        goto error;
+    }
+
+
+    for( i=0; i < inputChannelCount; ++i )
+    {
+        ASIOBufferInfo *info = &stream->asioBufferInfos[i];
+
+        info->isInput = ASIOTrue;
+
+        if( inputChannelSelectors ){
+            // inputChannelSelectors values have already been validated in
+            // ValidateAsioSpecificStreamInfo() above
+            info->channelNum = inputChannelSelectors[i];
+        }else{
+            info->channelNum = i;
+        }
+
+        info->buffers[0] = info->buffers[1] = 0;
+    }
+
+    for( i=0; i < outputChannelCount; ++i ){
+        ASIOBufferInfo *info = &stream->asioBufferInfos[inputChannelCount+i];
+
+        info->isInput = ASIOFalse;
+
+        if( outputChannelSelectors ){
+            // outputChannelSelectors values have already been validated in
+            // ValidateAsioSpecificStreamInfo() above
+            info->channelNum = outputChannelSelectors[i];
+        }else{
+            info->channelNum = i;
+        }
+        
+        info->buffers[0] = info->buffers[1] = 0;
+    }
+
+
+    /* Using blocking i/o interface... */
+    if( usingBlockingIo )
+    {
+/** @todo REVIEW selection of host buffer size for blocking i/o */
+
+        framesPerHostBuffer = SelectHostBufferSize( 0, framesPerBuffer, driverInfo );
+
+    }
+    else /* Using callback interface... */
+    {
+        /* Select the host buffer size based on user framesPerBuffer and the
+           maximum of suggestedInputLatencyFrames and 
+           suggestedOutputLatencyFrames.
+
+           We should subtract any fixed known driver latency from 
+           suggestedLatencyFrames before computing the host buffer size.
+           However, the ASIO API doesn't provide a method for determining fixed 
+           latencies independent of the host buffer size. ASIOGetLatencies()  
+           only returns latencies after the buffer size has been configured, so 
+           we can't reliably use it to determine fixed latencies here.
+
+           We could set the preferred buffer size and then subtract it from
+           the values returned from ASIOGetLatencies, but this would not be 100%
+           reliable, so we don't do it.
+        */
+
+        unsigned long targetBufferingLatencyFrames = 
+                (( suggestedInputLatencyFrames > suggestedOutputLatencyFrames )
+                ? suggestedInputLatencyFrames 
+                : suggestedOutputLatencyFrames);
+
+        framesPerHostBuffer = SelectHostBufferSize( targetBufferingLatencyFrames, 
+                framesPerBuffer, driverInfo );
+    }
+
+
+    PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n",  framesPerHostBuffer));
+
+    asioError = ASIOCreateBuffers( stream->asioBufferInfos,
+            inputChannelCount+outputChannelCount,
+            framesPerHostBuffer, &asioCallbacks_ );
+
+    if( asioError != ASE_OK
+            && framesPerHostBuffer != (unsigned long)driverInfo->bufferPreferredSize )
+    {
+        PA_DEBUG(("ERROR: ASIOCreateBuffers: %s\n", PaAsio_GetAsioErrorText(asioError) ));
+        /*
+            Some buggy drivers (like the Hoontech DSP24) give incorrect
+            [min, preferred, max] values They should work with the preferred size
+            value, thus if Pa_ASIO_CreateBuffers fails with the hostBufferSize
+            computed in SelectHostBufferSize, we try again with the preferred size.
+        */
+
+        framesPerHostBuffer = driverInfo->bufferPreferredSize;
+
+        PA_DEBUG(("PaAsioOpenStream: CORRECTED framesPerHostBuffer :%d\n",  framesPerHostBuffer));
+
+        ASIOError asioError2 = ASIOCreateBuffers( stream->asioBufferInfos,
+                inputChannelCount+outputChannelCount,
+                 framesPerHostBuffer, &asioCallbacks_ );
+        if( asioError2 == ASE_OK )
+            asioError = ASE_OK;
+    }
+
+    if( asioError != ASE_OK )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        PA_DEBUG(("OpenStream ERROR9\n"));
+        goto error;
+    }
+
+    asioBuffersCreated = 1;
+
+    stream->asioChannelInfos = (ASIOChannelInfo*)PaUtil_AllocateMemory(
+            sizeof(ASIOChannelInfo) * (inputChannelCount + outputChannelCount) );
+    if( !stream->asioChannelInfos )
+    {
+        result = paInsufficientMemory;
+        PA_DEBUG(("OpenStream ERROR10\n"));
+        goto error;
+    }
+
+    for( i=0; i < inputChannelCount + outputChannelCount; ++i )
+    {
+        stream->asioChannelInfos[i].channel = stream->asioBufferInfos[i].channelNum;
+        stream->asioChannelInfos[i].isInput = stream->asioBufferInfos[i].isInput;
+        asioError = ASIOGetChannelInfo( &stream->asioChannelInfos[i] );
+        if( asioError != ASE_OK )
+        {
+            result = paUnanticipatedHostError;
+            PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+            PA_DEBUG(("OpenStream ERROR11\n"));
+            goto error;
+        }
+    }
+
+    stream->bufferPtrs = (void**)PaUtil_AllocateMemory(
+            2 * sizeof(void*) * (inputChannelCount + outputChannelCount) );
+    if( !stream->bufferPtrs )
+    {
+        result = paInsufficientMemory;
+        PA_DEBUG(("OpenStream ERROR12\n"));
+        goto error;
+    }
+
+    if( inputChannelCount > 0 )
+    {
+        stream->inputBufferPtrs[0] = stream-> bufferPtrs;
+        stream->inputBufferPtrs[1] = &stream->bufferPtrs[inputChannelCount];
+
+        for( i=0; i<inputChannelCount; ++i )
+        {
+            stream->inputBufferPtrs[0][i] = stream->asioBufferInfos[i].buffers[0];
+            stream->inputBufferPtrs[1][i] = stream->asioBufferInfos[i].buffers[1];
+        }
+    }
+    else
+    {
+        stream->inputBufferPtrs[0] = 0;
+        stream->inputBufferPtrs[1] = 0;
+    }
+
+    if( outputChannelCount > 0 )
+    {
+        stream->outputBufferPtrs[0] = &stream->bufferPtrs[inputChannelCount*2];
+        stream->outputBufferPtrs[1] = &stream->bufferPtrs[inputChannelCount*2 + outputChannelCount];
+
+        for( i=0; i<outputChannelCount; ++i )
+        {
+            stream->outputBufferPtrs[0][i] = stream->asioBufferInfos[inputChannelCount+i].buffers[0];
+            stream->outputBufferPtrs[1][i] = stream->asioBufferInfos[inputChannelCount+i].buffers[1];
+        }
+    }
+    else
+    {
+        stream->outputBufferPtrs[0] = 0;
+        stream->outputBufferPtrs[1] = 0;
+    }
+
+    if( inputChannelCount > 0 )
+    {
+        /* FIXME: assume all channels use the same type for now 
+        
+            see: "ASIO devices with multiple sample formats are unsupported"
+            http://www.portaudio.com/trac/ticket/106
+        */
+        ASIOSampleType inputType = stream->asioChannelInfos[0].type;
+
+        PA_DEBUG(("ASIO Input  type:%d",inputType));
+        AsioSampleTypeLOG(inputType);
+        hostInputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( inputType );
+
+        SelectAsioToPaConverter( inputType, &stream->inputBufferConverter, &stream->inputShift );
+    }
+    else
+    {
+        hostInputSampleFormat = 0;
+        stream->inputBufferConverter = 0;
+    }
+
+    if( outputChannelCount > 0 )
+    {
+        /* FIXME: assume all channels use the same type for now 
+        
+            see: "ASIO devices with multiple sample formats are unsupported"
+            http://www.portaudio.com/trac/ticket/106
+        */
+        ASIOSampleType outputType = stream->asioChannelInfos[inputChannelCount].type;
+
+        PA_DEBUG(("ASIO Output type:%d",outputType));
+        AsioSampleTypeLOG(outputType);
+        hostOutputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( outputType );
+
+        SelectPaToAsioConverter( outputType, &stream->outputBufferConverter, &stream->outputShift );
+    }
+    else
+    {
+        hostOutputSampleFormat = 0;
+        stream->outputBufferConverter = 0;
+    }
+
+    /* Values returned by ASIOGetLatencies() include the latency introduced by 
+       the ASIO double buffer. */
+    ASIOGetLatencies( &stream->asioInputLatencyFrames, &stream->asioOutputLatencyFrames );
+
+
+    /* Using blocking i/o interface... */
+    if( usingBlockingIo )
+    {
+        /* Allocate the blocking i/o input ring buffer memory. */
+        stream->blockingState = (PaAsioStreamBlockingState*)PaUtil_AllocateMemory( sizeof(PaAsioStreamBlockingState) );
+        if( !stream->blockingState )
+        {
+            result = paInsufficientMemory;
+            PA_DEBUG(("ERROR! Blocking i/o interface struct allocation failed in OpenStream()\n"));
+            goto error;
+        }
+
+        /* Initialize blocking i/o interface struct. */
+        stream->blockingState->readFramesReadyEvent   = NULL; /* Uninitialized, yet. */
+        stream->blockingState->writeBuffersReadyEvent = NULL; /* Uninitialized, yet. */
+        stream->blockingState->readRingBufferData     = NULL; /* Uninitialized, yet. */
+        stream->blockingState->writeRingBufferData    = NULL; /* Uninitialized, yet. */
+        stream->blockingState->readStreamBuffer       = NULL; /* Uninitialized, yet. */
+        stream->blockingState->writeStreamBuffer      = NULL; /* Uninitialized, yet. */
+        stream->blockingState->stopFlag               = TRUE; /* Not started, yet. */
+
+
+        /* If the user buffer is unspecified */
+        if( framesPerBuffer == paFramesPerBufferUnspecified )
+        {
+            /* Make the user buffer the same size as the host buffer. */
+            framesPerBuffer = framesPerHostBuffer;
+        }
+
+
+        /* Initialize callback buffer processor. */
+        result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor               ,
+                                                    inputChannelCount                     ,
+                                                    inputSampleFormat & ~paNonInterleaved , /* Ring buffer. */
+                                                    (hostInputSampleFormat | paNonInterleaved), /* Host format. */
+                                                    outputChannelCount                    ,
+                                                    outputSampleFormat & ~paNonInterleaved, /* Ring buffer. */
+                                                    (hostOutputSampleFormat | paNonInterleaved), /* Host format. */
+                                                    sampleRate                            ,
+                                                    streamFlags                           ,
+                                                    framesPerBuffer                       , /* Frames per ring buffer block. */
+                                                    framesPerHostBuffer                   , /* Frames per asio buffer. */
+                                                    paUtilFixedHostBufferSize             ,
+                                                    streamCallback                        ,
+                                                    userData                               );
+        if( result != paNoError ){
+            PA_DEBUG(("OpenStream ERROR13\n"));
+            goto error;
+        }
+        callbackBufferProcessorInited = TRUE;
+
+        /* Initialize the blocking i/o buffer processor. */
+        result = PaUtil_InitializeBufferProcessor(&stream->blockingState->bufferProcessor,
+                                                   inputChannelCount                     ,
+                                                   inputSampleFormat                     , /* User format. */
+                                                   inputSampleFormat & ~paNonInterleaved , /* Ring buffer. */
+                                                   outputChannelCount                    ,
+                                                   outputSampleFormat                    , /* User format. */
+                                                   outputSampleFormat & ~paNonInterleaved, /* Ring buffer. */
+                                                   sampleRate                            ,
+                                                   paClipOff | paDitherOff               , /* Don't use dither nor clipping. */
+                                                   framesPerBuffer                       , /* Frames per user buffer. */
+                                                   framesPerBuffer                       , /* Frames per ring buffer block. */
+                                                   paUtilBoundedHostBufferSize           ,
+                                                   NULL, NULL                            );/* No callback! */
+        if( result != paNoError ){
+            PA_DEBUG(("ERROR! Blocking i/o buffer processor initialization failed in OpenStream()\n"));
+            goto error;
+        }
+        blockingBufferProcessorInited = TRUE;
+
+        /* If input is requested. */
+        if( inputChannelCount )
+        {
+            /* Create the callback sync-event. */
+            stream->blockingState->readFramesReadyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
+            if( stream->blockingState->readFramesReadyEvent == NULL )
+            {
+                result = paUnanticipatedHostError;
+                PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+                PA_DEBUG(("ERROR! Blocking i/o \"read frames ready\" event creation failed in OpenStream()\n"));
+                goto error;
+            }
+            blockingReadFramesReadyEventInitialized = 1;
+
+
+            /* Create pointer buffer to access non-interleaved data in ReadStream() */
+            stream->blockingState->readStreamBuffer = (void**)PaUtil_AllocateMemory( sizeof(void*) * inputChannelCount );
+            if( !stream->blockingState->readStreamBuffer )
+            {
+                result = paInsufficientMemory;
+                PA_DEBUG(("ERROR! Blocking i/o read stream buffer allocation failed in OpenStream()\n"));
+                goto error;
+            }
+
+            /* The ring buffer should store as many data blocks as needed
+               to achieve the requested latency. Whereas it must be large
+               enough to store at least two complete data blocks.
+
+               1) Determine the amount of latency to be added to the
+                  prefered ASIO latency.
+               2) Make sure we have at lest one additional latency frame.
+               3) Divide the number of frames by the desired block size to
+                  get the number (rounded up to pure integer) of blocks to
+                  be stored in the buffer.
+               4) Add one additional block for block processing and convert
+                  to samples frames.
+               5) Get the next larger (or equal) power-of-two buffer size.
+             */
+            lBlockingBufferSize = suggestedInputLatencyFrames - stream->asioInputLatencyFrames;
+            lBlockingBufferSize = (lBlockingBufferSize > 0) ? lBlockingBufferSize : 1;
+            lBlockingBufferSize = (lBlockingBufferSize + framesPerBuffer - 1) / framesPerBuffer;
+            lBlockingBufferSize = (lBlockingBufferSize + 1) * framesPerBuffer;
+
+            /* Get the next larger or equal power-of-two buffersize. */
+            lBlockingBufferSizePow2 = 1;
+            while( lBlockingBufferSize > (lBlockingBufferSizePow2<<=1) );
+            lBlockingBufferSize = lBlockingBufferSizePow2;
+
+            /* Compute total intput latency in seconds */
+            stream->streamRepresentation.streamInfo.inputLatency =
+                (double)( PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor               )
+                        + PaUtil_GetBufferProcessorInputLatencyFrames(&stream->blockingState->bufferProcessor)
+                        + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer
+                        + stream->asioInputLatencyFrames )
+                / sampleRate;
+
+            /* The code below prints the ASIO latency which doesn't include
+               the buffer processor latency nor the blocking i/o latency. It
+               reports the added latency separately.
+            */
+            PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms),\n         added buffProc:%ld (%ld ms),\n         added blocking:%ld (%ld ms)\n",
+                stream->asioInputLatencyFrames,
+                (long)( stream->asioInputLatencyFrames * (1000.0 / sampleRate) ),
+                PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor),
+                (long)( PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor) * (1000.0 / sampleRate) ),
+                PaUtil_GetBufferProcessorInputLatencyFrames(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer,
+                (long)( (PaUtil_GetBufferProcessorInputLatencyFrames(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer) * (1000.0 / sampleRate) )
+                ));
+
+            /* Determine the size of ring buffer in bytes. */
+            lBytesPerFrame = inputChannelCount * Pa_GetSampleSize(inputSampleFormat );
+
+            /* Allocate the blocking i/o input ring buffer memory. */
+            stream->blockingState->readRingBufferData = (void*)PaUtil_AllocateMemory( lBlockingBufferSize * lBytesPerFrame );
+            if( !stream->blockingState->readRingBufferData )
+            {
+                result = paInsufficientMemory;
+                PA_DEBUG(("ERROR! Blocking i/o input ring buffer allocation failed in OpenStream()\n"));
+                goto error;
+            }
+
+            /* Initialize the input ring buffer struct. */
+            PaUtil_InitializeRingBuffer( &stream->blockingState->readRingBuffer    ,
+                                          lBytesPerFrame                           ,
+                                          lBlockingBufferSize                      ,
+                                          stream->blockingState->readRingBufferData );
+        }
+
+        /* If output is requested. */
+        if( outputChannelCount )
+        {
+            stream->blockingState->writeBuffersReadyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
+            if( stream->blockingState->writeBuffersReadyEvent == NULL )
+            {
+                result = paUnanticipatedHostError;
+                PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+                PA_DEBUG(("ERROR! Blocking i/o \"write buffers ready\" event creation failed in OpenStream()\n"));
+                goto error;
+            }
+            blockingWriteBuffersReadyEventInitialized = 1;
+
+            /* Create pointer buffer to access non-interleaved data in WriteStream() */
+            stream->blockingState->writeStreamBuffer = (const void**)PaUtil_AllocateMemory( sizeof(const void*) * outputChannelCount );
+            if( !stream->blockingState->writeStreamBuffer )
+            {
+                result = paInsufficientMemory;
+                PA_DEBUG(("ERROR! Blocking i/o write stream buffer allocation failed in OpenStream()\n"));
+                goto error;
+            }
+
+            /* The ring buffer should store as many data blocks as needed
+               to achieve the requested latency. Whereas it must be large
+               enough to store at least two complete data blocks.
+
+               1) Determine the amount of latency to be added to the
+                  prefered ASIO latency.
+               2) Make sure we have at lest one additional latency frame.
+               3) Divide the number of frames by the desired block size to
+                  get the number (rounded up to pure integer) of blocks to
+                  be stored in the buffer.
+               4) Add one additional block for block processing and convert
+                  to samples frames.
+               5) Get the next larger (or equal) power-of-two buffer size.
+             */
+            lBlockingBufferSize = suggestedOutputLatencyFrames - stream->asioOutputLatencyFrames;
+            lBlockingBufferSize = (lBlockingBufferSize > 0) ? lBlockingBufferSize : 1;
+            lBlockingBufferSize = (lBlockingBufferSize + framesPerBuffer - 1) / framesPerBuffer;
+            lBlockingBufferSize = (lBlockingBufferSize + 1) * framesPerBuffer;
+
+            /* The buffer size (without the additional block) corresponds
+               to the initial number of silent samples in the output ring
+               buffer. */
+            stream->blockingState->writeRingBufferInitialFrames = lBlockingBufferSize - framesPerBuffer;
+
+            /* Get the next larger or equal power-of-two buffersize. */
+            lBlockingBufferSizePow2 = 1;
+            while( lBlockingBufferSize > (lBlockingBufferSizePow2<<=1) );
+            lBlockingBufferSize = lBlockingBufferSizePow2;
+
+            /* Compute total output latency in seconds */
+            stream->streamRepresentation.streamInfo.outputLatency =
+                (double)( PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
+                        + PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->blockingState->bufferProcessor)
+                        + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer
+                        + stream->asioOutputLatencyFrames )
+                / sampleRate;
+
+            /* The code below prints the ASIO latency which doesn't include
+               the buffer processor latency nor the blocking i/o latency. It
+               reports the added latency separately.
+            */
+            PA_DEBUG(("PaAsio : ASIO OutputLatency = %ld (%ld ms),\n         added buffProc:%ld (%ld ms),\n         added blocking:%ld (%ld ms)\n",
+                stream->asioOutputLatencyFrames,
+                (long)( stream->asioOutputLatencyFrames * (1000.0 / sampleRate) ),
+                PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor),
+                (long)( PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor) * (1000.0 / sampleRate) ),
+                PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer,
+                (long)( (PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer) * (1000.0 / sampleRate) )
+                ));
+
+            /* Determine the size of ring buffer in bytes. */
+            lBytesPerFrame = outputChannelCount * Pa_GetSampleSize(outputSampleFormat);
+
+            /* Allocate the blocking i/o output ring buffer memory. */
+            stream->blockingState->writeRingBufferData = (void*)PaUtil_AllocateMemory( lBlockingBufferSize * lBytesPerFrame );
+            if( !stream->blockingState->writeRingBufferData )
+            {
+                result = paInsufficientMemory;
+                PA_DEBUG(("ERROR! Blocking i/o output ring buffer allocation failed in OpenStream()\n"));
+                goto error;
+            }
+
+            /* Initialize the output ring buffer struct. */
+            PaUtil_InitializeRingBuffer( &stream->blockingState->writeRingBuffer    ,
+                                          lBytesPerFrame                            ,
+                                          lBlockingBufferSize                       ,
+                                          stream->blockingState->writeRingBufferData );
+        }
+
+        stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+
+    }
+    else /* Using callback interface... */
+    {
+        result =  PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+                        inputChannelCount, inputSampleFormat, (hostInputSampleFormat | paNonInterleaved),
+                        outputChannelCount, outputSampleFormat, (hostOutputSampleFormat | paNonInterleaved),
+                        sampleRate, streamFlags, framesPerBuffer,
+                        framesPerHostBuffer, paUtilFixedHostBufferSize,
+                        streamCallback, userData );
+        if( result != paNoError ){
+            PA_DEBUG(("OpenStream ERROR13\n"));
+            goto error;
+        }
+        callbackBufferProcessorInited = TRUE;
+
+        stream->streamRepresentation.streamInfo.inputLatency =
+                (double)( PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)
+                    + stream->asioInputLatencyFrames) / sampleRate;   // seconds
+        stream->streamRepresentation.streamInfo.outputLatency =
+                (double)( PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
+                    + stream->asioOutputLatencyFrames) / sampleRate; // seconds
+        stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+        // the code below prints the ASIO latency which doesn't include the
+        // buffer processor latency. it reports the added latency separately
+        PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n",
+                stream->asioInputLatencyFrames,
+                (long)((stream->asioInputLatencyFrames*1000)/ sampleRate),  
+                PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor),
+                (long)((PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)*1000)/ sampleRate)
+                ));
+
+        PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n",
+                stream->asioOutputLatencyFrames,
+                (long)((stream->asioOutputLatencyFrames*1000)/ sampleRate), 
+                PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor),
+                (long)((PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)*1000)/ sampleRate)
+                ));
+    }
+
+    stream->asioHostApi = asioHostApi;
+    stream->framesPerHostCallback = framesPerHostBuffer;
+
+    stream->inputChannelCount = inputChannelCount;
+    stream->outputChannelCount = outputChannelCount;
+    stream->postOutput = driverInfo->postOutput;
+    stream->isStopped = 1;
+    stream->isActive = 0;
+    
+    asioHostApi->openAsioDeviceIndex = asioDeviceIndex;
+
+    theAsioStream = stream;
+    *s = (PaStream*)stream;
+
+    return result;
+
+error:
+    PA_DEBUG(("goto errored\n"));
+    if( stream )
+    {
+        if( stream->blockingState )
+        {
+            if( blockingBufferProcessorInited )
+                PaUtil_TerminateBufferProcessor( &stream->blockingState->bufferProcessor );
+
+            if( stream->blockingState->writeRingBufferData )
+                PaUtil_FreeMemory( stream->blockingState->writeRingBufferData );
+            if( stream->blockingState->writeStreamBuffer )
+                PaUtil_FreeMemory( stream->blockingState->writeStreamBuffer );
+            if( blockingWriteBuffersReadyEventInitialized )
+                CloseHandle( stream->blockingState->writeBuffersReadyEvent );
+
+            if( stream->blockingState->readRingBufferData )
+                PaUtil_FreeMemory( stream->blockingState->readRingBufferData );
+            if( stream->blockingState->readStreamBuffer )
+                PaUtil_FreeMemory( stream->blockingState->readStreamBuffer );
+            if( blockingReadFramesReadyEventInitialized )
+                CloseHandle( stream->blockingState->readFramesReadyEvent );
+
+            PaUtil_FreeMemory( stream->blockingState );
+        }
+
+        if( callbackBufferProcessorInited )
+            PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+
+        if( completedBuffersPlayedEventInited )
+            CloseHandle( stream->completedBuffersPlayedEvent );
+
+        if( stream->asioBufferInfos )
+            PaUtil_FreeMemory( stream->asioBufferInfos );
+
+        if( stream->asioChannelInfos )
+            PaUtil_FreeMemory( stream->asioChannelInfos );
+
+        if( stream->bufferPtrs )
+            PaUtil_FreeMemory( stream->bufferPtrs );
+
+        PaUtil_FreeMemory( stream );
+    }
+
+    if( asioBuffersCreated )
+        ASIODisposeBuffers();
+
+    if( asioIsInitialized )
+	{
+		UnloadAsioDriver();
+	}
+    return result;
+}
+
+
+/*
+    When CloseStream() is called, the multi-api layer ensures that
+    the stream has already been stopped or aborted.
+*/
+static PaError CloseStream( PaStream* s )
+{
+    PaError result = paNoError;
+    PaAsioStream *stream = (PaAsioStream*)s;
+
+    /*
+        IMPLEMENT ME:
+            - additional stream closing + cleanup
+    */
+
+    PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+    PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+
+    stream->asioHostApi->openAsioDeviceIndex = paNoDevice;
+
+    CloseHandle( stream->completedBuffersPlayedEvent );
+
+    /* Using blocking i/o interface... */
+    if( stream->blockingState )
+    {
+        PaUtil_TerminateBufferProcessor( &stream->blockingState->bufferProcessor );
+
+        if( stream->inputChannelCount ) {
+            PaUtil_FreeMemory( stream->blockingState->readRingBufferData );
+            PaUtil_FreeMemory( stream->blockingState->readStreamBuffer  );
+            CloseHandle( stream->blockingState->readFramesReadyEvent );
+        }
+        if( stream->outputChannelCount ) {
+            PaUtil_FreeMemory( stream->blockingState->writeRingBufferData );
+            PaUtil_FreeMemory( stream->blockingState->writeStreamBuffer );
+            CloseHandle( stream->blockingState->writeBuffersReadyEvent );
+        }
+
+        PaUtil_FreeMemory( stream->blockingState );
+    }
+
+    PaUtil_FreeMemory( stream->asioBufferInfos );
+    PaUtil_FreeMemory( stream->asioChannelInfos );
+    PaUtil_FreeMemory( stream->bufferPtrs );
+    PaUtil_FreeMemory( stream );
+
+    ASIODisposeBuffers();
+    UnloadAsioDriver();
+
+    theAsioStream = 0;
+
+    return result;
+}
+
+
+static void bufferSwitch(long index, ASIOBool directProcess)
+{
+//TAKEN FROM THE ASIO SDK
+
+    // the actual processing callback.
+    // Beware that this is normally in a seperate thread, hence be sure that
+    // you take care about thread synchronization. This is omitted here for
+    // simplicity.
+
+    // as this is a "back door" into the bufferSwitchTimeInfo a timeInfo needs
+    // to be created though it will only set the timeInfo.samplePosition and
+    // timeInfo.systemTime fields and the according flags
+
+    ASIOTime  timeInfo;
+    memset( &timeInfo, 0, sizeof (timeInfo) );
+
+    // get the time stamp of the buffer, not necessary if no
+    // synchronization to other media is required
+    if( ASIOGetSamplePosition(&timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime) == ASE_OK)
+            timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid;
+
+    // Call the real callback
+    bufferSwitchTimeInfo( &timeInfo, index, directProcess );
+}
+
+
+// conversion from 64 bit ASIOSample/ASIOTimeStamp to double float
+#if NATIVE_INT64
+    #define ASIO64toDouble(a)  (a)
+#else
+    const double twoRaisedTo32 = 4294967296.;
+    #define ASIO64toDouble(a)  ((a).lo + (a).hi * twoRaisedTo32)
+#endif
+
+static ASIOTime *bufferSwitchTimeInfo( ASIOTime *timeInfo, long index, ASIOBool directProcess )
+{
+    // the actual processing callback.
+    // Beware that this is normally in a seperate thread, hence be sure that
+    // you take care about thread synchronization.
+
+
+    /* The SDK says the following about the directProcess flag:
+        suggests to the host whether it should immediately start processing
+        (directProcess == ASIOTrue), or whether its process should be deferred
+        because the call comes from a very low level (for instance, a high level
+        priority interrupt), and direct processing would cause timing instabilities for
+        the rest of the system. If in doubt, directProcess should be set to ASIOFalse.
+
+        We just ignore directProcess. This could cause incompatibilities with
+        drivers which really don't want the audio processing to occur in this
+        callback, but none have been identified yet.
+    */
+
+    (void) directProcess; /* suppress unused parameter warning */
+
+#if 0
+    // store the timeInfo for later use
+    asioDriverInfo.tInfo = *timeInfo;
+
+    // get the time stamp of the buffer, not necessary if no
+    // synchronization to other media is required
+
+    if (timeInfo->timeInfo.flags & kSystemTimeValid)
+            asioDriverInfo.nanoSeconds = ASIO64toDouble(timeInfo->timeInfo.systemTime);
+    else
+            asioDriverInfo.nanoSeconds = 0;
+
+    if (timeInfo->timeInfo.flags & kSamplePositionValid)
+            asioDriverInfo.samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition);
+    else
+            asioDriverInfo.samples = 0;
+
+    if (timeInfo->timeCode.flags & kTcValid)
+            asioDriverInfo.tcSamples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples);
+    else
+            asioDriverInfo.tcSamples = 0;
+
+    // get the system reference time
+    asioDriverInfo.sysRefTime = get_sys_reference_time();
+#endif
+
+#if 0
+    // a few debug messages for the Windows device driver developer
+    // tells you the time when driver got its interrupt and the delay until the app receives
+    // the event notification.
+    static double last_samples = 0;
+    char tmp[128];
+    sprintf (tmp, "diff: %d / %d ms / %d ms / %d samples                 \n", asioDriverInfo.sysRefTime - (long)(asioDriverInfo.nanoSeconds / 1000000.0), asioDriverInfo.sysRefTime, (long)(asioDriverInfo.nanoSeconds / 1000000.0), (long)(asioDriverInfo.samples - last_samples));
+    OutputDebugString (tmp);
+    last_samples = asioDriverInfo.samples;
+#endif
+
+
+    if( !theAsioStream )
+        return 0L;
+
+    // protect against reentrancy
+    if( PaAsio_AtomicIncrement(&theAsioStream->reenterCount) )
+    {
+        theAsioStream->reenterError++;
+        //DBUG(("bufferSwitchTimeInfo : reentrancy detection = %d\n", asioDriverInfo.reenterError));
+        return 0L;
+    }
+
+    int buffersDone = 0;
+    
+    do
+    {
+        if( buffersDone > 0 )
+        {
+            // this is a reentered buffer, we missed processing it on time
+            // set the input overflow and output underflow flags as appropriate
+            
+            if( theAsioStream->inputChannelCount > 0 )
+                theAsioStream->callbackFlags |= paInputOverflow;
+                
+            if( theAsioStream->outputChannelCount > 0 )
+                theAsioStream->callbackFlags |= paOutputUnderflow;
+        }
+        else
+        {
+            if( theAsioStream->zeroOutput )
+            {
+                ZeroOutputBuffers( theAsioStream, index );
+
+                // Finally if the driver supports the ASIOOutputReady() optimization,
+                // do it here, all data are in place
+                if( theAsioStream->postOutput )
+                    ASIOOutputReady();
+
+                if( theAsioStream->stopProcessing )
+                {
+                    if( theAsioStream->stopPlayoutCount < 2 )
+                    {
+                        ++theAsioStream->stopPlayoutCount;
+                        if( theAsioStream->stopPlayoutCount == 2 )
+                        {
+                            theAsioStream->isActive = 0;
+                            if( theAsioStream->streamRepresentation.streamFinishedCallback != 0 )
+                                theAsioStream->streamRepresentation.streamFinishedCallback( theAsioStream->streamRepresentation.userData );
+                            theAsioStream->streamFinishedCallbackCalled = true;
+                            SetEvent( theAsioStream->completedBuffersPlayedEvent );
+                        }
+                    }
+                }
+            }
+            else
+            {
+
+#if 0
+/*
+    see: "ASIO callback underflow/overflow buffer slip detection doesn't work"
+    http://www.portaudio.com/trac/ticket/110
+*/
+
+// test code to try to detect slip conditions... these may work on some systems
+// but neither of them work on the RME Digi96
+
+// check that sample delta matches buffer size (otherwise we must have skipped
+// a buffer.
+static double last_samples = -512;
+double samples;
+//if( timeInfo->timeCode.flags & kTcValid )
+//    samples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples);
+//else
+    samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition);
+int delta = samples - last_samples;
+//printf( "%d\n", delta);
+last_samples = samples;
+
+if( delta > theAsioStream->framesPerHostCallback )
+{
+    if( theAsioStream->inputChannelCount > 0 )
+        theAsioStream->callbackFlags |= paInputOverflow;
+
+    if( theAsioStream->outputChannelCount > 0 )
+        theAsioStream->callbackFlags |= paOutputUnderflow;
+}
+
+// check that the buffer index is not the previous index (which would indicate
+// that a buffer was skipped.
+static int previousIndex = 1;
+if( index == previousIndex )
+{
+    if( theAsioStream->inputChannelCount > 0 )
+        theAsioStream->callbackFlags |= paInputOverflow;
+
+    if( theAsioStream->outputChannelCount > 0 )
+        theAsioStream->callbackFlags |= paOutputUnderflow;
+}
+previousIndex = index;
+#endif
+
+                int i;
+
+                PaUtil_BeginCpuLoadMeasurement( &theAsioStream->cpuLoadMeasurer );
+
+                PaStreamCallbackTimeInfo paTimeInfo;
+
+                // asio systemTime is supposed to be measured according to the same
+                // clock as timeGetTime
+                paTimeInfo.currentTime = (ASIO64toDouble( timeInfo->timeInfo.systemTime ) * .000000001);
+
+                /* patch from Paul Boege */
+                paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime -
+                    ((double)theAsioStream->asioInputLatencyFrames/theAsioStream->streamRepresentation.streamInfo.sampleRate);
+
+                paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime +
+                    ((double)theAsioStream->asioOutputLatencyFrames/theAsioStream->streamRepresentation.streamInfo.sampleRate);
+
+                /* old version is buggy because the buffer processor also adds in its latency to the time parameters
+                paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - theAsioStream->streamRepresentation.streamInfo.inputLatency;
+                paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + theAsioStream->streamRepresentation.streamInfo.outputLatency;
+                */
+
+/* Disabled! Stopping and re-starting the stream causes an input overflow / output underflow. S.Fischer */
+#if 0
+// detect underflows by checking inter-callback time > 2 buffer period
+static double previousTime = -1;
+if( previousTime > 0 ){
+
+    double delta = paTimeInfo.currentTime - previousTime;
+
+    if( delta >= 2. * (theAsioStream->framesPerHostCallback / theAsioStream->streamRepresentation.streamInfo.sampleRate) ){
+        if( theAsioStream->inputChannelCount > 0 )
+            theAsioStream->callbackFlags |= paInputOverflow;
+
+        if( theAsioStream->outputChannelCount > 0 )
+            theAsioStream->callbackFlags |= paOutputUnderflow;
+    }
+}
+previousTime = paTimeInfo.currentTime;
+#endif
+
+                // note that the above input and output times do not need to be
+                // adjusted for the latency of the buffer processor -- the buffer
+                // processor handles that.
+
+                if( theAsioStream->inputBufferConverter )
+                {
+                    for( i=0; i<theAsioStream->inputChannelCount; i++ )
+                    {
+                        theAsioStream->inputBufferConverter( theAsioStream->inputBufferPtrs[index][i],
+                                theAsioStream->inputShift, theAsioStream->framesPerHostCallback );
+                    }
+                }
+
+                PaUtil_BeginBufferProcessing( &theAsioStream->bufferProcessor, &paTimeInfo, theAsioStream->callbackFlags );
+
+                /* reset status flags once they've been passed to the callback */
+                theAsioStream->callbackFlags = 0;
+
+                PaUtil_SetInputFrameCount( &theAsioStream->bufferProcessor, 0 /* default to host buffer size */ );
+                for( i=0; i<theAsioStream->inputChannelCount; ++i )
+                    PaUtil_SetNonInterleavedInputChannel( &theAsioStream->bufferProcessor, i, theAsioStream->inputBufferPtrs[index][i] );
+
+                PaUtil_SetOutputFrameCount( &theAsioStream->bufferProcessor, 0 /* default to host buffer size */ );
+                for( i=0; i<theAsioStream->outputChannelCount; ++i )
+                    PaUtil_SetNonInterleavedOutputChannel( &theAsioStream->bufferProcessor, i, theAsioStream->outputBufferPtrs[index][i] );
+
+                int callbackResult;
+                if( theAsioStream->stopProcessing )
+                    callbackResult = paComplete;
+                else
+                    callbackResult = paContinue;
+                unsigned long framesProcessed = PaUtil_EndBufferProcessing( &theAsioStream->bufferProcessor, &callbackResult );
+
+                if( theAsioStream->outputBufferConverter )
+                {
+                    for( i=0; i<theAsioStream->outputChannelCount; i++ )
+                    {
+                        theAsioStream->outputBufferConverter( theAsioStream->outputBufferPtrs[index][i],
+                                theAsioStream->outputShift, theAsioStream->framesPerHostCallback );
+                    }
+                }
+
+                PaUtil_EndCpuLoadMeasurement( &theAsioStream->cpuLoadMeasurer, framesProcessed );
+
+                // Finally if the driver supports the ASIOOutputReady() optimization,
+                // do it here, all data are in place
+                if( theAsioStream->postOutput )
+                    ASIOOutputReady();
+
+                if( callbackResult == paContinue )
+                {
+                    /* nothing special to do */
+                }
+                else if( callbackResult == paAbort )
+                {
+                    /* finish playback immediately  */
+                    theAsioStream->isActive = 0;
+                    if( theAsioStream->streamRepresentation.streamFinishedCallback != 0 )
+                        theAsioStream->streamRepresentation.streamFinishedCallback( theAsioStream->streamRepresentation.userData );
+                    theAsioStream->streamFinishedCallbackCalled = true;
+                    SetEvent( theAsioStream->completedBuffersPlayedEvent );
+                    theAsioStream->zeroOutput = true;
+                }
+                else /* paComplete or other non-zero value indicating complete */
+                {
+                    /* Finish playback once currently queued audio has completed. */
+                    theAsioStream->stopProcessing = true;
+
+                    if( PaUtil_IsBufferProcessorOutputEmpty( &theAsioStream->bufferProcessor ) )
+                    {
+                        theAsioStream->zeroOutput = true;
+                        theAsioStream->stopPlayoutCount = 0;
+                    }
+                }
+            }
+        }
+        
+        ++buffersDone;
+    }while( PaAsio_AtomicDecrement(&theAsioStream->reenterCount) >= 0 );
+
+    return 0L;
+}
+
+
+static void sampleRateChanged(ASIOSampleRate sRate)
+{
+    // TAKEN FROM THE ASIO SDK
+    // do whatever you need to do if the sample rate changed
+    // usually this only happens during external sync.
+    // Audio processing is not stopped by the driver, actual sample rate
+    // might not have even changed, maybe only the sample rate status of an
+    // AES/EBU or S/PDIF digital input at the audio device.
+    // You might have to update time/sample related conversion routines, etc.
+
+    (void) sRate; /* unused parameter */
+    PA_DEBUG( ("sampleRateChanged : %d \n", sRate));
+}
+
+static long asioMessages(long selector, long value, void* message, double* opt)
+{
+// TAKEN FROM THE ASIO SDK
+    // currently the parameters "value", "message" and "opt" are not used.
+    long ret = 0;
+
+    (void) message; /* unused parameters */
+    (void) opt;
+
+    PA_DEBUG( ("asioMessages : %d , %d \n", selector, value));
+
+    switch(selector)
+    {
+        case kAsioSelectorSupported:
+            if(value == kAsioResetRequest
+            || value == kAsioEngineVersion
+            || value == kAsioResyncRequest
+            || value == kAsioLatenciesChanged
+            // the following three were added for ASIO 2.0, you don't necessarily have to support them
+            || value == kAsioSupportsTimeInfo
+            || value == kAsioSupportsTimeCode
+            || value == kAsioSupportsInputMonitor)
+                    ret = 1L;
+            break;
+
+        case kAsioBufferSizeChange:
+            //printf("kAsioBufferSizeChange \n");
+            break;
+
+        case kAsioResetRequest:
+            // defer the task and perform the reset of the driver during the next "safe" situation
+            // You cannot reset the driver right now, as this code is called from the driver.
+            // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction
+            // Afterwards you initialize the driver again.
+
+            /*FIXME: commented the next line out
+
+                see: "PA/ASIO ignores some driver notifications it probably shouldn't"
+                http://www.portaudio.com/trac/ticket/108
+            */
+            //asioDriverInfo.stopped;  // In this sample the processing will just stop
+            ret = 1L;
+            break;
+
+        case kAsioResyncRequest:
+            // This informs the application, that the driver encountered some non fatal data loss.
+            // It is used for synchronization purposes of different media.
+            // Added mainly to work around the Win16Mutex problems in Windows 95/98 with the
+            // Windows Multimedia system, which could loose data because the Mutex was hold too long
+            // by another thread.
+            // However a driver can issue it in other situations, too.
+            ret = 1L;
+            break;
+
+        case kAsioLatenciesChanged:
+            // This will inform the host application that the drivers were latencies changed.
+            // Beware, it this does not mean that the buffer sizes have changed!
+            // You might need to update internal delay data.
+            ret = 1L;
+            //printf("kAsioLatenciesChanged \n");
+            break;
+
+        case kAsioEngineVersion:
+            // return the supported ASIO version of the host application
+            // If a host applications does not implement this selector, ASIO 1.0 is assumed
+            // by the driver
+            ret = 2L;
+            break;
+
+        case kAsioSupportsTimeInfo:
+            // informs the driver wether the asioCallbacks.bufferSwitchTimeInfo() callback
+            // is supported.
+            // For compatibility with ASIO 1.0 drivers the host application should always support
+            // the "old" bufferSwitch method, too.
+            ret = 1;
+            break;
+
+        case kAsioSupportsTimeCode:
+            // informs the driver wether application is interested in time code info.
+            // If an application does not need to know about time code, the driver has less work
+            // to do.
+            ret = 0;
+            break;
+    }
+    return ret;
+}
+
+
+static PaError StartStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaAsioStream *stream = (PaAsioStream*)s;
+    PaAsioStreamBlockingState *blockingState = stream->blockingState;
+    ASIOError asioError;
+
+    if( stream->outputChannelCount > 0 )
+    {
+        ZeroOutputBuffers( stream, 0 );
+        ZeroOutputBuffers( stream, 1 );
+    }
+
+    PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
+    stream->stopProcessing = false;
+    stream->zeroOutput = false;
+
+    /* Reentrancy counter initialisation */
+    stream->reenterCount = -1;
+    stream->reenterError = 0;
+
+    stream->callbackFlags = 0;
+
+    if( ResetEvent( stream->completedBuffersPlayedEvent ) == 0 )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+    }
+
+
+    /* Using blocking i/o interface... */
+    if( blockingState )
+    {
+        /* Reset blocking i/o buffer processor. */
+        PaUtil_ResetBufferProcessor( &blockingState->bufferProcessor );
+
+        /* If we're about to process some input data. */
+        if( stream->inputChannelCount )
+        {
+            /* Reset callback-ReadStream sync event. */
+            if( ResetEvent( blockingState->readFramesReadyEvent ) == 0 )
+            {
+                result = paUnanticipatedHostError;
+                PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+            }
+
+            /* Flush blocking i/o ring buffer. */
+            PaUtil_FlushRingBuffer( &blockingState->readRingBuffer );
+            (*blockingState->bufferProcessor.inputZeroer)( blockingState->readRingBuffer.buffer, 1, blockingState->bufferProcessor.inputChannelCount * blockingState->readRingBuffer.bufferSize );
+        }
+
+        /* If we're about to process some output data. */
+        if( stream->outputChannelCount )
+        {
+            /* Reset callback-WriteStream sync event. */
+            if( ResetEvent( blockingState->writeBuffersReadyEvent ) == 0 )
+            {
+                result = paUnanticipatedHostError;
+                PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+            }
+
+            /* Flush blocking i/o ring buffer. */
+            PaUtil_FlushRingBuffer( &blockingState->writeRingBuffer );
+            (*blockingState->bufferProcessor.outputZeroer)( blockingState->writeRingBuffer.buffer, 1, blockingState->bufferProcessor.outputChannelCount * blockingState->writeRingBuffer.bufferSize );
+
+            /* Initialize the output ring buffer to "silence". */
+            PaUtil_AdvanceRingBufferWriteIndex( &blockingState->writeRingBuffer, blockingState->writeRingBufferInitialFrames );
+        }
+
+        /* Clear requested frames / buffers count. */
+        blockingState->writeBuffersRequested     = 0;
+        blockingState->readFramesRequested       = 0;
+        blockingState->writeBuffersRequestedFlag = FALSE;
+        blockingState->readFramesRequestedFlag   = FALSE;
+        blockingState->outputUnderflowFlag       = FALSE;
+        blockingState->inputOverflowFlag         = FALSE;
+        blockingState->stopFlag                  = FALSE;
+    }
+
+
+    if( result == paNoError )
+    {
+        assert( theAsioStream == stream ); /* theAsioStream should be set correctly in OpenStream */
+
+        /* initialize these variables before the callback has a chance to be invoked */
+        stream->isStopped = 0;
+        stream->isActive = 1;
+        stream->streamFinishedCallbackCalled = false;
+
+        asioError = ASIOStart();
+        if( asioError != ASE_OK )
+        {
+            stream->isStopped = 1;
+            stream->isActive = 0;
+
+            result = paUnanticipatedHostError;
+            PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        }
+    }
+
+    return result;
+}
+
+static void EnsureCallbackHasCompleted( PaAsioStream *stream )
+{
+    // make sure that the callback is not still in-flight after ASIOStop()
+    // returns. This has been observed to happen on the Hoontech DSP24 for
+    // example.
+    int count = 2000;  // only wait for 2 seconds, rather than hanging.
+    while( stream->reenterCount != -1 && count > 0 )
+    {
+        Sleep(1);
+        --count;
+    }
+}
+
+static PaError StopStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaAsioStream *stream = (PaAsioStream*)s;
+    PaAsioStreamBlockingState *blockingState = stream->blockingState;
+    ASIOError asioError;
+
+    if( stream->isActive )
+    {
+        /* If blocking i/o output is in use */
+        if( blockingState && stream->outputChannelCount )
+        {
+            /* Request the whole output buffer to be available. */
+            blockingState->writeBuffersRequested = blockingState->writeRingBuffer.bufferSize;
+            /* Signalize that additional buffers are need. */
+            blockingState->writeBuffersRequestedFlag = TRUE;
+            /* Set flag to indicate the playback is to be stopped. */
+            blockingState->stopFlag = TRUE;
+
+            /* Wait until requested number of buffers has been freed. Time
+               out after twice the blocking i/o ouput buffer could have
+               been consumed. */
+            DWORD timeout = (DWORD)( 2 * blockingState->writeRingBuffer.bufferSize * 1000
+                                       / stream->streamRepresentation.streamInfo.sampleRate );
+            DWORD waitResult = WaitForSingleObject( blockingState->writeBuffersReadyEvent, timeout );
+
+            /* If something seriously went wrong... */
+            if( waitResult == WAIT_FAILED )
+            {
+                PA_DEBUG(("WaitForSingleObject() failed in StopStream()\n"));
+                result = paUnanticipatedHostError;
+                PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+            }
+            else if( waitResult == WAIT_TIMEOUT )
+            {
+                PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n"));
+                result = paTimedOut;
+            }
+        }
+
+        stream->stopProcessing = true;
+
+        /* wait for the stream to finish playing out enqueued buffers.
+            timeout after four times the stream latency.
+
+            @todo should use a better time out value - if the user buffer
+            length is longer than the asio buffer size then that should
+            be taken into account.
+        */
+        if( WaitForSingleObject( stream->completedBuffersPlayedEvent,
+                (DWORD)(stream->streamRepresentation.streamInfo.outputLatency * 1000. * 4.) )
+                    == WAIT_TIMEOUT )
+        {
+            PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n" ));
+        }
+    }
+
+    asioError = ASIOStop();
+    if( asioError == ASE_OK )
+    {
+        EnsureCallbackHasCompleted( stream );
+    }
+    else
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+    }
+
+    stream->isStopped = 1;
+    stream->isActive = 0;
+
+    if( !stream->streamFinishedCallbackCalled )
+    {
+        if( stream->streamRepresentation.streamFinishedCallback != 0 )
+            stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+    }
+
+    return result;
+}
+
+static PaError AbortStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaAsioStream *stream = (PaAsioStream*)s;
+    ASIOError asioError;
+
+    stream->zeroOutput = true;
+
+    asioError = ASIOStop();
+    if( asioError == ASE_OK )
+    {
+        EnsureCallbackHasCompleted( stream );
+    }
+    else
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+    }
+
+    stream->isStopped = 1;
+    stream->isActive = 0;
+
+    if( !stream->streamFinishedCallbackCalled )
+    {
+        if( stream->streamRepresentation.streamFinishedCallback != 0 )
+            stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+    }
+
+    return result;
+}
+
+
+static PaError IsStreamStopped( PaStream *s )
+{
+    PaAsioStream *stream = (PaAsioStream*)s;
+    
+    return stream->isStopped;
+}
+
+
+static PaError IsStreamActive( PaStream *s )
+{
+    PaAsioStream *stream = (PaAsioStream*)s;
+
+    return stream->isActive;
+}
+
+
+static PaTime GetStreamTime( PaStream *s )
+{
+    (void) s; /* unused parameter */
+
+    return (double)timeGetTime() * .001;
+}
+
+
+static double GetStreamCpuLoad( PaStream* s )
+{
+    PaAsioStream *stream = (PaAsioStream*)s;
+
+    return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
+}
+
+
+/*
+    As separate stream interfaces are used for blocking and callback
+    streams, the following functions can be guaranteed to only be called
+    for blocking streams.
+*/
+
+static PaError ReadStream( PaStream      *s     ,
+                           void          *buffer,
+                           unsigned long  frames )
+{
+    PaError result = paNoError; /* Initial return value. */
+    PaAsioStream *stream = (PaAsioStream*)s; /* The PA ASIO stream. */
+
+    /* Pointer to the blocking i/o data struct. */
+    PaAsioStreamBlockingState *blockingState = stream->blockingState;
+
+    /* Get blocking i/o buffer processor and ring buffer pointers. */
+    PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor;
+    PaUtilRingBuffer      *pRb = &blockingState->readRingBuffer;
+
+    /* Ring buffer segment(s) used for writing. */
+    void *pRingBufferData1st = NULL; /* First segment. (Mandatory) */
+    void *pRingBufferData2nd = NULL; /* Second segment. (Optional) */
+
+    /* Number of frames per ring buffer segment. */
+    long lRingBufferSize1st = 0; /* First segment. (Mandatory) */
+    long lRingBufferSize2nd = 0; /* Second segment. (Optional) */
+
+    /* Get number of frames to be processed per data block. */
+    unsigned long lFramesPerBlock = stream->bufferProcessor.framesPerUserBuffer;
+    /* Actual number of frames that has been copied into the ring buffer. */
+    unsigned long lFramesCopied = 0;
+    /* The number of remaining unprocessed dtat frames. */
+    unsigned long lFramesRemaining = frames;
+
+    /* Copy the input argument to avoid pointer increment! */
+    const void *userBuffer;
+    unsigned int i; /* Just a counter. */
+
+    /* About the time, needed to process 8 data blocks. */
+    DWORD timeout = (DWORD)( 8 * lFramesPerBlock * 1000 / stream->streamRepresentation.streamInfo.sampleRate );
+    DWORD waitResult = 0;
+
+
+    /* Check if the stream is still available ready to gather new data. */
+    if( blockingState->stopFlag || !stream->isActive )
+    {
+        PA_DEBUG(("Warning! Stream no longer available for reading in ReadStream()\n"));
+        result = paStreamIsStopped;
+        return result;
+    }
+
+    /* If the stream is a input stream. */
+    if( stream->inputChannelCount )
+    {
+        /* Prepare buffer access. */
+        if( !pBp->userOutputIsInterleaved )
+        {
+            userBuffer = blockingState->readStreamBuffer;
+            for( i = 0; i<pBp->inputChannelCount; ++i )
+            {
+                ((void**)userBuffer)[i] = ((void**)buffer)[i];
+            }
+        } /* Use the unchanged buffer. */
+        else { userBuffer = buffer; }
+
+        do /* Internal block processing for too large user data buffers. */
+        {
+            /* Get the size of the current data block to be processed. */
+            lFramesPerBlock =(lFramesPerBlock < lFramesRemaining)
+                            ? lFramesPerBlock : lFramesRemaining;
+            /* Use predefined block size for as long there are enough
+               buffers available, thereafter reduce the processing block
+               size to match the number of remaining buffers. So the final
+               data block is processed although it may be incomplete. */
+
+            /* If the available amount of data frames is insufficient. */
+            if( PaUtil_GetRingBufferReadAvailable(pRb) < (long) lFramesPerBlock )
+            {
+                /* Make sure, the event isn't already set! */
+                /* ResetEvent( blockingState->readFramesReadyEvent ); */
+
+                /* Set the number of requested buffers. */
+                blockingState->readFramesRequested = lFramesPerBlock;
+
+                /* Signalize that additional buffers are need. */
+                blockingState->readFramesRequestedFlag = TRUE;
+
+                /* Wait until requested number of buffers has been freed. */
+                waitResult = WaitForSingleObject( blockingState->readFramesReadyEvent, timeout );
+
+                /* If something seriously went wrong... */
+                if( waitResult == WAIT_FAILED )
+                {
+                    PA_DEBUG(("WaitForSingleObject() failed in ReadStream()\n"));
+                    result = paUnanticipatedHostError;
+                    PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+                    return result;
+                }
+                else if( waitResult == WAIT_TIMEOUT )
+                {
+                    PA_DEBUG(("WaitForSingleObject() timed out in ReadStream()\n"));
+
+                    /* If block processing has stopped, abort! */
+                    if( blockingState->stopFlag ) { return result = paStreamIsStopped; }
+
+                    /* If a timeout is encountered, give up eventually. */
+                    return result = paTimedOut;
+                }
+            }
+            /* Now, the ring buffer contains the required amount of data
+               frames.
+               (Therefor we don't need to check the return argument of
+               PaUtil_GetRingBufferReadRegions(). ;-) )
+            */
+
+            /* Retrieve pointer(s) to the ring buffer's current write
+               position(s). If the first buffer segment is too small to
+               store the requested number of bytes, an additional second
+               segment is returned. Otherwise, i.e. if the first segment
+               is large enough, the second segment's pointer will be NULL.
+            */
+            PaUtil_GetRingBufferReadRegions(pRb                ,
+                                            lFramesPerBlock    ,
+                                            &pRingBufferData1st,
+                                            &lRingBufferSize1st,
+                                            &pRingBufferData2nd,
+                                            &lRingBufferSize2nd);
+
+            /* Set number of frames to be copied from the ring buffer. */
+            PaUtil_SetInputFrameCount( pBp, lRingBufferSize1st ); 
+            /* Setup ring buffer access. */
+            PaUtil_SetInterleavedInputChannels(pBp               ,  /* Buffer processor. */
+                                               0                 ,  /* The first channel's index. */
+                                               pRingBufferData1st,  /* First ring buffer segment. */
+                                               0                 ); /* Use all available channels. */
+
+            /* If a second ring buffer segment is required. */
+            if( lRingBufferSize2nd ) {
+                /* Set number of frames to be copied from the ring buffer. */
+                PaUtil_Set2ndInputFrameCount( pBp, lRingBufferSize2nd );
+                /* Setup ring buffer access. */
+                PaUtil_Set2ndInterleavedInputChannels(pBp               ,  /* Buffer processor. */
+                                                      0                 ,  /* The first channel's index. */
+                                                      pRingBufferData2nd,  /* Second ring buffer segment. */
+                                                      0                 ); /* Use all available channels. */
+            }
+
+            /* Let the buffer processor handle "copy and conversion" and
+               update the ring buffer indices manually. */
+            lFramesCopied = PaUtil_CopyInput( pBp, &buffer, lFramesPerBlock );
+            PaUtil_AdvanceRingBufferReadIndex( pRb, lFramesCopied );
+
+            /* Decrease number of unprocessed frames. */
+            lFramesRemaining -= lFramesCopied;
+
+        } /* Continue with the next data chunk. */
+        while( lFramesRemaining );
+
+
+        /* If there has been an input overflow within the callback */
+        if( blockingState->inputOverflowFlag )
+        {
+            blockingState->inputOverflowFlag = FALSE;
+
+            /* Return the corresponding error code. */
+            result = paInputOverflowed;
+        }
+
+    } /* If this is not an input stream. */
+    else {
+        result = paCanNotReadFromAnOutputOnlyStream;
+    }
+
+    return result;
+}
+
+static PaError WriteStream( PaStream      *s     ,
+                            const void    *buffer,
+                            unsigned long  frames )
+{
+    PaError result = paNoError; /* Initial return value. */
+    PaAsioStream *stream = (PaAsioStream*)s; /* The PA ASIO stream. */
+
+    /* Pointer to the blocking i/o data struct. */
+    PaAsioStreamBlockingState *blockingState = stream->blockingState;
+
+    /* Get blocking i/o buffer processor and ring buffer pointers. */
+    PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor;
+    PaUtilRingBuffer      *pRb = &blockingState->writeRingBuffer;
+
+    /* Ring buffer segment(s) used for writing. */
+    void *pRingBufferData1st = NULL; /* First segment. (Mandatory) */
+    void *pRingBufferData2nd = NULL; /* Second segment. (Optional) */
+
+    /* Number of frames per ring buffer segment. */
+    long lRingBufferSize1st = 0; /* First segment. (Mandatory) */
+    long lRingBufferSize2nd = 0; /* Second segment. (Optional) */
+
+    /* Get number of frames to be processed per data block. */
+    unsigned long lFramesPerBlock = stream->bufferProcessor.framesPerUserBuffer;
+    /* Actual number of frames that has been copied into the ring buffer. */
+    unsigned long lFramesCopied = 0;
+    /* The number of remaining unprocessed dtat frames. */
+    unsigned long lFramesRemaining = frames;
+
+    /* About the time, needed to process 8 data blocks. */
+    DWORD timeout = (DWORD)( 8 * lFramesPerBlock * 1000 / stream->streamRepresentation.streamInfo.sampleRate );
+    DWORD waitResult = 0;
+
+    /* Copy the input argument to avoid pointer increment! */
+    const void *userBuffer;
+    unsigned int i; /* Just a counter. */
+
+
+    /* Check if the stream ist still available ready to recieve new data. */
+    if( blockingState->stopFlag || !stream->isActive )
+    {
+        PA_DEBUG(("Warning! Stream no longer available for writing in WriteStream()\n"));
+        result = paStreamIsStopped;
+        return result;
+    }
+
+    /* If the stream is a output stream. */
+    if( stream->outputChannelCount )
+    {
+        /* Prepare buffer access. */
+        if( !pBp->userOutputIsInterleaved )
+        {
+            userBuffer = blockingState->writeStreamBuffer;
+            for( i = 0; i<pBp->outputChannelCount; ++i )
+            {
+                ((const void**)userBuffer)[i] = ((const void**)buffer)[i];
+            }
+        } /* Use the unchanged buffer. */
+        else { userBuffer = buffer; }
+
+
+        do /* Internal block processing for too large user data buffers. */
+        {
+            /* Get the size of the current data block to be processed. */
+            lFramesPerBlock =(lFramesPerBlock < lFramesRemaining)
+                            ? lFramesPerBlock : lFramesRemaining;
+            /* Use predefined block size for as long there are enough
+               frames available, thereafter reduce the processing block
+               size to match the number of remaining frames. So the final
+               data block is processed although it may be incomplete. */
+
+            /* If the available amount of buffers is insufficient. */
+            if( PaUtil_GetRingBufferWriteAvailable(pRb) < (long) lFramesPerBlock )
+            {
+                /* Make sure, the event isn't already set! */
+                /* ResetEvent( blockingState->writeBuffersReadyEvent ); */
+
+                /* Set the number of requested buffers. */
+                blockingState->writeBuffersRequested = lFramesPerBlock;
+
+                /* Signalize that additional buffers are need. */
+                blockingState->writeBuffersRequestedFlag = TRUE;
+
+                /* Wait until requested number of buffers has been freed. */
+                waitResult = WaitForSingleObject( blockingState->writeBuffersReadyEvent, timeout );
+
+                /* If something seriously went wrong... */
+                if( waitResult == WAIT_FAILED )
+                {
+                    PA_DEBUG(("WaitForSingleObject() failed in WriteStream()\n"));
+                    result = paUnanticipatedHostError;
+                    PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() );
+                    return result;
+                }
+                else if( waitResult == WAIT_TIMEOUT )
+                {
+                    PA_DEBUG(("WaitForSingleObject() timed out in WriteStream()\n"));
+
+                    /* If block processing has stopped, abort! */
+                    if( blockingState->stopFlag ) { return result = paStreamIsStopped; }
+                    
+                    /* If a timeout is encountered, give up eventually. */
+                    return result = paTimedOut;
+                }
+            }
+            /* Now, the ring buffer contains the required amount of free
+               space to store the provided number of data frames.
+               (Therefor we don't need to check the return argument of
+               PaUtil_GetRingBufferWriteRegions(). ;-) )
+            */
+
+            /* Retrieve pointer(s) to the ring buffer's current write
+               position(s). If the first buffer segment is too small to
+               store the requested number of bytes, an additional second
+               segment is returned. Otherwise, i.e. if the first segment
+               is large enough, the second segment's pointer will be NULL.
+            */
+            PaUtil_GetRingBufferWriteRegions(pRb                ,
+                                             lFramesPerBlock    ,
+                                             &pRingBufferData1st,
+                                             &lRingBufferSize1st,
+                                             &pRingBufferData2nd,
+                                             &lRingBufferSize2nd);
+
+            /* Set number of frames to be copied to the ring buffer. */
+            PaUtil_SetOutputFrameCount( pBp, lRingBufferSize1st ); 
+            /* Setup ring buffer access. */
+            PaUtil_SetInterleavedOutputChannels(pBp               ,  /* Buffer processor. */
+                                                0                 ,  /* The first channel's index. */
+                                                pRingBufferData1st,  /* First ring buffer segment. */
+                                                0                 ); /* Use all available channels. */
+
+            /* If a second ring buffer segment is required. */
+            if( lRingBufferSize2nd ) {
+                /* Set number of frames to be copied to the ring buffer. */
+                PaUtil_Set2ndOutputFrameCount( pBp, lRingBufferSize2nd );
+                /* Setup ring buffer access. */
+                PaUtil_Set2ndInterleavedOutputChannels(pBp               ,  /* Buffer processor. */
+                                                       0                 ,  /* The first channel's index. */
+                                                       pRingBufferData2nd,  /* Second ring buffer segment. */
+                                                       0                 ); /* Use all available channels. */
+            }
+
+            /* Let the buffer processor handle "copy and conversion" and
+               update the ring buffer indices manually. */
+            lFramesCopied = PaUtil_CopyOutput( pBp, &userBuffer, lFramesPerBlock );
+            PaUtil_AdvanceRingBufferWriteIndex( pRb, lFramesCopied );
+
+            /* Decrease number of unprocessed frames. */
+            lFramesRemaining -= lFramesCopied;
+
+        } /* Continue with the next data chunk. */
+        while( lFramesRemaining );
+
+
+        /* If there has been an output underflow within the callback */
+        if( blockingState->outputUnderflowFlag )
+        {
+            blockingState->outputUnderflowFlag = FALSE;
+
+            /* Return the corresponding error code. */
+            result = paOutputUnderflowed;
+        }
+
+    } /* If this is not an output stream. */
+    else
+    {
+        result = paCanNotWriteToAnInputOnlyStream;
+    }
+
+    return result;
+}
+
+
+static signed long GetStreamReadAvailable( PaStream* s )
+{
+    PaAsioStream *stream = (PaAsioStream*)s;
+
+    /* Call buffer utility routine to get the number of available frames. */
+    return PaUtil_GetRingBufferReadAvailable( &stream->blockingState->readRingBuffer );
+}
+
+
+static signed long GetStreamWriteAvailable( PaStream* s )
+{
+    PaAsioStream *stream = (PaAsioStream*)s;
+
+    /* Call buffer utility routine to get the number of empty buffers. */
+    return PaUtil_GetRingBufferWriteAvailable( &stream->blockingState->writeRingBuffer );
+}
+
+
+/* This routine will be called by the PortAudio engine when audio is needed.
+** It may called at interrupt level on some machines so don't do anything
+** that could mess up the system like calling malloc() or free().
+*/
+static int BlockingIoPaCallback(const void                     *inputBuffer    ,
+                                      void                     *outputBuffer   ,
+                                      unsigned long             framesPerBuffer,
+                                const PaStreamCallbackTimeInfo *timeInfo       ,
+                                      PaStreamCallbackFlags     statusFlags    ,
+                                      void                     *userData       )
+{
+    PaError result = paNoError; /* Initial return value. */
+    PaAsioStream *stream = *(PaAsioStream**)userData; /* The PA ASIO stream. */
+    PaAsioStreamBlockingState *blockingState = stream->blockingState; /* Persume blockingState is valid, otherwise the callback wouldn't be running. */
+
+    /* Get a pointer to the stream's blocking i/o buffer processor. */
+    PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor;
+    PaUtilRingBuffer      *pRb = NULL;
+
+    /* If output data has been requested. */
+    if( stream->outputChannelCount )
+    {
+        /* If the callback input argument signalizes a output underflow,
+           make sure the WriteStream() function knows about it, too! */
+        if( statusFlags & paOutputUnderflowed ) {
+            blockingState->outputUnderflowFlag = TRUE;
+        }
+
+        /* Access the corresponding ring buffer. */
+        pRb = &blockingState->writeRingBuffer;
+
+        /* If the blocking i/o buffer contains enough output data, */
+        if( PaUtil_GetRingBufferReadAvailable(pRb) >= (long) framesPerBuffer )
+        {
+            /* Extract the requested data from the ring buffer. */
+            PaUtil_ReadRingBuffer( pRb, outputBuffer, framesPerBuffer );
+        }
+        else /* If no output data is available :-( */
+        {
+            /* Signalize a write-buffer underflow. */
+            blockingState->outputUnderflowFlag = TRUE;
+
+            /* Fill the output buffer with silence. */
+            (*pBp->outputZeroer)( outputBuffer, 1, pBp->outputChannelCount * framesPerBuffer );
+
+            /* If playback is to be stopped */
+            if( blockingState->stopFlag && PaUtil_GetRingBufferReadAvailable(pRb) < (long) framesPerBuffer )
+            {
+                /* Extract all the remaining data from the ring buffer,
+                   whether it is a complete data block or not. */
+                PaUtil_ReadRingBuffer( pRb, outputBuffer, PaUtil_GetRingBufferReadAvailable(pRb) );
+            }
+        }
+
+        /* Set blocking i/o event? */
+        if( blockingState->writeBuffersRequestedFlag && PaUtil_GetRingBufferWriteAvailable(pRb) >= (long) blockingState->writeBuffersRequested )
+        {
+            /* Reset buffer request. */
+            blockingState->writeBuffersRequestedFlag = FALSE;
+            blockingState->writeBuffersRequested     = 0;
+            /* Signalize that requested buffers are ready. */
+            SetEvent( blockingState->writeBuffersReadyEvent );
+            /* What do we do if SetEvent() returns zero, i.e. the event
+               could not be set? How to return errors from within the
+               callback? - S.Fischer */
+        }
+    }
+
+    /* If input data has been supplied. */
+    if( stream->inputChannelCount )
+    {
+        /* If the callback input argument signalizes a input overflow,
+           make sure the ReadStream() function knows about it, too! */
+        if( statusFlags & paInputOverflowed ) {
+            blockingState->inputOverflowFlag = TRUE;
+        }
+
+        /* Access the corresponding ring buffer. */
+        pRb = &blockingState->readRingBuffer;
+
+        /* If the blocking i/o buffer contains not enough input buffers */
+        if( PaUtil_GetRingBufferWriteAvailable(pRb) < (long) framesPerBuffer )
+        {
+            /* Signalize a read-buffer overflow. */
+            blockingState->inputOverflowFlag = TRUE;
+
+            /* Remove some old data frames from the buffer. */
+            PaUtil_AdvanceRingBufferReadIndex( pRb, framesPerBuffer );
+        }
+
+        /* Insert the current input data into the ring buffer. */
+        PaUtil_WriteRingBuffer( pRb, inputBuffer, framesPerBuffer );
+
+        /* Set blocking i/o event? */
+        if( blockingState->readFramesRequestedFlag && PaUtil_GetRingBufferReadAvailable(pRb) >= (long) blockingState->readFramesRequested )
+        {
+            /* Reset buffer request. */
+            blockingState->readFramesRequestedFlag = FALSE;
+            blockingState->readFramesRequested     = 0;
+            /* Signalize that requested buffers are ready. */
+            SetEvent( blockingState->readFramesReadyEvent );
+            /* What do we do if SetEvent() returns zero, i.e. the event
+               could not be set? How to return errors from within the
+               callback? - S.Fischer */
+            /** @todo report an error with PA_DEBUG */
+        }
+    }
+
+    return paContinue;
+}
+
+
+PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific )
+{
+    PaError result = paNoError;
+    PaUtilHostApiRepresentation *hostApi;
+    PaDeviceIndex hostApiDevice;
+    ASIODriverInfo asioDriverInfo;
+    ASIOError asioError;
+    int asioIsInitialized = 0;
+    PaAsioHostApiRepresentation *asioHostApi;
+    PaAsioDeviceInfo *asioDeviceInfo;
+    PaWinUtilComInitializationResult comInitializationResult;
+
+    /* initialize COM again here, we might be in another thread */
+    result = PaWinUtil_CoInitialize( paASIO, &comInitializationResult );
+    if( result != paNoError )
+        return result;
+
+    result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO );
+    if( result != paNoError )
+        goto error;
+
+    result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi );
+    if( result != paNoError )
+        goto error;
+
+    /*
+        In theory we could proceed if the currently open device was the same
+        one for which the control panel was requested, however  because the
+        window pointer is not available until this function is called we
+        currently need to call ASIOInit() again here, which of course can't be
+        done safely while a stream is open.
+    */
+
+    asioHostApi = (PaAsioHostApiRepresentation*)hostApi;
+    if( asioHostApi->openAsioDeviceIndex != paNoDevice )
+    {
+        result = paDeviceUnavailable;
+        goto error;
+    }
+
+    asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice];
+
+    if( !asioHostApi->asioDrivers->loadDriver( const_cast<char*>(asioDeviceInfo->commonDeviceInfo.name) ) )
+    {
+        result = paUnanticipatedHostError;
+        goto error;
+    }
+
+    /* CRUCIAL!!! */
+    memset( &asioDriverInfo, 0, sizeof(ASIODriverInfo) );
+    asioDriverInfo.asioVersion = 2;
+    asioDriverInfo.sysRef = systemSpecific;
+    asioError = ASIOInit( &asioDriverInfo );
+    if( asioError != ASE_OK )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        goto error;
+    }
+    else
+    {
+        asioIsInitialized = 1;
+    }
+
+PA_DEBUG(("PaAsio_ShowControlPanel: ASIOInit(): %s\n", PaAsio_GetAsioErrorText(asioError) ));
+PA_DEBUG(("asioVersion: ASIOInit(): %ld\n",   asioDriverInfo.asioVersion )); 
+PA_DEBUG(("driverVersion: ASIOInit(): %ld\n", asioDriverInfo.driverVersion )); 
+PA_DEBUG(("Name: ASIOInit(): %s\n",           asioDriverInfo.name )); 
+PA_DEBUG(("ErrorMessage: ASIOInit(): %s\n",   asioDriverInfo.errorMessage )); 
+
+    asioError = ASIOControlPanel();
+    if( asioError != ASE_OK )
+    {
+        PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErrorText(asioError) ));
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        goto error;
+    }
+
+PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErrorText(asioError) ));
+
+    asioError = ASIOExit();
+    if( asioError != ASE_OK )
+    {
+        result = paUnanticipatedHostError;
+        PA_ASIO_SET_LAST_ASIO_ERROR( asioError );
+        asioIsInitialized = 0;
+        goto error;
+    }
+
+PA_DEBUG(("PaAsio_ShowControlPanel: ASIOExit(): %s\n", PaAsio_GetAsioErrorText(asioError) ));
+
+    return result;
+
+error:
+    if( asioIsInitialized )
+	{
+		ASIOExit();
+	}
+
+    PaWinUtil_CoUninitialize( paASIO, &comInitializationResult );
+
+    return result;
+}
+
+
+PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex,
+        const char** channelName )
+{
+    PaError result = paNoError;
+    PaUtilHostApiRepresentation *hostApi;
+    PaDeviceIndex hostApiDevice;
+    PaAsioDeviceInfo *asioDeviceInfo;
+
+
+    result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO );
+    if( result != paNoError )
+        goto error;
+
+    result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi );
+    if( result != paNoError )
+        goto error;
+
+    asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice];
+
+    if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxInputChannels ){
+        result = paInvalidChannelCount;
+        goto error;
+    }
+
+    *channelName = asioDeviceInfo->asioChannelInfos[channelIndex].name;
+
+    return paNoError;
+    
+error:
+    return result;
+}
+
+
+PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex,
+        const char** channelName )
+{
+    PaError result = paNoError;
+    PaUtilHostApiRepresentation *hostApi;
+    PaDeviceIndex hostApiDevice;
+    PaAsioDeviceInfo *asioDeviceInfo;
+
+
+    result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO );
+    if( result != paNoError )
+        goto error;
+
+    result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi );
+    if( result != paNoError )
+        goto error;
+
+    asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice];
+
+    if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxOutputChannels ){
+        result = paInvalidChannelCount;
+        goto error;
+    }
+
+    *channelName = asioDeviceInfo->asioChannelInfos[
+            asioDeviceInfo->commonDeviceInfo.maxInputChannels + channelIndex].name;
+
+    return paNoError;
+    
+error:
+    return result;
+}
+
+
+/* NOTE: the following functions are ASIO-stream specific, and are called directly
+    by client code. We need to check for many more error conditions here because
+    we don't have the benefit of pa_front.c's parameter checking.
+*/
+
+static PaError GetAsioStreamPointer( PaAsioStream **stream, PaStream *s )
+{
+    PaError result;
+    PaUtilHostApiRepresentation *hostApi;
+    PaAsioHostApiRepresentation *asioHostApi;
+    
+    result = PaUtil_ValidateStreamPointer( s );
+    if( result != paNoError )
+        return result;
+
+    result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO );
+    if( result != paNoError )
+        return result;
+
+    asioHostApi = (PaAsioHostApiRepresentation*)hostApi;
+    
+    if( PA_STREAM_REP( s )->streamInterface == &asioHostApi->callbackStreamInterface
+            || PA_STREAM_REP( s )->streamInterface == &asioHostApi->blockingStreamInterface )
+    {
+        /* s is an ASIO  stream */
+        *stream = (PaAsioStream *)s;
+        return paNoError;
+    }
+    else
+    {
+        return paIncompatibleStreamHostApi;
+    }
+}
+
+
+PaError PaAsio_SetStreamSampleRate( PaStream* s, double sampleRate )
+{
+    PaAsioStream *stream;
+    PaError result = GetAsioStreamPointer( &stream, s );
+    if( result != paNoError )
+        return result;
+
+    if( stream != theAsioStream )
+        return paBadStreamPtr;
+
+    return ValidateAndSetSampleRate( sampleRate );
+}
+
diff --git a/external/portaudio/pa_asio.h b/external/portaudio/pa_asio.h
new file mode 100644
index 0000000..8f4624e
--- /dev/null
+++ b/external/portaudio/pa_asio.h
@@ -0,0 +1,150 @@
+#ifndef PA_ASIO_H
+#define PA_ASIO_H
+/*
+ * $Id: pa_asio.h 1667 2011-05-02 15:49:20Z rossb $
+ * PortAudio Portable Real-Time Audio Library
+ * ASIO specific extensions
+ *
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+
+/** @file
+ @ingroup public_header
+ @brief ASIO-specific PortAudio API extension header file.
+*/
+
+#include "portaudio.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+/** Retrieve legal native buffer sizes for the specificed device, in sample frames.
+
+ @param device The global index of the device about which the query is being made.
+ @param minBufferSizeFrames A pointer to the location which will receive the minimum buffer size value.
+ @param maxBufferSizeFrames A pointer to the location which will receive the maximum buffer size value.
+ @param preferredBufferSizeFrames A pointer to the location which will receive the preferred buffer size value.
+ @param granularity A pointer to the location which will receive the "granularity". This value determines
+ the step size used to compute the legal values between minBufferSizeFrames and maxBufferSizeFrames.
+ If granularity is -1 then available buffer size values are powers of two.
+
+ @see ASIOGetBufferSize in the ASIO SDK.
+
+ @note: this function used to be called PaAsio_GetAvailableLatencyValues. There is a
+ #define that maps PaAsio_GetAvailableLatencyValues to this function for backwards compatibility.
+*/
+PaError PaAsio_GetAvailableBufferSizes( PaDeviceIndex device,
+		long *minBufferSizeFrames, long *maxBufferSizeFrames, long *preferredBufferSizeFrames, long *granularity );
+
+
+/** Backwards compatibility alias for PaAsio_GetAvailableBufferSizes
+
+ @see PaAsio_GetAvailableBufferSizes
+*/
+#define PaAsio_GetAvailableLatencyValues PaAsio_GetAvailableBufferSizes
+
+
+/** Display the ASIO control panel for the specified device.
+
+  @param device The global index of the device whose control panel is to be displayed.
+  @param systemSpecific On Windows, the calling application's main window handle,
+  on Macintosh this value should be zero.
+*/
+PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific );
+
+
+
+
+/** Retrieve a pointer to a string containing the name of the specified
+ input channel. The string is valid until Pa_Terminate is called.
+
+ The string will be no longer than 32 characters including the null terminator.
+*/
+PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex,
+        const char** channelName );
+
+        
+/** Retrieve a pointer to a string containing the name of the specified
+ input channel. The string is valid until Pa_Terminate is called.
+
+ The string will be no longer than 32 characters including the null terminator.
+*/
+PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex,
+        const char** channelName );
+
+
+/** Set the sample rate of an open paASIO stream.
+ 
+ @param stream The stream to operate on.
+ @param sampleRate The new sample rate. 
+
+ Note that this function may fail if the stream is alredy running and the 
+ ASIO driver does not support switching the sample rate of a running stream.
+
+ Returns paIncompatibleStreamHostApi if stream is not a paASIO stream.
+*/
+PaError PaAsio_SetStreamSampleRate( PaStream* stream, double sampleRate );
+
+
+#define paAsioUseChannelSelectors      (0x01)
+
+typedef struct PaAsioStreamInfo{
+    unsigned long size;             /**< sizeof(PaAsioStreamInfo) */
+    PaHostApiTypeId hostApiType;    /**< paASIO */
+    unsigned long version;          /**< 1 */
+
+    unsigned long flags;
+
+    /* Support for opening only specific channels of an ASIO device.
+        If the paAsioUseChannelSelectors flag is set, channelSelectors is a
+        pointer to an array of integers specifying the device channels to use.
+        When used, the length of the channelSelectors array must match the
+        corresponding channelCount parameter to Pa_OpenStream() otherwise a
+        crash may result.
+        The values in the selectors array must specify channels within the
+        range of supported channels for the device or paInvalidChannelCount will
+        result.
+    */
+    int *channelSelectors;
+}PaAsioStreamInfo;
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PA_ASIO_H */
diff --git a/external/portaudio/pa_converters.c b/external/portaudio/pa_converters.c
index 3b98c85..ef65b68 100644
--- a/external/portaudio/pa_converters.c
+++ b/external/portaudio/pa_converters.c
@@ -1,5 +1,5 @@
 /*
- * $Id: pa_converters.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_converters.c 1748 2011-09-01 22:08:32Z philburk $
  * Portable Audio I/O Library sample conversion mechanism
  *
  * Based on the Open Source API proposed by Ross Bencina
@@ -39,18 +39,21 @@
 /** @file
  @ingroup common_src
 
- @brief Conversion functions implementations.
+ @brief Conversion function implementations.
  
  If the C9x function lrintf() is available, define PA_USE_C99_LRINTF to use it
 
  @todo Consider whether functions which dither but don't clip should exist,
  V18 automatically enabled clipping whenever dithering was selected. Perhaps
- we should do the same.
+ we should do the same. 
+    see: "require clipping for dithering sample conversion functions?"
+    http://www.portaudio.com/trac/ticket/112
 
- @todo implement the converters marked IMPLEMENT ME: Float32_To_UInt8_Dither,
- Float32_To_UInt8_Clip, Float32_To_UInt8_DitherClip, Int32_To_Int24_Dither,
+ @todo implement the converters marked IMPLEMENT ME: Int32_To_Int24_Dither,
  Int32_To_UInt8_Dither, Int24_To_Int16_Dither, Int24_To_Int8_Dither, 
- Int24_To_UInt8_Dither, Int16_To_Int8_Dither, Int16_To_UInt8_Dither,
+ Int24_To_UInt8_Dither, Int16_To_Int8_Dither, Int16_To_UInt8_Dither
+    see: "some conversion functions are not implemented in pa_converters.c"
+    http://www.portaudio.com/trac/ticket/35
 
  @todo review the converters marked REVIEW: Float32_To_Int32,
  Float32_To_Int32_Dither, Float32_To_Int32_Clip, Float32_To_Int32_DitherClip,
@@ -459,7 +462,7 @@ static void Float32_To_Int24(
     while( count-- )
     {
         /* convert to 32 bit and drop the low 8 bits */
-        double scaled = *src * 0x7FFFFFFF;
+        double scaled = (double)(*src) * 2147483647.0;
         temp = (PaInt32) scaled;
         
 #if defined(PA_LITTLE_ENDIAN)
@@ -725,8 +728,7 @@ static void Float32_To_Int8_Dither(
 {
     float *src = (float*)sourceBuffer;
     signed char *dest =  (signed char*)destinationBuffer;
-    (void)ditherGenerator; /* unused parameter */
-
+    
     while( count-- )
     {
         float dither  = PaUtil_GenerateFloatTriangularDither( ditherGenerator );
@@ -817,12 +819,15 @@ static void Float32_To_UInt8_Dither(
 {
     float *src = (float*)sourceBuffer;
     unsigned char *dest =  (unsigned char*)destinationBuffer;
-    (void)ditherGenerator; /* unused parameter */
-
+    
     while( count-- )
     {
-        /* IMPLEMENT ME */
-
+        float dither  = PaUtil_GenerateFloatTriangularDither( ditherGenerator );
+        /* use smaller scaler to prevent overflow when we add the dither */
+        float dithered = (*src * (126.0f)) + dither;
+        PaInt32 samp = (PaInt32) dithered;
+        *dest = (unsigned char) (128 + samp);
+        
         src += sourceStride;
         dest += destinationStride;
     }
@@ -841,7 +846,9 @@ static void Float32_To_UInt8_Clip(
 
     while( count-- )
     {
-        /* IMPLEMENT ME */
+        PaInt32 samp = 128 + (PaInt32)(*src * (127.0f));
+        PA_CLIP_( samp, 0x0000, 0x00FF );
+        *dest = (unsigned char) samp;
 
         src += sourceStride;
         dest += destinationStride;
@@ -861,7 +868,12 @@ static void Float32_To_UInt8_DitherClip(
 
     while( count-- )
     {
-        /* IMPLEMENT ME */
+        float dither  = PaUtil_GenerateFloatTriangularDither( ditherGenerator );
+        /* use smaller scaler to prevent overflow when we add the dither */
+        float dithered = (*src * (126.0f)) + dither;
+        PaInt32 samp = 128 + (PaInt32) dithered;
+        PA_CLIP_( samp, 0x0000, 0x00FF );
+        *dest = (unsigned char) samp;
 
         src += sourceStride;
         dest += destinationStride;
@@ -1073,13 +1085,13 @@ static void Int24_To_Float32(
     {
 
 #if defined(PA_LITTLE_ENDIAN)
-        temp = (((long)src[0]) << 8);  
-        temp = temp | (((long)src[1]) << 16);
-        temp = temp | (((long)src[2]) << 24);
+        temp = (((PaInt32)src[0]) << 8);  
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 24);
 #elif defined(PA_BIG_ENDIAN)
-        temp = (((long)src[0]) << 24);
-        temp = temp | (((long)src[1]) << 16);
-        temp = temp | (((long)src[2]) << 8);
+        temp = (((PaInt32)src[0]) << 24);
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 8);
 #endif
 
         *dest = (float) ((double)temp * const_1_div_2147483648_);
@@ -1106,13 +1118,13 @@ static void Int24_To_Int32(
     {
 
 #if defined(PA_LITTLE_ENDIAN)
-        temp = (((long)src[0]) << 8);  
-        temp = temp | (((long)src[1]) << 16);
-        temp = temp | (((long)src[2]) << 24);
+        temp = (((PaInt32)src[0]) << 8);  
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 24);
 #elif defined(PA_BIG_ENDIAN)
-        temp = (((long)src[0]) << 24);
-        temp = temp | (((long)src[1]) << 16);
-        temp = temp | (((long)src[2]) << 8);
+        temp = (((PaInt32)src[0]) << 24);
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 8);
 #endif
 
         *dest = temp;
@@ -1163,13 +1175,31 @@ static void Int24_To_Int16_Dither(
     void *sourceBuffer, signed int sourceStride,
     unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator )
 {
-    (void) destinationBuffer; /* unused parameters */
-    (void) destinationStride; /* unused parameters */
-    (void) sourceBuffer; /* unused parameters */
-    (void) sourceStride; /* unused parameters */
-    (void) count; /* unused parameters */
-    (void) ditherGenerator; /* unused parameters */
-    /* IMPLEMENT ME */
+    unsigned char *src = (unsigned char*)sourceBuffer;
+    PaInt16 *dest = (PaInt16*)destinationBuffer;
+
+    PaInt32 temp, dither;
+
+    while( count-- )
+    {
+
+#if defined(PA_LITTLE_ENDIAN)
+        temp = (((PaInt32)src[0]) << 8);  
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 24);
+#elif defined(PA_BIG_ENDIAN)
+        temp = (((PaInt32)src[0]) << 24);
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 8);
+#endif
+
+        /* REVIEW */
+        dither = PaUtil_Generate16BitTriangularDither( ditherGenerator );
+        *dest = (PaInt16) (((temp >> 1) + dither) >> 15);
+
+        src  += sourceStride * 3;
+        dest += destinationStride;
+    }
 }
 
 /* -------------------------------------------------------------------------- */
@@ -1209,13 +1239,31 @@ static void Int24_To_Int8_Dither(
     void *sourceBuffer, signed int sourceStride,
     unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator )
 {
-    (void) destinationBuffer; /* unused parameters */
-    (void) destinationStride; /* unused parameters */
-    (void) sourceBuffer; /* unused parameters */
-    (void) sourceStride; /* unused parameters */
-    (void) count; /* unused parameters */
-    (void) ditherGenerator; /* unused parameters */
-    /* IMPLEMENT ME */
+    unsigned char *src = (unsigned char*)sourceBuffer;
+    signed char  *dest = (signed char*)destinationBuffer;
+    
+    PaInt32 temp, dither;
+
+    while( count-- )
+    {
+
+#if defined(PA_LITTLE_ENDIAN)
+        temp = (((PaInt32)src[0]) << 8);  
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 24);
+#elif defined(PA_BIG_ENDIAN)
+        temp = (((PaInt32)src[0]) << 24);
+        temp = temp | (((PaInt32)src[1]) << 16);
+        temp = temp | (((PaInt32)src[2]) << 8);
+#endif
+
+        /* REVIEW */
+        dither = PaUtil_Generate16BitTriangularDither( ditherGenerator );
+        *dest = (signed char) (((temp >> 1) + dither) >> 23);
+
+        src += sourceStride * 3;
+        dest += destinationStride;
+    }
 }
 
 /* -------------------------------------------------------------------------- */
diff --git a/external/portaudio/pa_cpuload.c b/external/portaudio/pa_cpuload.c
index 445503c..4465a50 100644
--- a/external/portaudio/pa_cpuload.c
+++ b/external/portaudio/pa_cpuload.c
@@ -1,5 +1,5 @@
 /*
- * $Id: pa_cpuload.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_cpuload.c 1577 2011-02-01 13:03:45Z rossb $
  * Portable Audio I/O Library CPU Load measurement functions
  * Portable CPU load measurement facility.
  *
@@ -46,7 +46,7 @@
  @todo Dynamically calculate the coefficients used to smooth the CPU Load
  Measurements over time to provide a uniform characterisation of CPU Load
  independent of rate at which PaUtil_BeginCpuLoadMeasurement /
- PaUtil_EndCpuLoadMeasurement are called.
+ PaUtil_EndCpuLoadMeasurement are called. see http://www.portaudio.com/trac/ticket/113
 */
 
 
@@ -89,7 +89,7 @@ void PaUtil_EndCpuLoadMeasurement( PaUtilCpuLoadMeasurer* measurer, unsigned lon
         measuredLoad = (measurementEndTime - measurer->measurementStartTime) / secondsFor100Percent;
 
         /* Low pass filter the calculated CPU load to reduce jitter using a simple IIR low pass filter. */
-        /** FIXME @todo these coefficients shouldn't be hardwired */
+        /** FIXME @todo these coefficients shouldn't be hardwired see: http://www.portaudio.com/trac/ticket/113 */
 #define LOWPASS_COEFFICIENT_0   (0.9)
 #define LOWPASS_COEFFICIENT_1   (0.99999 - LOWPASS_COEFFICIENT_0)
 
diff --git a/external/portaudio/pa_debugprint.c b/external/portaudio/pa_debugprint.c
index b788151..67e414a 100644
--- a/external/portaudio/pa_debugprint.c
+++ b/external/portaudio/pa_debugprint.c
@@ -48,22 +48,23 @@
 	"byte code/abi portable". So the technique used here is to allocate a local
 	a static array, write in it, then callback the user with a pointer to its
 	start.
-
-    @todo Consider allocating strdump using dynamic allocation.
-    @todo Consider reentrancy and possibly corrupted strdump buffer.
 */
 
-
 #include <stdio.h>
 #include <stdarg.h>
 
 #include "pa_debugprint.h"
 
+// for OutputDebugStringA
+#if defined(_MSC_VER) && defined(PA_ENABLE_MSVC_DEBUG_OUTPUT)
+	#define WIN32_LEAN_AND_MEAN // exclude rare headers
+	#include "windows.h"
+#endif
 
+// User callback
+static PaUtilLogCallback userCB = NULL;
 
-static PaUtilLogCallback userCB=0;
-
-
+// Sets user callback
 void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb)
 {
     userCB = cb;
@@ -72,36 +73,51 @@ void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb)
 /*
  If your platform doesn�t have vsnprintf, you are stuck with a
  VERY dangerous alternative, vsprintf (with no n)
- */
-
-#if (_MSC_VER) && (_MSC_VER <= 1400)
-#define VSNPRINTF  _vsnprintf
+*/
+#if _MSC_VER
+	/* Some Windows Mobile SDKs don't define vsnprintf but all define _vsnprintf (hopefully).
+	   According to MSDN "vsnprintf is identical to _vsnprintf". So we use _vsnprintf with MSC.
+	*/
+	#define VSNPRINTF  _vsnprintf 
 #else
-#define VSNPRINTF  vsnprintf
+	#define VSNPRINTF  vsnprintf
 #endif
 
-#define SIZEDUMP 1024
-
-static char strdump[SIZEDUMP];
+#define PA_LOG_BUF_SIZE 2048
 
 void PaUtil_DebugPrint( const char *format, ... )
 {
+	// Optional logging into Output console of Visual Studio
+#if defined(_MSC_VER) && defined(PA_ENABLE_MSVC_DEBUG_OUTPUT)
+	{
+		char buf[PA_LOG_BUF_SIZE];
+		va_list ap;
+		va_start(ap, format);
+		VSNPRINTF(buf, sizeof(buf), format, ap);
+		buf[sizeof(buf)-1] = 0;
+		OutputDebugStringA(buf);
+		va_end(ap);
+	}
+#endif
 
-    if (userCB)
+	// Output to User-Callback
+    if (userCB != NULL)
     {
+        char strdump[PA_LOG_BUF_SIZE];
         va_list ap;
-        va_start( ap, format );
-        VSNPRINTF( strdump, SIZEDUMP, format, ap );
+        va_start(ap, format);
+        VSNPRINTF(strdump, sizeof(strdump), format, ap);
+        strdump[sizeof(strdump)-1] = 0;
         userCB(strdump);
-        va_end( ap ); 
+        va_end(ap);
     }
     else
+	// Standard output to stderr
     {
         va_list ap;
-        va_start( ap, format );
-        vfprintf( stderr, format, ap );
-        va_end( ap );
-        fflush( stderr );
+        va_start(ap, format);
+        vfprintf(stderr, format, ap);
+        va_end(ap);
+        fflush(stderr);
     }
-
 }
diff --git a/external/portaudio/pa_dither.c b/external/portaudio/pa_dither.c
index f5bdcb6..7a1b131 100644
--- a/external/portaudio/pa_dither.c
+++ b/external/portaudio/pa_dither.c
@@ -1,5 +1,5 @@
 /*
- * $Id: pa_dither.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_dither.c 1418 2009-10-12 21:00:53Z philburk $
  * Portable Audio I/O Library triangular dither generator
  *
  * Based on the Open Source API proposed by Ross Bencina
@@ -40,13 +40,16 @@
  @ingroup common_src
 
  @brief Functions for generating dither noise
- 
- Paul Boersma 2013: corrected for sizeof(long)
 */
 
-
-#include "pa_dither.h"
 #include "pa_types.h"
+#include "pa_dither.h"
+
+
+/* Note that the linear congruential algorithm requires 32 bit integers
+ * because it uses arithmetic overflow. So use PaUint32 instead of
+ * unsigned long so it will work on 64 bit systems.
+ */
 
 #define PA_DITHER_BITS_   (15)
 
@@ -59,9 +62,9 @@ void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *st
 }
 
 
-signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *state )
+PaInt32 PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *state )
 {
-    signed long current, highPass;
+    PaInt32 current, highPass;
 
     /* Generate two random numbers. */
     state->randSeed1 = (state->randSeed1 * 196314165) + 907633515;
@@ -71,9 +74,10 @@ signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerato
      * Shift before adding to prevent overflow which would skew the distribution.
      * Also shift an extra bit for the high pass filter. 
      */
-#define DITHER_SHIFT_  ((sizeof(long)*8 - PA_DITHER_BITS_) + 1)
-    current = (((signed long)state->randSeed1)>>DITHER_SHIFT_) +
-              (((signed long)state->randSeed2)>>DITHER_SHIFT_);
+#define DITHER_SHIFT_  ((sizeof(PaInt32)*8 - PA_DITHER_BITS_) + 1)
+	
+    current = (((PaInt32)state->randSeed1)>>DITHER_SHIFT_) +
+              (((PaInt32)state->randSeed2)>>DITHER_SHIFT_);
 
     /* High pass filter to reduce audibility. */
     highPass = current - state->previous;
@@ -88,7 +92,7 @@ static const float const_float_dither_scale_ = PA_FLOAT_DITHER_SCALE_;
 
 float PaUtil_GenerateFloatTriangularDither( PaUtilTriangularDitherGenerator *state )
 {
-    signed long current, highPass;
+    PaInt32 current, highPass;
 
     /* Generate two random numbers. */
     state->randSeed1 = (state->randSeed1 * 196314165) + 907633515;
@@ -98,9 +102,8 @@ float PaUtil_GenerateFloatTriangularDither( PaUtilTriangularDitherGenerator *sta
      * Shift before adding to prevent overflow which would skew the distribution.
      * Also shift an extra bit for the high pass filter. 
      */
-#define DITHER_SHIFT_  ((sizeof(long)*8 - PA_DITHER_BITS_) + 1)
-    current = (((signed long)state->randSeed1)>>DITHER_SHIFT_) +
-              (((signed long)state->randSeed2)>>DITHER_SHIFT_);
+    current = (((PaInt32)state->randSeed1)>>DITHER_SHIFT_) +
+              (((PaInt32)state->randSeed2)>>DITHER_SHIFT_);
 
     /* High pass filter to reduce audibility. */
     highPass = current - state->previous;
diff --git a/external/portaudio/pa_dither.h b/external/portaudio/pa_dither.h
index e77ce47..a5131b2 100644
--- a/external/portaudio/pa_dither.h
+++ b/external/portaudio/pa_dither.h
@@ -1,7 +1,7 @@
 #ifndef PA_DITHER_H
 #define PA_DITHER_H
 /*
- * $Id: pa_dither.h 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_dither.h 1418 2009-10-12 21:00:53Z philburk $
  * Portable Audio I/O Library triangular dither generator
  *
  * Based on the Open Source API proposed by Ross Bencina
@@ -44,18 +44,24 @@
  @brief Functions for generating dither noise
 */
 
+#include "pa_types.h"
+
 
 #ifdef __cplusplus
 extern "C"
 {
 #endif /* __cplusplus */
 
+/* Note that the linear congruential algorithm requires 32 bit integers
+ * because it uses arithmetic overflow. So use PaUint32 instead of
+ * unsigned long so it will work on 64 bit systems.
+ */
 
 /** @brief State needed to generate a dither signal */
 typedef struct PaUtilTriangularDitherGenerator{
-    unsigned long previous;
-    unsigned long randSeed1;
-    unsigned long randSeed2;
+    PaUint32 previous;
+    PaUint32 randSeed1;
+    PaUint32 randSeed2;
 } PaUtilTriangularDitherGenerator;
 
 
@@ -73,9 +79,9 @@ void PaUtil_InitializeTriangularDitherState( PaUtilTriangularDitherGenerator *di
     signed short out = (signed short)(((in>>1) + dither) >> 15);
 </pre>
  @return
- A signed long with a range of +32767 to -32768
+ A signed 32-bit integer with a range of +32767 to -32768
 */
-signed long PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *ditherState );
+PaInt32 PaUtil_Generate16BitTriangularDither( PaUtilTriangularDitherGenerator *ditherState );
 
 
 /**
diff --git a/external/portaudio/pa_endianness.h b/external/portaudio/pa_endianness.h
index bdcc74f..84e904c 100644
--- a/external/portaudio/pa_endianness.h
+++ b/external/portaudio/pa_endianness.h
@@ -1,7 +1,7 @@
 #ifndef PA_ENDIANNESS_H
 #define PA_ENDIANNESS_H
 /*
- * $Id: pa_endianness.h 1216 2007-06-10 09:26:00Z aknudsen $
+ * $Id: pa_endianness.h 1324 2008-01-27 02:03:30Z bjornroche $
  * Portable Audio I/O Library current platform endianness macros
  *
  * Based on the Open Source API proposed by Ross Bencina
@@ -120,18 +120,22 @@ extern "C"
  and raises an assertion if they don't match. <assert.h> must be included in
  the context in which this macro is used.
 */
-#if defined(PA_LITTLE_ENDIAN)
-    #define PA_VALIDATE_ENDIANNESS \
-    { \
-        const long nativeOne = 1; \
-        assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 1 ); \
-    }
-#elif defined(PA_BIG_ENDIAN)
-    #define PA_VALIDATE_ENDIANNESS \
-    { \
-        const long nativeOne = 1; \
-        assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 0 ); \
-    }
+#if defined(NDEBUG)
+    #define PA_VALIDATE_ENDIANNESS
+#else
+    #if defined(PA_LITTLE_ENDIAN)
+        #define PA_VALIDATE_ENDIANNESS \
+        { \
+            const long nativeOne = 1; \
+            assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 1 ); \
+        }
+    #elif defined(PA_BIG_ENDIAN)
+        #define PA_VALIDATE_ENDIANNESS \
+        { \
+            const long nativeOne = 1; \
+            assert( "PortAudio: compile time and runtime endianness don't match" && (((char *)&nativeOne)[0]) == 0 ); \
+        }
+    #endif
 #endif
 
 
diff --git a/external/portaudio/pa_front.c b/external/portaudio/pa_front.c
index 5af90d4..301ed39 100644
--- a/external/portaudio/pa_front.c
+++ b/external/portaudio/pa_front.c
@@ -1,10 +1,10 @@
 /*
- * $Id: pa_front.c 1229 2007-06-15 16:11:11Z rossb $
+ * $Id: pa_front.c 1880 2012-12-04 18:39:48Z rbencina $
  * Portable Audio I/O Library Multi-Host API front end
  * Validate function parameters and manage multiple host APIs.
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ * Copyright (c) 1999-2008 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -40,34 +40,25 @@
 /** @file
  @ingroup common_src
 
- @brief Implements public PortAudio API, checks some errors, forwards to
- host API implementations.
+ @brief Implements PortAudio API functions defined in portaudio.h, checks 
+ some errors, delegates platform-specific behavior to host API implementations.
  
- Implements the functions defined in the PortAudio API, checks for
- some parameter and state inconsistencies and forwards API requests to
- specific Host API implementations (via the interface declared in
- pa_hostapi.h), and Streams (via the interface declared in pa_stream.h).
+ Implements the functions defined in the PortAudio API (portaudio.h), 
+ validates some parameters and checks for state inconsistencies before 
+ forwarding API requests to specific Host API implementations (via the 
+ interface declared in pa_hostapi.h), and Streams (via the interface 
+ declared in pa_stream.h).
 
- This file handles initialization and termination of Host API
- implementations via initializers stored in the paHostApiInitializers
- global variable.
+ This file manages initialization and termination of Host API
+ implementations via initializer functions stored in the paHostApiInitializers
+ global array (usually defined in an os-specific pa_[os]_hostapis.c file).
+
+ This file maintains a list of all open streams and closes them at Pa_Terminate().
 
  Some utility functions declared in pa_util.h are implemented in this file.
 
  All PortAudio API functions can be conditionally compiled with logging code.
  To compile with logging, define the PA_LOG_API_CALLS precompiler symbol.
-
-    @todo Consider adding host API specific error text in Pa_GetErrorText() for
-    paUnanticipatedHostError
-
-    @todo Consider adding a new error code for when (inputParameters == NULL)
-    && (outputParameters == NULL)
-
-    @todo review whether Pa_CloseStream() should call the interface's
-    CloseStream function if aborting the stream returns an error code.
-
-    @todo Create new error codes if a NULL buffer pointer, or a
-    zero frame count is passed to Pa_ReadStream or Pa_WriteStream.
 */
 
 
@@ -87,7 +78,7 @@
 
 
 #define PA_VERSION_  1899
-#define PA_VERSION_TEXT_ "PortAudio V19-devel (built " __DATE__  ")"
+#define PA_VERSION_TEXT_ "PortAudio V19-devel (built " __DATE__  " " __TIME__ ")"
 
 
 
@@ -125,6 +116,7 @@ void PaUtil_SetLastHostErrorInfo( PaHostApiTypeId hostApiType, long errorCode,
 
 static PaUtilHostApiRepresentation **hostApis_ = 0;
 static int hostApisCount_ = 0;
+static int defaultHostApiIndex_ = 0;
 static int initializationCount_ = 0;
 static int deviceCount_ = 0;
 
@@ -155,6 +147,7 @@ static void TerminateHostApis( void )
         hostApis_[hostApisCount_]->Terminate( hostApis_[hostApisCount_] );
     }
     hostApisCount_ = 0;
+    defaultHostApiIndex_ = 0;
     deviceCount_ = 0;
 
     if( hostApis_ != 0 )
@@ -181,6 +174,7 @@ static PaError InitializeHostApis( void )
     }
 
     hostApisCount_ = 0;
+    defaultHostApiIndex_ = -1; /* indicates that we haven't determined the default host API yet */
     deviceCount_ = 0;
     baseDeviceIndex = 0;
 
@@ -202,6 +196,16 @@ static PaError InitializeHostApis( void )
             assert( hostApi->info.defaultInputDevice < hostApi->info.deviceCount );
             assert( hostApi->info.defaultOutputDevice < hostApi->info.deviceCount );
 
+            /* the first successfully initialized host API with a default input *or* 
+               output device is used as the default host API.
+            */
+            if( (defaultHostApiIndex_ == -1) &&
+                    ( hostApi->info.defaultInputDevice != paNoDevice 
+                        || hostApi->info.defaultOutputDevice != paNoDevice ) )
+            {
+                defaultHostApiIndex_ = hostApisCount_;
+            }
+
             hostApi->privatePaFrontInfo.baseDeviceIndex = baseDeviceIndex;
 
             if( hostApi->info.defaultInputDevice != paNoDevice )
@@ -217,6 +221,10 @@ static PaError InitializeHostApis( void )
         }
     }
 
+    /* if no host APIs have devices, the default host API is the first initialized host API */
+    if( defaultHostApiIndex_ == -1 )
+        defaultHostApiIndex_ = 0;
+
     return result;
 
 error:
@@ -378,7 +386,7 @@ const char *Pa_GetErrorText( PaError errorCode )
     {
     case paNoError:                  result = "Success"; break;
     case paNotInitialized:           result = "PortAudio not initialized"; break;
-    /** @todo could catenate the last host error text to result in the case of paUnanticipatedHostError */
+    /** @todo could catenate the last host error text to result in the case of paUnanticipatedHostError. see: http://www.portaudio.com/trac/ticket/114 */
     case paUnanticipatedHostError:   result = "Unanticipated host error"; break;
     case paInvalidChannelCount:      result = "Invalid number of channels"; break;
     case paInvalidSampleRate:        result = "Invalid sample rate"; break;
@@ -405,6 +413,8 @@ const char *Pa_GetErrorText( PaError errorCode )
     case paCanNotWriteToACallbackStream:        result = "Can't write to a callback stream"; break;
     case paCanNotReadFromAnOutputOnlyStream:    result = "Can't read from an output only stream"; break;
     case paCanNotWriteToAnInputOnlyStream:      result = "Can't write to an input only stream"; break;
+    case paIncompatibleStreamHostApi: result = "Incompatible stream host API"; break;
+    case paBadBufferPtr:             result = "Bad buffer pointer"; break;
     default:                         
 		if( errorCode > 0 )
 			result = "Invalid error code (value greater than zero)"; 
@@ -532,7 +542,7 @@ PaHostApiIndex Pa_GetDefaultHostApi( void )
     }
     else
     {
-        result = paDefaultHostApiIndex;
+        result = defaultHostApiIndex_;
 
         /* internal consistency check: make sure that the default host api
          index is within range */
@@ -814,7 +824,7 @@ static int SampleFormatIsValid( PaSampleFormat format )
         - if supplied its hostApi field matches the output device's host Api
  
     double sampleRate
-        - is not an 'absurd' rate (less than 1000. or greater than 200000.)
+        - is not an 'absurd' rate (less than 1000. or greater than 384000.)
         - sampleRate is NOT validated against device capabilities
  
     PaStreamFlags streamFlags
@@ -955,7 +965,7 @@ static PaError ValidateOpenStreamParameters(
     
     
     /* Check for absurd sample rates. */
-    if( (sampleRate < 1000.0) || (sampleRate > 200000.0) )
+    if( (sampleRate < 1000.0) || (sampleRate > 384000.0) )
         return paInvalidSampleRate;
 
     if( ((streamFlags & ~paPlatformSpecificFlags) & ~(paClipOff | paDitherOff | paNeverDropInput | paPrimeOutputBuffersUsingStreamCallback ) ) != 0 )
@@ -1313,7 +1323,6 @@ PaError PaUtil_ValidateStreamPointer( PaStream* stream )
     return paNoError;
 }
 
-
 PaError Pa_CloseStream( PaStream* stream )
 {
     PaUtilStreamInterface *interface;
@@ -1338,7 +1347,7 @@ PaError Pa_CloseStream( PaStream* stream )
         else if( result == 0 )
             result = interface->Abort( stream );
 
-        if( result == paNoError )                 /** @todo REVIEW: shouldn't we close anyway? */
+        if( result == paNoError )                 /** @todo REVIEW: shouldn't we close anyway? see: http://www.portaudio.com/trac/ticket/115 */
             result = interface->Close( stream );
     }
 
@@ -1501,7 +1510,7 @@ const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream )
         result = 0;
 
         PA_LOGAPI(("Pa_GetStreamInfo returned:\n" ));
-        PA_LOGAPI(("\tconst PaStreamInfo*: 0 [PaError error:%d ( %s )]\n", result, error, Pa_GetErrorText( error ) ));
+        PA_LOGAPI(("\tconst PaStreamInfo*: 0 [PaError error:%d ( %s )]\n", error, Pa_GetErrorText( error ) ));
 
     }
     else
@@ -1596,7 +1605,7 @@ PaError Pa_ReadStream( PaStream* stream,
     {
         if( frames == 0 )
         {
-            /* XXX: Should we not allow the implementation to signal any overflow condition? */
+            /* @todo Should we not allow the implementation to signal any overflow condition? see: http://www.portaudio.com/trac/ticket/116*/
             result = paNoError;
         }
         else if( buffer == 0 )
@@ -1636,7 +1645,7 @@ PaError Pa_WriteStream( PaStream* stream,
     {
         if( frames == 0 )
         {
-            /* XXX: Should we not allow the implementation to signal any underflow condition? */
+            /* @todo Should we not allow the implementation to signal any underflow condition? see: http://www.portaudio.com/trac/ticket/116*/
             result = paNoError;
         }
         else if( buffer == 0 )
diff --git a/external/portaudio/pa_hostapi.h b/external/portaudio/pa_hostapi.h
index 5a86d4e..d38b8fe 100644
--- a/external/portaudio/pa_hostapi.h
+++ b/external/portaudio/pa_hostapi.h
@@ -1,12 +1,12 @@
 #ifndef PA_HOSTAPI_H
 #define PA_HOSTAPI_H
 /*
- * $Id: pa_hostapi.h 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_hostapi.h 1880 2012-12-04 18:39:48Z rbencina $
  * Portable Audio I/O Library
  * host api representation
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ * Copyright (c) 1999-2008 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -42,13 +42,117 @@
 /** @file
  @ingroup common_src
 
- @brief Interface used by pa_front to virtualize functions which operate on
- host APIs.
+ @brief Interfaces and representation structures used by pa_front.c 
+ to manage and communicate with host API implementations.
 */
 
-
 #include "portaudio.h"
 
+/**
+The PA_NO_* host API macros are now deprecated in favor of PA_USE_* macros.
+PA_USE_* indicates whether a particular host API will be initialized by PortAudio.
+An undefined or 0 value indicates that the host API will not be used. A value of 1 
+indicates that the host API will be used. PA_USE_* macros should be left undefined 
+or defined to either 0 or 1.
+
+The code below ensures that PA_USE_* macros are always defined and have value
+0 or 1. Undefined symbols are defaulted to 0. Symbols that are neither 0 nor 1 
+are defaulted to 1.
+*/
+
+#ifndef PA_USE_SKELETON
+#define PA_USE_SKELETON 0
+#elif (PA_USE_SKELETON != 0) && (PA_USE_SKELETON != 1)
+#undef PA_USE_SKELETON
+#define PA_USE_SKELETON 1
+#endif 
+
+#if defined(PA_NO_ASIO) || defined(PA_NO_DS) || defined(PA_NO_WMME) || defined(PA_NO_WASAPI) || defined(PA_NO_WDMKS)
+#error "Portaudio: PA_NO_<APINAME> is no longer supported, please remove definition and use PA_USE_<APINAME> instead"
+#endif
+
+#ifndef PA_USE_ASIO
+#define PA_USE_ASIO 0
+#elif (PA_USE_ASIO != 0) && (PA_USE_ASIO != 1)
+#undef PA_USE_ASIO
+#define PA_USE_ASIO 1
+#endif 
+
+#ifndef PA_USE_DS
+#define PA_USE_DS 0
+#elif (PA_USE_DS != 0) && (PA_USE_DS != 1)
+#undef PA_USE_DS
+#define PA_USE_DS 1
+#endif 
+
+#ifndef PA_USE_WMME
+#define PA_USE_WMME 0
+#elif (PA_USE_WMME != 0) && (PA_USE_WMME != 1)
+#undef PA_USE_WMME
+#define PA_USE_WMME 1
+#endif 
+
+#ifndef PA_USE_WASAPI
+#define PA_USE_WASAPI 0
+#elif (PA_USE_WASAPI != 0) && (PA_USE_WASAPI != 1)
+#undef PA_USE_WASAPI
+#define PA_USE_WASAPI 1
+#endif 
+
+#ifndef PA_USE_WDMKS
+#define PA_USE_WDMKS 0
+#elif (PA_USE_WDMKS != 0) && (PA_USE_WDMKS != 1)
+#undef PA_USE_WDMKS
+#define PA_USE_WDMKS 1
+#endif 
+
+/* Set default values for Unix based APIs. */
+#if defined(PA_NO_OSS) || defined(PA_NO_ALSA) || defined(PA_NO_JACK) || defined(PA_NO_COREAUDIO) || defined(PA_NO_SGI) || defined(PA_NO_ASIHPI)
+#error "Portaudio: PA_NO_<APINAME> is no longer supported, please remove definition and use PA_USE_<APINAME> instead"
+#endif
+
+#ifndef PA_USE_OSS
+#define PA_USE_OSS 0
+#elif (PA_USE_OSS != 0) && (PA_USE_OSS != 1)
+#undef PA_USE_OSS
+#define PA_USE_OSS 1
+#endif 
+
+#ifndef PA_USE_ALSA
+#define PA_USE_ALSA 0
+#elif (PA_USE_ALSA != 0) && (PA_USE_ALSA != 1)
+#undef PA_USE_ALSA
+#define PA_USE_ALSA 1
+#endif 
+
+#ifndef PA_USE_JACK
+#define PA_USE_JACK 0
+#elif (PA_USE_JACK != 0) && (PA_USE_JACK != 1)
+#undef PA_USE_JACK
+#define PA_USE_JACK 1
+#endif 
+
+#ifndef PA_USE_SGI
+#define PA_USE_SGI 0
+#elif (PA_USE_SGI != 0) && (PA_USE_SGI != 1)
+#undef PA_USE_SGI
+#define PA_USE_SGI 1
+#endif 
+
+#ifndef PA_USE_COREAUDIO
+#define PA_USE_COREAUDIO 0
+#elif (PA_USE_COREAUDIO != 0) && (PA_USE_COREAUDIO != 1)
+#undef PA_USE_COREAUDIO
+#define PA_USE_COREAUDIO 1
+#endif 
+
+#ifndef PA_USE_ASIHPI
+#define PA_USE_ASIHPI 0
+#elif (PA_USE_ASIHPI != 0) && (PA_USE_ASIHPI != 1)
+#undef PA_USE_ASIHPI
+#define PA_USE_ASIHPI 1
+#endif 
+
 #ifdef __cplusplus
 extern "C"
 {
@@ -160,7 +264,7 @@ typedef struct PaUtilHostApiRepresentation {
                 - if supplied its hostApi field matches the output device's host Api
  
             double sampleRate
-                - is not an 'absurd' rate (less than 1000. or greater than 200000.)
+                - is not an 'absurd' rate (less than 1000. or greater than 384000.)
                 - sampleRate is NOT validated against device capabilities
  
             PaStreamFlags streamFlags
@@ -224,29 +328,34 @@ typedef struct PaUtilHostApiRepresentation {
 /** Prototype for the initialization function which must be implemented by every
  host API.
  
+ This function should only return an error other than paNoError if it encounters 
+ an unexpected and fatal error (memory allocation error for example). In general, 
+ there may be conditions under which it returns a NULL interface pointer and also 
+ returns paNoError. For example, if the ASIO implementation detects that ASIO is 
+ not installed, it should return a NULL interface, and paNoError.
+
  @see paHostApiInitializers
 */
 typedef PaError PaUtilHostApiInitializer( PaUtilHostApiRepresentation**, PaHostApiIndex );
 
 
 /** paHostApiInitializers is a NULL-terminated array of host API initialization
- functions. These functions are called by pa_front to initialize the host APIs
- when the client calls Pa_Initialize().
+ functions. These functions are called by pa_front.c to initialize the host APIs
+ when the client calls Pa_Initialize(). 
+ 
+ The initialization functions are invoked in order.
+
+ The first successfully initialized host API that has a default input *or* output 
+ device is used as the default PortAudio host API. This is based on the logic that
+ there is only one default host API, and it must contain the default input and output
+ devices (if defined).
 
- There is a platform specific file which defines paHostApiInitializers for that
+ There is a platform specific file that defines paHostApiInitializers for that
  platform, pa_win/pa_win_hostapis.c contains the Win32 definitions for example.
 */
 extern PaUtilHostApiInitializer *paHostApiInitializers[];
 
 
-/** The index of the default host API in the paHostApiInitializers array.
- 
- There is a platform specific file which defines paDefaultHostApiIndex for that
- platform, see pa_win/pa_win_hostapis.c for example.
-*/
-extern int paDefaultHostApiIndex;
-
-
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/external/portaudio/pa_jack.c b/external/portaudio/pa_jack.c
new file mode 100644
index 0000000..2a5dd33
--- /dev/null
+++ b/external/portaudio/pa_jack.c
@@ -0,0 +1,1769 @@
+/*
+ * $Id: pa_jack.c 1912 2013-11-15 12:27:07Z gineera $
+ * PortAudio Portable Real-Time Audio Library
+ * Latest Version at: http://www.portaudio.com
+ * JACK Implementation by Joshua Haberman
+ *
+ * Copyright (c) 2004 Stefan Westerfeld <stefan at space.twc.de>
+ * Copyright (c) 2004 Arve Knudsen <aknuds-1 at broadpark.no>
+ * Copyright (c) 2002 Joshua Haberman <joshua at haberman.com>
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/**
+ @file
+ @ingroup hostapi_src
+*/
+
+#include <string.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>  /* EBUSY */
+#include <signal.h> /* sig_atomic_t */
+#include <math.h>
+#include <semaphore.h>
+
+#include <jack/types.h>
+#include <jack/jack.h>
+
+#include "pa_util.h"
+#include "pa_hostapi.h"
+#include "pa_stream.h"
+#include "pa_process.h"
+#include "pa_allocation.h"
+#include "pa_cpuload.h"
+#include "pa_ringbuffer.h"
+#include "pa_debugprint.h"
+
+static pthread_t mainThread_;
+static char *jackErr_ = NULL;
+static const char* clientName_ = "PortAudio";
+
+#define STRINGIZE_HELPER(expr) #expr
+#define STRINGIZE(expr) STRINGIZE_HELPER(expr)
+
+/* Check PaError */
+#define ENSURE_PA(expr) \
+    do { \
+        PaError paErr; \
+        if( (paErr = (expr)) < paNoError ) \
+        { \
+            if( (paErr) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \
+            { \
+                const char *err = jackErr_; \
+                if (! err ) err = "unknown error"; \
+                PaUtil_SetLastHostErrorInfo( paJACK, -1, err ); \
+            } \
+            PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+            result = paErr; \
+            goto error; \
+        } \
+    } while( 0 )
+
+#define UNLESS(expr, code) \
+    do { \
+        if( (expr) == 0 ) \
+        { \
+            if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \
+            { \
+                const char *err = jackErr_; \
+                if (!err) err = "unknown error"; \
+                PaUtil_SetLastHostErrorInfo( paJACK, -1, err ); \
+            } \
+            PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+            result = (code); \
+            goto error; \
+        } \
+    } while( 0 )
+
+#define ASSERT_CALL(expr, success) \
+    do { \
+        int err = (expr); \
+        assert( err == success ); \
+    } while( 0 )
+
+/*
+ * Functions that directly map to the PortAudio stream interface
+ */
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate );
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                           PaStream** s,
+                           const PaStreamParameters *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           double sampleRate,
+                           unsigned long framesPerBuffer,
+                           PaStreamFlags streamFlags,
+                           PaStreamCallback *streamCallback,
+                           void *userData );
+static PaError CloseStream( PaStream* stream );
+static PaError StartStream( PaStream *stream );
+static PaError StopStream( PaStream *stream );
+static PaError AbortStream( PaStream *stream );
+static PaError IsStreamStopped( PaStream *s );
+static PaError IsStreamActive( PaStream *stream );
+/*static PaTime GetStreamInputLatency( PaStream *stream );*/
+/*static PaTime GetStreamOutputLatency( PaStream *stream );*/
+static PaTime GetStreamTime( PaStream *stream );
+static double GetStreamCpuLoad( PaStream* stream );
+
+
+/*
+ * Data specific to this API
+ */
+
+struct PaJackStream;
+
+typedef struct
+{
+    PaUtilHostApiRepresentation commonHostApiRep;
+    PaUtilStreamInterface callbackStreamInterface;
+    PaUtilStreamInterface blockingStreamInterface;
+
+    PaUtilAllocationGroup *deviceInfoMemory;
+
+    jack_client_t *jack_client;
+    int jack_buffer_size;
+    PaHostApiIndex hostApiIndex;
+
+    pthread_mutex_t mtx;
+    pthread_cond_t cond;
+    unsigned long inputBase, outputBase;
+
+    /* For dealing with the process thread */
+    volatile int xrun;     /* Received xrun notification from JACK? */
+    struct PaJackStream * volatile toAdd, * volatile toRemove;
+    struct PaJackStream *processQueue;
+    volatile sig_atomic_t jackIsDown;
+}
+PaJackHostApiRepresentation;
+
+/* PaJackStream - a stream data structure specifically for this implementation */
+
+typedef struct PaJackStream
+{
+    PaUtilStreamRepresentation streamRepresentation;
+    PaUtilBufferProcessor bufferProcessor;
+    PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+    PaJackHostApiRepresentation *hostApi;
+
+    /* our input and output ports */
+    jack_port_t **local_input_ports;
+    jack_port_t **local_output_ports;
+
+    /* the input and output ports of the client we are connecting to */
+    jack_port_t **remote_input_ports;
+    jack_port_t **remote_output_ports;
+
+    int num_incoming_connections;
+    int num_outgoing_connections;
+
+    jack_client_t *jack_client;
+
+    /* The stream is running if it's still producing samples.
+     * The stream is active if samples it produced are still being heard.
+     */
+    volatile sig_atomic_t is_running;
+    volatile sig_atomic_t is_active;
+    /* Used to signal processing thread that stream should start or stop, respectively */
+    volatile sig_atomic_t doStart, doStop, doAbort;
+
+    jack_nframes_t t0;
+
+    PaUtilAllocationGroup *stream_memory;
+
+    /* These are useful in the process callback */
+
+    int callbackResult;
+    int isSilenced;
+    int xrun;
+
+    /* These are useful for the blocking API */
+
+    int                     isBlockingStream;
+    PaUtilRingBuffer        inFIFO;
+    PaUtilRingBuffer        outFIFO;
+    volatile sig_atomic_t   data_available;
+    sem_t                   data_semaphore;
+    int                     bytesPerFrame;
+    int                     samplesPerFrame;
+
+    struct PaJackStream *next;
+}
+PaJackStream;
+
+/* In calls to jack_get_ports() this filter expression is used instead of ""
+ * to prevent any other types (eg Midi ports etc) being listed */
+#define JACK_PORT_TYPE_FILTER "audio"
+
+#define TRUE 1
+#define FALSE 0
+
+/*
+ * Functions specific to this API
+ */
+
+static int JackCallback( jack_nframes_t frames, void *userData );
+
+
+/*
+ *
+ * Implementation
+ *
+ */
+
+/* ---- blocking emulation layer ---- */
+
+/* Allocate buffer. */
+static PaError BlockingInitFIFO( PaUtilRingBuffer *rbuf, long numFrames, long bytesPerFrame )
+{
+    long numBytes = numFrames * bytesPerFrame;
+    char *buffer = (char *) malloc( numBytes );
+    if( buffer == NULL ) return paInsufficientMemory;
+    memset( buffer, 0, numBytes );
+    return (PaError) PaUtil_InitializeRingBuffer( rbuf, 1, numBytes, buffer );
+}
+
+/* Free buffer. */
+static PaError BlockingTermFIFO( PaUtilRingBuffer *rbuf )
+{
+    if( rbuf->buffer ) free( rbuf->buffer );
+    rbuf->buffer = NULL;
+    return paNoError;
+}
+
+static int
+BlockingCallback( const void                      *inputBuffer,
+                  void                            *outputBuffer,
+		  unsigned long                    framesPerBuffer,
+		  const PaStreamCallbackTimeInfo*  timeInfo,
+		  PaStreamCallbackFlags            statusFlags,
+		  void                             *userData )
+{
+    struct PaJackStream *stream = (PaJackStream *)userData;
+    long numBytes = stream->bytesPerFrame * framesPerBuffer;
+
+    /* This may get called with NULL inputBuffer during initial setup. */
+    if( inputBuffer != NULL )
+    {
+        PaUtil_WriteRingBuffer( &stream->inFIFO, inputBuffer, numBytes );
+    }
+    if( outputBuffer != NULL )
+    {
+        int numRead = PaUtil_ReadRingBuffer( &stream->outFIFO, outputBuffer, numBytes );
+        /* Zero out remainder of buffer if we run out of data. */
+        memset( (char *)outputBuffer + numRead, 0, numBytes - numRead );
+    }
+
+    if( !stream->data_available )
+    {
+        stream->data_available = 1;
+        sem_post( &stream->data_semaphore );
+    }
+    return paContinue;
+}
+
+static PaError
+BlockingBegin( PaJackStream *stream, int minimum_buffer_size )
+{
+    long    doRead = 0;
+    long    doWrite = 0;
+    PaError result = paNoError;
+    long    numFrames;
+
+    doRead = stream->local_input_ports != NULL;
+    doWrite = stream->local_output_ports != NULL;
+    /* <FIXME> */
+    stream->samplesPerFrame = 2;
+    stream->bytesPerFrame = sizeof(float) * stream->samplesPerFrame;
+    /* </FIXME> */
+    numFrames = 32;
+    while (numFrames < minimum_buffer_size)
+        numFrames *= 2;
+
+    if( doRead )
+    {
+        ENSURE_PA( BlockingInitFIFO( &stream->inFIFO, numFrames, stream->bytesPerFrame ) );
+    }
+    if( doWrite )
+    {
+        long numBytes;
+
+        ENSURE_PA( BlockingInitFIFO( &stream->outFIFO, numFrames, stream->bytesPerFrame ) );
+
+        /* Make Write FIFO appear full initially. */
+        numBytes = PaUtil_GetRingBufferWriteAvailable( &stream->outFIFO );
+        PaUtil_AdvanceRingBufferWriteIndex( &stream->outFIFO, numBytes );
+    }
+
+    stream->data_available = 0;
+    sem_init( &stream->data_semaphore, 0, 0 );
+
+error:
+    return result;
+}
+
+static void
+BlockingEnd( PaJackStream *stream )
+{
+    BlockingTermFIFO( &stream->inFIFO );
+    BlockingTermFIFO( &stream->outFIFO );
+
+    sem_destroy( &stream->data_semaphore );
+}
+
+static PaError BlockingReadStream( PaStream* s, void *data, unsigned long numFrames )
+{
+    PaError result = paNoError;
+    PaJackStream *stream = (PaJackStream *)s;
+
+    long bytesRead;
+    char *p = (char *) data;
+    long numBytes = stream->bytesPerFrame * numFrames;
+    while( numBytes > 0 )
+    {
+        bytesRead = PaUtil_ReadRingBuffer( &stream->inFIFO, p, numBytes );
+        numBytes -= bytesRead;
+        p += bytesRead;
+        if( numBytes > 0 )
+        {
+            /* see write for an explanation */
+            if( stream->data_available )
+                stream->data_available = 0;
+            else
+                sem_wait( &stream->data_semaphore );
+        }
+    }
+
+    return result;
+}
+
+static PaError BlockingWriteStream( PaStream* s, const void *data, unsigned long numFrames )
+{
+    PaError result = paNoError;
+    PaJackStream *stream = (PaJackStream *)s;
+    long bytesWritten;
+    char *p = (char *) data;
+    long numBytes = stream->bytesPerFrame * numFrames;
+    while( numBytes > 0 )
+    {
+        bytesWritten = PaUtil_WriteRingBuffer( &stream->outFIFO, p, numBytes );
+        numBytes -= bytesWritten;
+        p += bytesWritten;
+        if( numBytes > 0 )
+        {
+            /* we use the following algorithm:
+             *   (1) write data
+             *   (2) if some data didn't fit into the ringbuffer, set data_available to 0
+             *       to indicate to the audio that if space becomes available, we want to know
+             *   (3) retry to write data (because it might be that between (1) and (2)
+             *       new space in the buffer became available)
+             *   (4) if this failed, we are sure that the buffer is really empty and
+             *       we will definitely receive a notification when it becomes available
+             *       thus we can safely sleep
+             *
+             * if the algorithm bailed out in step (3) before, it leaks a count of 1
+             * on the semaphore; however, it doesn't matter, because if we block in (4),
+             * we also do it in a loop
+             */
+            if( stream->data_available )
+                stream->data_available = 0;
+            else
+                sem_wait( &stream->data_semaphore );
+        }
+    }
+
+    return result;
+}
+
+static signed long
+BlockingGetStreamReadAvailable( PaStream* s )
+{
+    PaJackStream *stream = (PaJackStream *)s;
+
+    int bytesFull = PaUtil_GetRingBufferReadAvailable( &stream->inFIFO );
+    return bytesFull / stream->bytesPerFrame;
+}
+
+static signed long
+BlockingGetStreamWriteAvailable( PaStream* s )
+{
+    PaJackStream *stream = (PaJackStream *)s;
+
+    int bytesEmpty = PaUtil_GetRingBufferWriteAvailable( &stream->outFIFO );
+    return bytesEmpty / stream->bytesPerFrame;
+}
+
+static PaError
+BlockingWaitEmpty( PaStream *s )
+{
+    PaJackStream *stream = (PaJackStream *)s;
+
+    while( PaUtil_GetRingBufferReadAvailable( &stream->outFIFO ) > 0 )
+    {
+        stream->data_available = 0;
+        sem_wait( &stream->data_semaphore );
+    }
+    return 0;
+}
+
+/* ---- jack driver ---- */
+
+/* BuildDeviceList():
+ *
+ * The process of determining a list of PortAudio "devices" from
+ * JACK's client/port system is fairly involved, so it is separated
+ * into its own routine.
+ */
+
+static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
+{
+    /* Utility macros for the repetitive process of allocating memory */
+
+    /* JACK has no concept of a device.  To JACK, there are clients
+     * which have an arbitrary number of ports.  To make this
+     * intelligible to PortAudio clients, we will group each JACK client
+     * into a device, and make each port of that client a channel */
+
+    PaError result = paNoError;
+    PaUtilHostApiRepresentation *commonApi = &jackApi->commonHostApiRep;
+
+    const char **jack_ports = NULL;
+    char **client_names = NULL;
+    char *regex_pattern = NULL;
+    int port_index, client_index, i;
+    double globalSampleRate;
+    regex_t port_regex;
+    unsigned long numClients = 0, numPorts = 0;
+    char *tmp_client_name = NULL;
+
+    commonApi->info.defaultInputDevice = paNoDevice;
+    commonApi->info.defaultOutputDevice = paNoDevice;
+    commonApi->info.deviceCount = 0;
+
+    /* Parse the list of ports, using a regex to grab the client names */
+    ASSERT_CALL( regcomp( &port_regex, "^[^:]*", REG_EXTENDED ), 0 );
+
+    /* since we are rebuilding the list of devices, free all memory
+     * associated with the previous list */
+    PaUtil_FreeAllAllocations( jackApi->deviceInfoMemory );
+
+    regex_pattern = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, jack_client_name_size() + 3 );
+    tmp_client_name = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, jack_client_name_size() );
+
+    /* We can only retrieve the list of clients indirectly, by first
+     * asking for a list of all ports, then parsing the port names
+     * according to the client_name:port_name convention (which is
+     * enforced by jackd)
+     * A: If jack_get_ports returns NULL, there's nothing for us to do */
+    UNLESS( (jack_ports = jack_get_ports( jackApi->jack_client, "", JACK_PORT_TYPE_FILTER, 0 )) && jack_ports[0], paNoError );
+    /* Find number of ports */
+    while( jack_ports[numPorts] )
+        ++numPorts;
+    /* At least there will be one port per client :) */
+    UNLESS( client_names = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, numPorts *
+                sizeof (char *) ), paInsufficientMemory );
+
+    /* Build a list of clients from the list of ports */
+    for( numClients = 0, port_index = 0; jack_ports[port_index] != NULL; port_index++ )
+    {
+        int client_seen = FALSE;
+        regmatch_t match_info;
+        const char *port = jack_ports[port_index];
+
+        /* extract the client name from the port name, using a regex
+         * that parses the clientname:portname syntax */
+        UNLESS( !regexec( &port_regex, port, 1, &match_info, 0 ), paInternalError );
+        assert(match_info.rm_eo - match_info.rm_so < jack_client_name_size());
+        memcpy( tmp_client_name, port + match_info.rm_so,
+                match_info.rm_eo - match_info.rm_so );
+        tmp_client_name[match_info.rm_eo - match_info.rm_so] = '\0';
+
+        /* do we know about this port's client yet? */
+        for( i = 0; i < numClients; i++ )
+        {
+            if( strcmp( tmp_client_name, client_names[i] ) == 0 )
+                client_seen = TRUE;
+        }
+
+        if (client_seen)
+            continue;   /* A: Nothing to see here, move along */
+
+        UNLESS( client_names[numClients] = (char*)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+                    strlen(tmp_client_name) + 1), paInsufficientMemory );
+
+        /* The alsa_pcm client should go in spot 0.  If this
+         * is the alsa_pcm client AND we are NOT about to put
+         * it in spot 0 put it in spot 0 and move whatever
+         * was already in spot 0 to the end. */
+        if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && numClients > 0 )
+        {
+            /* alsa_pcm goes in spot 0 */
+            strcpy( client_names[ numClients ], client_names[0] );
+            strcpy( client_names[0], tmp_client_name );
+        }
+        else
+        {
+            /* put the new client at the end of the client list */
+            strcpy( client_names[ numClients ], tmp_client_name );
+        }
+        ++numClients;
+    }
+
+    /* Now we have a list of clients, which will become the list of
+     * PortAudio devices. */
+
+    /* there is one global sample rate all clients must conform to */
+
+    globalSampleRate = jack_get_sample_rate( jackApi->jack_client );
+    UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+                sizeof(PaDeviceInfo*) * numClients ), paInsufficientMemory );
+
+    assert( commonApi->info.deviceCount == 0 );
+
+    /* Create a PaDeviceInfo structure for every client */
+    for( client_index = 0; client_index < numClients; client_index++ )
+    {
+        PaDeviceInfo *curDevInfo;
+        const char **clientPorts = NULL;
+
+        UNLESS( curDevInfo = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+                    sizeof(PaDeviceInfo) ), paInsufficientMemory );
+        UNLESS( curDevInfo->name = (char*)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+                    strlen(client_names[client_index]) + 1 ), paInsufficientMemory );
+        strcpy( (char *)curDevInfo->name, client_names[client_index] );
+
+        curDevInfo->structVersion = 2;
+        curDevInfo->hostApi = jackApi->hostApiIndex;
+
+        /* JACK is very inflexible: there is one sample rate the whole
+         * system must run at, and all clients must speak IEEE float. */
+        curDevInfo->defaultSampleRate = globalSampleRate;
+
+        /* To determine how many input and output channels are available,
+         * we re-query jackd with more specific parameters. */
+
+        sprintf( regex_pattern, "%s:.*", client_names[client_index] );
+
+        /* ... what are your output ports (that we could input from)? */
+        clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern,
+                                     JACK_PORT_TYPE_FILTER, JackPortIsOutput);
+        curDevInfo->maxInputChannels = 0;
+        curDevInfo->defaultLowInputLatency = 0.;
+        curDevInfo->defaultHighInputLatency = 0.;
+        if( clientPorts )
+        {
+            jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] );
+            curDevInfo->defaultLowInputLatency = curDevInfo->defaultHighInputLatency =
+                jack_port_get_latency( p ) / globalSampleRate;
+
+            for( i = 0; clientPorts[i] != NULL; i++)
+            {
+                /* The number of ports returned is the number of output channels.
+                 * We don't care what they are, we just care how many */
+                curDevInfo->maxInputChannels++;
+            }
+            free(clientPorts);
+        }
+
+        /* ... what are your input ports (that we could output to)? */
+        clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern,
+                                     JACK_PORT_TYPE_FILTER, JackPortIsInput);
+        curDevInfo->maxOutputChannels = 0;
+        curDevInfo->defaultLowOutputLatency = 0.;
+        curDevInfo->defaultHighOutputLatency = 0.;
+        if( clientPorts )
+        {
+            jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] );
+            curDevInfo->defaultLowOutputLatency = curDevInfo->defaultHighOutputLatency =
+                jack_port_get_latency( p ) / globalSampleRate;
+
+            for( i = 0; clientPorts[i] != NULL; i++)
+            {
+                /* The number of ports returned is the number of input channels.
+                 * We don't care what they are, we just care how many */
+                curDevInfo->maxOutputChannels++;
+            }
+            free(clientPorts);
+        }
+
+        /* Add this client to the list of devices */
+        commonApi->deviceInfos[client_index] = curDevInfo;
+        ++commonApi->info.deviceCount;
+        if( commonApi->info.defaultInputDevice == paNoDevice && curDevInfo->maxInputChannels > 0 )
+            commonApi->info.defaultInputDevice = client_index;
+        if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 )
+            commonApi->info.defaultOutputDevice = client_index;
+    }
+
+error:
+    regfree( &port_regex );
+    free( jack_ports );
+    return result;
+}
+
+static void UpdateSampleRate( PaJackStream *stream, double sampleRate )
+{
+    /* XXX: Maybe not the cleanest way of going about this? */
+    stream->cpuLoadMeasurer.samplingPeriod = stream->bufferProcessor.samplePeriod = 1. / sampleRate;
+    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+}
+
+static void JackErrorCallback( const char *msg )
+{
+    if( pthread_self() == mainThread_ )
+    {
+        assert( msg );
+        jackErr_ = realloc( jackErr_, strlen( msg ) + 1 );
+        strcpy( jackErr_, msg );
+    }
+}
+
+static void JackOnShutdown( void *arg )
+{
+    PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg;
+    PaJackStream *stream = jackApi->processQueue;
+
+    PA_DEBUG(( "%s: JACK server is shutting down\n", __FUNCTION__ ));
+    for( ; stream; stream = stream->next )
+    {
+        stream->is_active = 0;
+    }
+
+    /* Make sure that the main thread doesn't get stuck waiting on the condition */
+    ASSERT_CALL( pthread_mutex_lock( &jackApi->mtx ), 0 );
+    jackApi->jackIsDown = 1;
+    ASSERT_CALL( pthread_cond_signal( &jackApi->cond ), 0 );
+    ASSERT_CALL( pthread_mutex_unlock( &jackApi->mtx ), 0 );
+
+}
+
+static int JackSrCb( jack_nframes_t nframes, void *arg )
+{
+    PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg;
+    double sampleRate = (double)nframes;
+    PaJackStream *stream = jackApi->processQueue;
+
+    /* Update all streams in process queue */
+    PA_DEBUG(( "%s: Acting on change in JACK samplerate: %f\n", __FUNCTION__, sampleRate ));
+    for( ; stream; stream = stream->next )
+    {
+        if( stream->streamRepresentation.streamInfo.sampleRate != sampleRate )
+        {
+            PA_DEBUG(( "%s: Updating samplerate\n", __FUNCTION__ ));
+            UpdateSampleRate( stream, sampleRate );
+        }
+    }
+
+    return 0;
+}
+
+static int JackXRunCb(void *arg) {
+    PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)arg;
+    assert( hostApi );
+    hostApi->xrun = TRUE;
+    PA_DEBUG(( "%s: JACK signalled xrun\n", __FUNCTION__ ));
+    return 0;
+}
+
+PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi,
+                           PaHostApiIndex hostApiIndex )
+{
+    PaError result = paNoError;
+    PaJackHostApiRepresentation *jackHostApi;
+    int activated = 0;
+    jack_status_t jackStatus = 0;
+    *hostApi = NULL;    /* Initialize to NULL */
+
+    UNLESS( jackHostApi = (PaJackHostApiRepresentation*)
+        PaUtil_AllocateMemory( sizeof(PaJackHostApiRepresentation) ), paInsufficientMemory );
+    UNLESS( jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
+
+    mainThread_ = pthread_self();
+    ASSERT_CALL( pthread_mutex_init( &jackHostApi->mtx, NULL ), 0 );
+    ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, NULL ), 0 );
+
+    /* Try to become a client of the JACK server.  If we cannot do
+     * this, then this API cannot be used.
+     *
+     * Without the JackNoStartServer option, the jackd server is started
+     * automatically which we do not want.
+     */
+
+    jackHostApi->jack_client = jack_client_open( clientName_, JackNoStartServer, &jackStatus );
+    if( !jackHostApi->jack_client )
+    {
+        /* the V19 development docs say that if an implementation
+         * detects that it cannot be used, it should return a NULL
+         * interface and paNoError */
+        PA_DEBUG(( "%s: Couldn't connect to JACK, status: %d\n", __FUNCTION__, jackStatus ));
+        result = paNoError;
+        goto error;
+    }
+
+    jackHostApi->hostApiIndex = hostApiIndex;
+
+    *hostApi = &jackHostApi->commonHostApiRep;
+    (*hostApi)->info.structVersion = 1;
+    (*hostApi)->info.type = paJACK;
+    (*hostApi)->info.name = "JACK Audio Connection Kit";
+
+    /* Build a device list by querying the JACK server */
+    ENSURE_PA( BuildDeviceList( jackHostApi ) );
+
+    /* Register functions */
+
+    (*hostApi)->Terminate = Terminate;
+    (*hostApi)->OpenStream = OpenStream;
+    (*hostApi)->IsFormatSupported = IsFormatSupported;
+
+    PaUtil_InitializeStreamInterface( &jackHostApi->callbackStreamInterface,
+                                      CloseStream, StartStream,
+                                      StopStream, AbortStream,
+                                      IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, GetStreamCpuLoad,
+                                      PaUtil_DummyRead, PaUtil_DummyWrite,
+                                      PaUtil_DummyGetReadAvailable,
+                                      PaUtil_DummyGetWriteAvailable );
+
+    PaUtil_InitializeStreamInterface( &jackHostApi->blockingStreamInterface, CloseStream, StartStream,
+                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, PaUtil_DummyGetCpuLoad,
+                                      BlockingReadStream, BlockingWriteStream,
+                                      BlockingGetStreamReadAvailable, BlockingGetStreamWriteAvailable );
+
+    jackHostApi->inputBase = jackHostApi->outputBase = 0;
+    jackHostApi->xrun = 0;
+    jackHostApi->toAdd = jackHostApi->toRemove = NULL;
+    jackHostApi->processQueue = NULL;
+    jackHostApi->jackIsDown = 0;
+
+    jack_on_shutdown( jackHostApi->jack_client, JackOnShutdown, jackHostApi );
+    jack_set_error_function( JackErrorCallback );
+    jackHostApi->jack_buffer_size = jack_get_buffer_size ( jackHostApi->jack_client );
+    /* Don't check for error, may not be supported (deprecated in at least jackdmp) */
+    jack_set_sample_rate_callback( jackHostApi->jack_client, JackSrCb, jackHostApi );
+    UNLESS( !jack_set_xrun_callback( jackHostApi->jack_client, JackXRunCb, jackHostApi ), paUnanticipatedHostError );
+    UNLESS( !jack_set_process_callback( jackHostApi->jack_client, JackCallback, jackHostApi ), paUnanticipatedHostError );
+    UNLESS( !jack_activate( jackHostApi->jack_client ), paUnanticipatedHostError );
+    activated = 1;
+
+    return result;
+
+error:
+    if( activated )
+        ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 );
+
+    if( jackHostApi )
+    {
+        if( jackHostApi->jack_client )
+            ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 );
+
+        if( jackHostApi->deviceInfoMemory )
+        {
+            PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory );
+            PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory );
+        }
+
+        PaUtil_FreeMemory( jackHostApi );
+    }
+    return result;
+}
+
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+    PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
+
+    /* note: this automatically disconnects all ports, since a deactivated
+     * client is not allowed to have any ports connected */
+    ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 );
+
+    ASSERT_CALL( pthread_mutex_destroy( &jackHostApi->mtx ), 0 );
+    ASSERT_CALL( pthread_cond_destroy( &jackHostApi->cond ), 0 );
+
+    ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 );
+
+    if( jackHostApi->deviceInfoMemory )
+    {
+        PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory );
+        PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory );
+    }
+
+    PaUtil_FreeMemory( jackHostApi );
+
+    free( jackErr_ );
+    jackErr_ = NULL;
+}
+
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate )
+{
+    int inputChannelCount = 0, outputChannelCount = 0;
+    PaSampleFormat inputSampleFormat, outputSampleFormat;
+
+    if( inputParameters )
+    {
+        inputChannelCount = inputParameters->channelCount;
+        inputSampleFormat = inputParameters->sampleFormat;
+
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        /* check that input device can support inputChannelCount */
+        if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
+            return paInvalidChannelCount;
+
+        /* validate inputStreamInfo */
+        if( inputParameters->hostApiSpecificStreamInfo )
+            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+    }
+    else
+    {
+        inputChannelCount = 0;
+    }
+
+    if( outputParameters )
+    {
+        outputChannelCount = outputParameters->channelCount;
+        outputSampleFormat = outputParameters->sampleFormat;
+
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        /* check that output device can support inputChannelCount */
+        if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
+            return paInvalidChannelCount;
+
+        /* validate outputStreamInfo */
+        if( outputParameters->hostApiSpecificStreamInfo )
+            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+    }
+    else
+    {
+        outputChannelCount = 0;
+    }
+
+    /*
+        The following check is not necessary for JACK.
+
+            - if a full duplex stream is requested, check that the combination
+                of input and output parameters is supported
+
+
+        Because the buffer adapter handles conversion between all standard
+        sample formats, the following checks are only required if paCustomFormat
+        is implemented, or under some other unusual conditions.
+
+            - check that input device can support inputSampleFormat, or that
+                we have the capability to convert from outputSampleFormat to
+                a native format
+
+            - check that output device can support outputSampleFormat, or that
+                we have the capability to convert from outputSampleFormat to
+                a native format
+    */
+
+    /* check that the device supports sampleRate */
+
+#define ABS(x) ( (x) > 0 ? (x) : -(x) )
+    if( ABS(sampleRate - jack_get_sample_rate(((PaJackHostApiRepresentation *) hostApi)->jack_client )) > 1 )
+       return paInvalidSampleRate;
+#undef ABS
+
+    return paFormatIsSupported;
+}
+
+/* Basic stream initialization */
+static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentation *hostApi, int numInputChannels,
+        int numOutputChannels )
+{
+    PaError result = paNoError;
+    assert( stream );
+
+    memset( stream, 0, sizeof (PaJackStream) );
+    UNLESS( stream->stream_memory = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
+    stream->jack_client = hostApi->jack_client;
+    stream->hostApi = hostApi;
+
+    if( numInputChannels > 0 )
+    {
+        UNLESS( stream->local_input_ports =
+                (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
+                paInsufficientMemory );
+        memset( stream->local_input_ports, 0, sizeof(jack_port_t*) * numInputChannels );
+        UNLESS( stream->remote_output_ports =
+                (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
+                paInsufficientMemory );
+        memset( stream->remote_output_ports, 0, sizeof(jack_port_t*) * numInputChannels );
+    }
+    if( numOutputChannels > 0 )
+    {
+        UNLESS( stream->local_output_ports =
+                (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
+                paInsufficientMemory );
+        memset( stream->local_output_ports, 0, sizeof(jack_port_t*) * numOutputChannels );
+        UNLESS( stream->remote_input_ports =
+                (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
+                paInsufficientMemory );
+        memset( stream->remote_input_ports, 0, sizeof(jack_port_t*) * numOutputChannels );
+    }
+
+    stream->num_incoming_connections = numInputChannels;
+    stream->num_outgoing_connections = numOutputChannels;
+
+error:
+    return result;
+}
+
+/*!
+ * Free resources associated with stream, and eventually stream itself.
+ *
+ * Frees allocated memory, and closes opened pcms.
+ */
+static void CleanUpStream( PaJackStream *stream, int terminateStreamRepresentation, int terminateBufferProcessor )
+{
+    int i;
+    assert( stream );
+
+    if( stream->isBlockingStream )
+        BlockingEnd( stream );
+
+    for( i = 0; i < stream->num_incoming_connections; ++i )
+    {
+        if( stream->local_input_ports[i] )
+            ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_input_ports[i] ), 0 );
+    }
+    for( i = 0; i < stream->num_outgoing_connections; ++i )
+    {
+        if( stream->local_output_ports[i] )
+            ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_output_ports[i] ), 0 );
+    }
+
+    if( terminateStreamRepresentation )
+        PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+    if( terminateBufferProcessor )
+        PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+
+    if( stream->stream_memory )
+    {
+        PaUtil_FreeAllAllocations( stream->stream_memory );
+        PaUtil_DestroyAllocationGroup( stream->stream_memory );
+    }
+    PaUtil_FreeMemory( stream );
+}
+
+static PaError WaitCondition( PaJackHostApiRepresentation *hostApi )
+{
+    PaError result = paNoError;
+    int err = 0;
+    PaTime pt = PaUtil_GetTime();
+    struct timespec ts;
+
+    ts.tv_sec = (time_t) floor( pt + 10 * 60 /* 10 minutes */ );
+    ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000);
+    /* XXX: Best enclose in loop, in case of spurious wakeups? */
+    err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts );
+
+    /* Make sure we didn't time out */
+    UNLESS( err != ETIMEDOUT, paTimedOut );
+    UNLESS( !err, paInternalError );
+
+error:
+    return result;
+}
+
+static PaError AddStream( PaJackStream *stream )
+{
+    PaError result = paNoError;
+    PaJackHostApiRepresentation *hostApi = stream->hostApi;
+    /* Add to queue of streams that should be processed */
+    ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 );
+    if( !hostApi->jackIsDown )
+    {
+        hostApi->toAdd = stream;
+        /* Unlock mutex and await signal from processing thread */
+        result = WaitCondition( stream->hostApi );
+    }
+    ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
+    ENSURE_PA( result );
+
+    UNLESS( !hostApi->jackIsDown, paDeviceUnavailable );
+
+error:
+    return result;
+}
+
+/* Remove stream from processing queue */
+static PaError RemoveStream( PaJackStream *stream )
+{
+    PaError result = paNoError;
+    PaJackHostApiRepresentation *hostApi = stream->hostApi;
+
+    /* Add to queue over streams that should be processed */
+    ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 );
+    if( !hostApi->jackIsDown )
+    {
+        hostApi->toRemove = stream;
+        /* Unlock mutex and await signal from processing thread */
+        result = WaitCondition( stream->hostApi );
+    }
+    ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
+    ENSURE_PA( result );
+
+error:
+    return result;
+}
+
+/* Add stream to JACK callback processing queue */
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                           PaStream** s,
+                           const PaStreamParameters *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           double sampleRate,
+                           unsigned long framesPerBuffer,
+                           PaStreamFlags streamFlags,
+                           PaStreamCallback *streamCallback,
+                           void *userData )
+{
+    PaError result = paNoError;
+    PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
+    PaJackStream *stream = NULL;
+    char *port_string = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, jack_port_name_size() );
+    unsigned long regexSz = jack_client_name_size() + 3;
+    char *regex_pattern = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, regexSz );
+    const char **jack_ports = NULL;
+    /* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */
+    int i;
+    int inputChannelCount, outputChannelCount;
+    const double jackSr = jack_get_sample_rate( jackHostApi->jack_client );
+    PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0;
+    int bpInitialized = 0, srInitialized = 0;   /* Initialized buffer processor and stream representation? */
+    unsigned long ofs;
+
+    /* validate platform specific flags */
+    if( (streamFlags & paPlatformSpecificFlags) != 0 )
+        return paInvalidFlag; /* unexpected platform specific flag */
+    if( (streamFlags & paPrimeOutputBuffersUsingStreamCallback) != 0 )
+    {
+        streamFlags &= ~paPrimeOutputBuffersUsingStreamCallback;
+        /*return paInvalidFlag;*/   /* This implementation does not support buffer priming */
+    }
+
+    if( framesPerBuffer != paFramesPerBufferUnspecified )
+    {
+        /* Jack operates with power of two buffers, and we don't support non-integer buffer adaption (yet) */
+        /*UNLESS( !(framesPerBuffer & (framesPerBuffer - 1)), paBufferTooBig );*/  /* TODO: Add descriptive error code? */
+    }
+
+    /* Preliminary checks */
+
+    if( inputParameters )
+    {
+        inputChannelCount = inputParameters->channelCount;
+        inputSampleFormat = inputParameters->sampleFormat;
+
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        /* check that input device can support inputChannelCount */
+        if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
+            return paInvalidChannelCount;
+
+        /* validate inputStreamInfo */
+        if( inputParameters->hostApiSpecificStreamInfo )
+            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+    }
+    else
+    {
+        inputChannelCount = 0;
+    }
+
+    if( outputParameters )
+    {
+        outputChannelCount = outputParameters->channelCount;
+        outputSampleFormat = outputParameters->sampleFormat;
+
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        /* check that output device can support inputChannelCount */
+        if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
+            return paInvalidChannelCount;
+
+        /* validate outputStreamInfo */
+        if( outputParameters->hostApiSpecificStreamInfo )
+            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+    }
+    else
+    {
+        outputChannelCount = 0;
+    }
+
+    /* ... check that the sample rate exactly matches the ONE acceptable rate
+     * A: This rate isn't necessarily constant though? */
+
+#define ABS(x) ( (x) > 0 ? (x) : -(x) )
+    if( ABS(sampleRate - jackSr) > 1 )
+       return paInvalidSampleRate;
+#undef ABS
+
+    UNLESS( stream = (PaJackStream*)PaUtil_AllocateMemory( sizeof(PaJackStream) ), paInsufficientMemory );
+    ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount ) );
+
+    /* the blocking emulation, if necessary */
+    stream->isBlockingStream = !streamCallback;
+    if( stream->isBlockingStream )
+    {
+        float latency = 0.001; /* 1ms is the absolute minimum we support */
+        int   minimum_buffer_frames = 0;
+
+        if( inputParameters && inputParameters->suggestedLatency > latency )
+            latency = inputParameters->suggestedLatency;
+        else if( outputParameters && outputParameters->suggestedLatency > latency )
+            latency = outputParameters->suggestedLatency;
+
+        /* the latency the user asked for indicates the minimum buffer size in frames */
+        minimum_buffer_frames = (int) (latency * jack_get_sample_rate( jackHostApi->jack_client ));
+
+        /* we also need to be able to store at least three full jack buffers to avoid dropouts */
+        if( jackHostApi->jack_buffer_size * 3 > minimum_buffer_frames )
+            minimum_buffer_frames = jackHostApi->jack_buffer_size * 3;
+
+        /* setup blocking API data structures (FIXME: can fail) */
+        BlockingBegin( stream, minimum_buffer_frames );
+
+        /* install our own callback for the blocking API */
+        streamCallback = BlockingCallback;
+        userData = stream;
+
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &jackHostApi->blockingStreamInterface, streamCallback, userData );
+    }
+    else
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &jackHostApi->callbackStreamInterface, streamCallback, userData );
+    }
+    srInitialized = 1;
+    PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, jackSr );
+
+    /* create the JACK ports.  We cannot connect them until audio
+     * processing begins */
+
+    /* Register a unique set of ports for this stream
+     * TODO: Robust allocation of new port names */
+
+    ofs = jackHostApi->inputBase;
+    for( i = 0; i < inputChannelCount; i++ )
+    {
+        snprintf( port_string, jack_port_name_size(), "in_%lu", ofs + i );
+        UNLESS( stream->local_input_ports[i] = jack_port_register(
+              jackHostApi->jack_client, port_string,
+              JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ), paInsufficientMemory );
+    }
+    jackHostApi->inputBase += inputChannelCount;
+
+    ofs = jackHostApi->outputBase;
+    for( i = 0; i < outputChannelCount; i++ )
+    {
+        snprintf( port_string, jack_port_name_size(), "out_%lu", ofs + i );
+        UNLESS( stream->local_output_ports[i] = jack_port_register(
+             jackHostApi->jack_client, port_string,
+             JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ), paInsufficientMemory );
+    }
+    jackHostApi->outputBase += outputChannelCount;
+
+    /* look up the jack_port_t's for the remote ports.  We could do
+     * this at stream start time, but doing it here ensures the
+     * name lookup only happens once. */
+
+    if( inputChannelCount > 0 )
+    {
+        int err = 0;
+
+        /* Get output ports of our capture device */
+        snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ inputParameters->device ]->name );
+        UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern,
+                                     JACK_PORT_TYPE_FILTER, JackPortIsOutput ), paUnanticipatedHostError );
+        for( i = 0; i < inputChannelCount && jack_ports[i]; i++ )
+        {
+            if( (stream->remote_output_ports[i] = jack_port_by_name(
+                 jackHostApi->jack_client, jack_ports[i] )) == NULL )
+            {
+                err = 1;
+                break;
+            }
+        }
+        free( jack_ports );
+        UNLESS( !err, paInsufficientMemory );
+
+        /* Fewer ports than expected? */
+        UNLESS( i == inputChannelCount, paInternalError );
+    }
+
+    if( outputChannelCount > 0 )
+    {
+        int err = 0;
+
+        /* Get input ports of our playback device */
+        snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name );
+        UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern,
+                                     JACK_PORT_TYPE_FILTER, JackPortIsInput ), paUnanticipatedHostError );
+        for( i = 0; i < outputChannelCount && jack_ports[i]; i++ )
+        {
+            if( (stream->remote_input_ports[i] = jack_port_by_name(
+                 jackHostApi->jack_client, jack_ports[i] )) == 0 )
+            {
+                err = 1;
+                break;
+            }
+        }
+        free( jack_ports );
+        UNLESS( !err , paInsufficientMemory );
+
+        /* Fewer ports than expected? */
+        UNLESS( i == outputChannelCount, paInternalError );
+    }
+
+    ENSURE_PA( PaUtil_InitializeBufferProcessor(
+                  &stream->bufferProcessor,
+                  inputChannelCount,
+                  inputSampleFormat,
+                  paFloat32 | paNonInterleaved, /* hostInputSampleFormat */
+                  outputChannelCount,
+                  outputSampleFormat,
+                  paFloat32 | paNonInterleaved, /* hostOutputSampleFormat */
+                  jackSr,
+                  streamFlags,
+                  framesPerBuffer,
+                  0,                            /* Ignored */
+                  paUtilUnknownHostBufferSize,  /* Buffer size may vary on JACK's discretion */
+                  streamCallback,
+                  userData ) );
+    bpInitialized = 1;
+
+    if( stream->num_incoming_connections > 0 )
+        stream->streamRepresentation.streamInfo.inputLatency = (jack_port_get_latency( stream->remote_output_ports[0] )
+                - jack_get_buffer_size( jackHostApi->jack_client )  /* One buffer is not counted as latency */
+            + PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor )) / sampleRate;
+    if( stream->num_outgoing_connections > 0 )
+        stream->streamRepresentation.streamInfo.outputLatency = (jack_port_get_latency( stream->remote_input_ports[0] )
+                - jack_get_buffer_size( jackHostApi->jack_client )  /* One buffer is not counted as latency */
+            + PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor )) / sampleRate;
+
+    stream->streamRepresentation.streamInfo.sampleRate = jackSr;
+    stream->t0 = jack_frame_time( jackHostApi->jack_client );   /* A: Time should run from Pa_OpenStream */
+
+    /* Add to queue of opened streams */
+    ENSURE_PA( AddStream( stream ) );
+
+    *s = (PaStream*)stream;
+
+    return result;
+
+error:
+    if( stream )
+        CleanUpStream( stream, srInitialized, bpInitialized );
+
+    return result;
+}
+
+/*
+    When CloseStream() is called, the multi-api layer ensures that
+    the stream has already been stopped or aborted.
+*/
+static PaError CloseStream( PaStream* s )
+{
+    PaError result = paNoError;
+    PaJackStream *stream = (PaJackStream*)s;
+
+    /* Remove this stream from the processing queue */
+    ENSURE_PA( RemoveStream( stream ) );
+
+error:
+    CleanUpStream( stream, 1, 1 );
+    return result;
+}
+
+static PaError RealProcess( PaJackStream *stream, jack_nframes_t frames )
+{
+    PaError result = paNoError;
+    PaStreamCallbackTimeInfo timeInfo = {0,0,0};
+    int chn;
+    int framesProcessed;
+    const double sr = jack_get_sample_rate( stream->jack_client );    /* Shouldn't change during the process callback */
+    PaStreamCallbackFlags cbFlags = 0;
+
+    /* If the user has returned !paContinue from the callback we'll want to flush the internal buffers,
+     * when these are empty we can finally mark the stream as inactive */
+    if( stream->callbackResult != paContinue &&
+            PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
+    {
+        stream->is_active = 0;
+        if( stream->streamRepresentation.streamFinishedCallback )
+            stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+        PA_DEBUG(( "%s: Callback finished\n", __FUNCTION__ ));
+
+        goto end;
+    }
+
+    timeInfo.currentTime = (jack_frame_time( stream->jack_client ) - stream->t0) / sr;
+    if( stream->num_incoming_connections > 0 )
+        timeInfo.inputBufferAdcTime = timeInfo.currentTime - jack_port_get_latency( stream->remote_output_ports[0] )
+            / sr;
+    if( stream->num_outgoing_connections > 0 )
+        timeInfo.outputBufferDacTime = timeInfo.currentTime + jack_port_get_latency( stream->remote_input_ports[0] )
+            / sr;
+
+    PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
+
+    if( stream->xrun )
+    {
+        /* XXX: Any way to tell which of these occurred? */
+        cbFlags = paOutputUnderflow | paInputOverflow;
+        stream->xrun = FALSE;
+    }
+    PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo,
+            cbFlags );
+
+    if( stream->num_incoming_connections > 0 )
+        PaUtil_SetInputFrameCount( &stream->bufferProcessor, frames );
+    if( stream->num_outgoing_connections > 0 )
+        PaUtil_SetOutputFrameCount( &stream->bufferProcessor, frames );
+
+    for( chn = 0; chn < stream->num_incoming_connections; chn++ )
+    {
+        jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*)
+            jack_port_get_buffer( stream->local_input_ports[chn],
+                    frames );
+
+        PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor,
+                chn,
+                channel_buf );
+    }
+
+    for( chn = 0; chn < stream->num_outgoing_connections; chn++ )
+    {
+        jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*)
+            jack_port_get_buffer( stream->local_output_ports[chn],
+                    frames );
+
+        PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor,
+                chn,
+                channel_buf );
+    }
+
+    framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor,
+            &stream->callbackResult );
+    /* We've specified a host buffer size mode where every frame should be consumed by the buffer processor */
+    assert( framesProcessed == frames );
+
+    PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
+
+end:
+    return result;
+}
+
+/* Update the JACK callback's stream processing queue. */
+static PaError UpdateQueue( PaJackHostApiRepresentation *hostApi )
+{
+    PaError result = paNoError;
+    int queueModified = 0;
+    const double jackSr = jack_get_sample_rate( hostApi->jack_client );
+    int err;
+
+    if( (err = pthread_mutex_trylock( &hostApi->mtx )) != 0 )
+    {
+        assert( err == EBUSY );
+        return paNoError;
+    }
+
+    if( hostApi->toAdd )
+    {
+        if( hostApi->processQueue )
+        {
+            PaJackStream *node = hostApi->processQueue;
+            /* Advance to end of queue */
+            while( node->next )
+                node = node->next;
+
+            node->next = hostApi->toAdd;
+        }
+        else
+        {
+            /* The only queue entry. */
+            hostApi->processQueue = (PaJackStream *)hostApi->toAdd;
+        }
+
+        /* If necessary, update stream state */
+        if( hostApi->toAdd->streamRepresentation.streamInfo.sampleRate != jackSr )
+            UpdateSampleRate( hostApi->toAdd, jackSr );
+
+        hostApi->toAdd = NULL;
+        queueModified = 1;
+    }
+    if( hostApi->toRemove )
+    {
+        int removed = 0;
+        PaJackStream *node = hostApi->processQueue, *prev = NULL;
+        assert( hostApi->processQueue );
+
+        while( node )
+        {
+            if( node == hostApi->toRemove )
+            {
+                if( prev )
+                    prev->next = node->next;
+                else
+                    hostApi->processQueue = (PaJackStream *)node->next;
+
+                removed = 1;
+                break;
+            }
+
+            prev = node;
+            node = node->next;
+        }
+        UNLESS( removed, paInternalError );
+        hostApi->toRemove = NULL;
+        PA_DEBUG(( "%s: Removed stream from processing queue\n", __FUNCTION__ ));
+        queueModified = 1;
+    }
+
+    if( queueModified )
+    {
+        /* Signal that we've done what was asked of us */
+        ASSERT_CALL( pthread_cond_signal( &hostApi->cond ), 0 );
+    }
+
+error:
+    ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
+
+    return result;
+}
+
+/* Audio processing callback invoked periodically from JACK. */
+static int JackCallback( jack_nframes_t frames, void *userData )
+{
+    PaError result = paNoError;
+    PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)userData;
+    PaJackStream *stream = NULL;
+    int xrun = hostApi->xrun;
+    hostApi->xrun = 0;
+
+    assert( hostApi );
+
+    ENSURE_PA( UpdateQueue( hostApi ) );
+
+    /* Process each stream */
+    stream = hostApi->processQueue;
+    for( ; stream; stream = stream->next )
+    {
+        if( xrun )  /* Don't override if already set */
+            stream->xrun = 1;
+
+        /* See if this stream is to be started */
+        if( stream->doStart )
+        {
+            /* If we can't obtain a lock, we'll try next time */
+            int err = pthread_mutex_trylock( &stream->hostApi->mtx );
+            if( !err )
+            {
+                if( stream->doStart )   /* Could potentially change before obtaining the lock */
+                {
+                    stream->is_active = 1;
+                    stream->doStart = 0;
+                    PA_DEBUG(( "%s: Starting stream\n", __FUNCTION__ ));
+                    ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 );
+                    stream->callbackResult = paContinue;
+                    stream->isSilenced = 0;
+                }
+
+                ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+            }
+            else
+                assert( err == EBUSY );
+        }
+        else if( stream->doStop || stream->doAbort )    /* Should we stop/abort stream? */
+        {
+            if( stream->callbackResult == paContinue )     /* Ok, make it stop */
+            {
+                PA_DEBUG(( "%s: Stopping stream\n", __FUNCTION__ ));
+                stream->callbackResult = stream->doStop ? paComplete : paAbort;
+            }
+        }
+
+        if( stream->is_active )
+            ENSURE_PA( RealProcess( stream, frames ) );
+        /* If we have just entered inactive state, silence output */
+        if( !stream->is_active && !stream->isSilenced )
+        {
+            int i;
+
+            /* Silence buffer after entering inactive state */
+            PA_DEBUG(( "Silencing the output\n" ));
+            for( i = 0; i < stream->num_outgoing_connections; ++i )
+            {
+                jack_default_audio_sample_t *buffer = jack_port_get_buffer( stream->local_output_ports[i], frames );
+                memset( buffer, 0, sizeof (jack_default_audio_sample_t) * frames );
+            }
+
+            stream->isSilenced = 1;
+        }
+
+        if( stream->doStop || stream->doAbort )
+        {
+            /* See if RealProcess has acted on the request */
+            if( !stream->is_active )   /* Ok, signal to the main thread that we've carried out the operation */
+            {
+                /* If we can't obtain a lock, we'll try next time */
+                int err = pthread_mutex_trylock( &stream->hostApi->mtx );
+                if( !err )
+                {
+                    stream->doStop = stream->doAbort = 0;
+                    ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 );
+                    ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+                }
+                else
+                    assert( err == EBUSY );
+            }
+        }
+    }
+
+    return 0;
+error:
+    return -1;
+}
+
+static PaError StartStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaJackStream *stream = (PaJackStream*)s;
+    int i;
+
+    /* Ready the processor */
+    PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
+
+    /* Connect the ports. Note that the ports may already have been connected by someone else in
+     * the meantime, in which case JACK returns EEXIST. */
+
+    if( stream->num_incoming_connections > 0 )
+    {
+        for( i = 0; i < stream->num_incoming_connections; i++ )
+        {
+            int r = jack_connect( stream->jack_client, jack_port_name( stream->remote_output_ports[i] ),
+                    jack_port_name( stream->local_input_ports[i] ) );
+           UNLESS( 0 == r || EEXIST == r, paUnanticipatedHostError );
+        }
+    }
+
+    if( stream->num_outgoing_connections > 0 )
+    {
+        for( i = 0; i < stream->num_outgoing_connections; i++ )
+        {
+            int r = jack_connect( stream->jack_client, jack_port_name( stream->local_output_ports[i] ),
+                    jack_port_name( stream->remote_input_ports[i] ) );
+           UNLESS( 0 == r || EEXIST == r, paUnanticipatedHostError );
+        }
+    }
+
+    stream->xrun = FALSE;
+
+    /* Enable processing */
+
+    ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 );
+    stream->doStart = 1;
+
+    /* Wait for stream to be started */
+    result = WaitCondition( stream->hostApi );
+    /*
+    do
+    {
+        err = pthread_cond_timedwait( &stream->hostApi->cond, &stream->hostApi->mtx, &ts );
+    } while( !stream->is_active && !err );
+    */
+    if( result != paNoError )   /* Something went wrong, call off the stream start */
+    {
+        stream->doStart = 0;
+        stream->is_active = 0;  /* Cancel any processing */
+    }
+    ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+
+    ENSURE_PA( result );
+
+    stream->is_running = TRUE;
+    PA_DEBUG(( "%s: Stream started\n", __FUNCTION__ ));
+
+error:
+    return result;
+}
+
+static PaError RealStop( PaJackStream *stream, int abort )
+{
+    PaError result = paNoError;
+    int i;
+
+    if( stream->isBlockingStream )
+        BlockingWaitEmpty ( stream );
+
+    ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 );
+    if( abort )
+        stream->doAbort = 1;
+    else
+        stream->doStop = 1;
+
+    /* Wait for stream to be stopped */
+    result = WaitCondition( stream->hostApi );
+    ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+    ENSURE_PA( result );
+
+    UNLESS( !stream->is_active, paInternalError );
+
+    PA_DEBUG(( "%s: Stream stopped\n", __FUNCTION__ ));
+
+error:
+    stream->is_running = FALSE;
+
+    /* Disconnect ports belonging to this stream */
+
+    if( !stream->hostApi->jackIsDown )  /* XXX: Well? */
+    {
+        for( i = 0; i < stream->num_incoming_connections; i++ )
+        {
+            if( jack_port_connected( stream->local_input_ports[i] ) )
+            {
+                UNLESS( !jack_port_disconnect( stream->jack_client, stream->local_input_ports[i] ),
+                        paUnanticipatedHostError );
+            }
+        }
+        for( i = 0; i < stream->num_outgoing_connections; i++ )
+        {
+            if( jack_port_connected( stream->local_output_ports[i] ) )
+            {
+                UNLESS( !jack_port_disconnect( stream->jack_client, stream->local_output_ports[i] ),
+                        paUnanticipatedHostError );
+            }
+        }
+    }
+
+    return result;
+}
+
+static PaError StopStream( PaStream *s )
+{
+    assert(s);
+    return RealStop( (PaJackStream *)s, 0 );
+}
+
+static PaError AbortStream( PaStream *s )
+{
+    assert(s);
+    return RealStop( (PaJackStream *)s, 1 );
+}
+
+static PaError IsStreamStopped( PaStream *s )
+{
+    PaJackStream *stream = (PaJackStream*)s;
+    return !stream->is_running;
+}
+
+
+static PaError IsStreamActive( PaStream *s )
+{
+    PaJackStream *stream = (PaJackStream*)s;
+    return stream->is_active;
+}
+
+
+static PaTime GetStreamTime( PaStream *s )
+{
+    PaJackStream *stream = (PaJackStream*)s;
+
+    /* A: Is this relevant?? --> TODO: what if we're recording-only? */
+    return (jack_frame_time( stream->jack_client ) - stream->t0) / (PaTime)jack_get_sample_rate( stream->jack_client );
+}
+
+
+static double GetStreamCpuLoad( PaStream* s )
+{
+    PaJackStream *stream = (PaJackStream*)s;
+    return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
+}
+
+PaError PaJack_SetClientName( const char* name )
+{
+    if( strlen( name ) > jack_client_name_size() )
+    {
+        /* OK, I don't know any better error code */
+        return paInvalidFlag;
+    }
+    clientName_ = name;
+    return paNoError;
+}
+
+PaError PaJack_GetClientName(const char** clientName)
+{
+    PaError result = paNoError;
+    PaJackHostApiRepresentation* jackHostApi = NULL;
+    PaJackHostApiRepresentation** ref = &jackHostApi;
+    ENSURE_PA( PaUtil_GetHostApiRepresentation( (PaUtilHostApiRepresentation**)ref, paJACK ) );
+    *clientName = jack_get_client_name( jackHostApi->jack_client );
+
+error:
+    return result;
+}
diff --git a/external/portaudio/pa_trace.h b/external/portaudio/pa_jack.h
similarity index 57%
copy from external/portaudio/pa_trace.h
copy to external/portaudio/pa_jack.h
index a4d2a33..99ef833 100644
--- a/external/portaudio/pa_trace.h
+++ b/external/portaudio/pa_jack.h
@@ -1,12 +1,12 @@
-#ifndef PA_TRACE_H
-#define PA_TRACE_H
+#ifndef PA_JACK_H
+#define PA_JACK_H
+
 /*
- * $Id: pa_trace.h 1097 2006-08-26 08:27:53Z rossb $
- * Portable Audio I/O Library Trace Facility
- * Store trace information in real-time for later printing.
+ * $Id:
+ * PortAudio Portable Real-Time Audio Library
+ * JACK-specific extensions
  *
- * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2000 Phil Burk
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -40,40 +40,38 @@
  */
 
 /** @file
- @ingroup common_src
-
- @brief Event trace mechanism for debugging.
-
- Allows data to be written to the buffer at interrupt time and dumped later.
-*/
-
+ *  @ingroup public_header
+ *  @brief JACK-specific PortAudio API extension header file.
+ */
 
-#define PA_TRACE_REALTIME_EVENTS     (0)   /* Keep log of various real-time events. */
-#define PA_MAX_TRACE_RECORDS      (2048)
+#include "portaudio.h"
 
 #ifdef __cplusplus
-extern "C"
-{
-#endif /* __cplusplus */
-
-
-#if PA_TRACE_REALTIME_EVENTS
-
-void PaUtil_ResetTraceMessages();
-void PaUtil_AddTraceMessage( const char *msg, int data );
-void PaUtil_DumpTraceMessages();
-    
-#else
-
-#define PaUtil_ResetTraceMessages() /* noop */
-#define PaUtil_AddTraceMessage(msg,data) /* noop */
-#define PaUtil_DumpTraceMessages() /* noop */
-
+extern "C" {
 #endif
 
+/** Set the JACK client name.
+ *
+ * During Pa_Initialize, When PA JACK connects as a client of the JACK server, it requests a certain
+ * name, which is for instance prepended to port names. By default this name is "PortAudio". The
+ * JACK server may append a suffix to the client name, in order to avoid clashes among clients that
+ * try to connect with the same name (e.g., different PA JACK clients).
+ *
+ * This function must be called before Pa_Initialize, otherwise it won't have any effect. Note that
+ * the string is not copied, but instead referenced directly, so it must not be freed for as long as
+ * PA might need it.
+ * @sa PaJack_GetClientName
+ */
+PaError PaJack_SetClientName( const char* name );
+
+/** Get the JACK client name used by PA JACK.
+ *
+ * The caller is responsible for freeing the returned pointer.
+ */
+PaError PaJack_GetClientName(const char** clientName);
 
 #ifdef __cplusplus
 }
-#endif /* __cplusplus */
+#endif
 
-#endif /* PA_TRACE_H */
+#endif
diff --git a/external/portaudio/pa_linux_alsa.c b/external/portaudio/pa_linux_alsa.c
index 3bbcd19..91f6b20 100644
--- a/external/portaudio/pa_linux_alsa.c
+++ b/external/portaudio/pa_linux_alsa.c
@@ -1,12 +1,13 @@
 #if defined (UNIX) && defined (ALSA)
 /*
- * $Id: pa_linux_alsa.c 1278 2007-09-12 17:39:48Z aknudsen $
+ * $Id: pa_linux_alsa.c 1911 2013-10-17 12:44:09Z gineera $
  * PortAudio Portable Real-Time Audio Library
  * Latest Version at: http://www.portaudio.com
  * ALSA implementation by Joshua Haberman and Arve Knudsen
  *
  * Copyright (c) 2002 Joshua Haberman <joshua at haberman.com>
- * Copyright (c) 2005-2007 Arve Knudsen <aknuds-1 at broadpark.no>
+ * Copyright (c) 2005-2009 Arve Knudsen <arve.knudsen at gmail.com>
+ * Copyright (c) 2008 Kevin Kofler <kevin.kofler at chello.at>
  *
  * Based on the Open Source API proposed by Ross Bencina
  * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
@@ -32,13 +33,13 @@
  */
 
 /*
- * The text above constitutes the entire PortAudio license; however, 
+ * The text above constitutes the entire PortAudio license; however,
  * the PortAudio community also makes the following non-binding requests:
  *
  * Any person wishing to distribute modifications to the Software is
  * requested to send the modifications to the original developer so that
- * they can be incorporated into the canonical version. It is also 
- * requested that these non-binding requests be included along with the 
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the
  * license above.
  */
 
@@ -62,6 +63,9 @@
 #include <time.h>
 #include <sys/mman.h>
 #include <signal.h> /* For sig_atomic_t */
+#ifdef PA_ALSA_DYNAMIC
+    #include <dlfcn.h> /* For dlXXX functions */
+#endif
 
 #include "portaudio.h"
 #include "pa_util.h"
@@ -76,30 +80,513 @@
 
 #include "pa_linux_alsa.h"
 
+/* Add missing define (for compatibility with older ALSA versions) */
+#ifndef SND_PCM_TSTAMP_ENABLE
+    #define SND_PCM_TSTAMP_ENABLE SND_PCM_TSTAMP_MMAP
+#endif
+
+/* Combine version elements into a single (unsigned) integer */
+#define ALSA_VERSION_INT(major, minor, subminor)  ((major << 16) | (minor << 8) | subminor)
+
+/* The acceptable tolerance of sample rate set, to that requested (as a ratio, eg 50 is 2%, 100 is 1%) */
+#define RATE_MAX_DEVIATE_RATIO 100
+
+/* Defines Alsa function types and pointers to these functions. */
+#define _PA_DEFINE_FUNC(x)  typedef typeof(x) x##_ft; static x##_ft *alsa_##x = 0
+
+/* Alloca helper. */
+#define __alsa_snd_alloca(ptr,type) do { size_t __alsa_alloca_size = alsa_##type##_sizeof(); (*ptr) = (type##_t *) alloca(__alsa_alloca_size); memset(*ptr, 0, __alsa_alloca_size); } while (0)
+
+_PA_DEFINE_FUNC(snd_pcm_open);
+_PA_DEFINE_FUNC(snd_pcm_close);
+_PA_DEFINE_FUNC(snd_pcm_nonblock);
+_PA_DEFINE_FUNC(snd_pcm_frames_to_bytes);
+_PA_DEFINE_FUNC(snd_pcm_prepare);
+_PA_DEFINE_FUNC(snd_pcm_start);
+_PA_DEFINE_FUNC(snd_pcm_resume);
+_PA_DEFINE_FUNC(snd_pcm_wait);
+_PA_DEFINE_FUNC(snd_pcm_state);
+_PA_DEFINE_FUNC(snd_pcm_avail_update);
+_PA_DEFINE_FUNC(snd_pcm_areas_silence);
+_PA_DEFINE_FUNC(snd_pcm_mmap_begin);
+_PA_DEFINE_FUNC(snd_pcm_mmap_commit);
+_PA_DEFINE_FUNC(snd_pcm_readi);
+_PA_DEFINE_FUNC(snd_pcm_readn);
+_PA_DEFINE_FUNC(snd_pcm_writei);
+_PA_DEFINE_FUNC(snd_pcm_writen);
+_PA_DEFINE_FUNC(snd_pcm_drain);
+_PA_DEFINE_FUNC(snd_pcm_recover);
+_PA_DEFINE_FUNC(snd_pcm_drop);
+_PA_DEFINE_FUNC(snd_pcm_area_copy);
+_PA_DEFINE_FUNC(snd_pcm_poll_descriptors);
+_PA_DEFINE_FUNC(snd_pcm_poll_descriptors_count);
+_PA_DEFINE_FUNC(snd_pcm_poll_descriptors_revents);
+_PA_DEFINE_FUNC(snd_pcm_format_size);
+_PA_DEFINE_FUNC(snd_pcm_link);
+_PA_DEFINE_FUNC(snd_pcm_delay);
+
+_PA_DEFINE_FUNC(snd_pcm_hw_params_sizeof);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_malloc);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_free);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_any);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_access);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_format);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_channels);
+//_PA_DEFINE_FUNC(snd_pcm_hw_params_set_periods_near);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_rate_near); //!!!
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_rate);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_rate_resample);
+//_PA_DEFINE_FUNC(snd_pcm_hw_params_set_buffer_time_near);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_buffer_size);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_buffer_size_near); //!!!
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_buffer_size_min);
+//_PA_DEFINE_FUNC(snd_pcm_hw_params_set_period_time_near);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_period_size_near);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_periods_integer);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_periods_min);
+
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_buffer_size);
+//_PA_DEFINE_FUNC(snd_pcm_hw_params_get_period_size);
+//_PA_DEFINE_FUNC(snd_pcm_hw_params_get_access);
+//_PA_DEFINE_FUNC(snd_pcm_hw_params_get_periods);
+//_PA_DEFINE_FUNC(snd_pcm_hw_params_get_rate);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_channels_min);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_channels_max);
+
+_PA_DEFINE_FUNC(snd_pcm_hw_params_test_period_size);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_test_format);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_test_access);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_dump);
+_PA_DEFINE_FUNC(snd_pcm_hw_params);
+
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_periods_min);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_periods_max);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_set_period_size);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_period_size_min);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_period_size_max);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_buffer_size_max);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_rate_min);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_rate_max);
+_PA_DEFINE_FUNC(snd_pcm_hw_params_get_rate_numden);
+#define alsa_snd_pcm_hw_params_alloca(ptr) __alsa_snd_alloca(ptr, snd_pcm_hw_params)
+
+_PA_DEFINE_FUNC(snd_pcm_sw_params_sizeof);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_malloc);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_current);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_set_avail_min);
+_PA_DEFINE_FUNC(snd_pcm_sw_params);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_free);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_set_start_threshold);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_set_stop_threshold);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_get_boundary);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_set_silence_threshold);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_set_silence_size);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_set_xfer_align);
+_PA_DEFINE_FUNC(snd_pcm_sw_params_set_tstamp_mode);
+#define alsa_snd_pcm_sw_params_alloca(ptr) __alsa_snd_alloca(ptr, snd_pcm_sw_params)
+
+_PA_DEFINE_FUNC(snd_pcm_info);
+_PA_DEFINE_FUNC(snd_pcm_info_sizeof);
+_PA_DEFINE_FUNC(snd_pcm_info_malloc);
+_PA_DEFINE_FUNC(snd_pcm_info_free);
+_PA_DEFINE_FUNC(snd_pcm_info_set_device);
+_PA_DEFINE_FUNC(snd_pcm_info_set_subdevice);
+_PA_DEFINE_FUNC(snd_pcm_info_set_stream);
+_PA_DEFINE_FUNC(snd_pcm_info_get_name);
+_PA_DEFINE_FUNC(snd_pcm_info_get_card);
+#define alsa_snd_pcm_info_alloca(ptr) __alsa_snd_alloca(ptr, snd_pcm_info)
+
+_PA_DEFINE_FUNC(snd_ctl_pcm_next_device);
+_PA_DEFINE_FUNC(snd_ctl_pcm_info);
+_PA_DEFINE_FUNC(snd_ctl_open);
+_PA_DEFINE_FUNC(snd_ctl_close);
+_PA_DEFINE_FUNC(snd_ctl_card_info_malloc);
+_PA_DEFINE_FUNC(snd_ctl_card_info_free);
+_PA_DEFINE_FUNC(snd_ctl_card_info);
+_PA_DEFINE_FUNC(snd_ctl_card_info_sizeof);
+_PA_DEFINE_FUNC(snd_ctl_card_info_get_name);
+#define alsa_snd_ctl_card_info_alloca(ptr) __alsa_snd_alloca(ptr, snd_ctl_card_info)
+
+_PA_DEFINE_FUNC(snd_config);
+_PA_DEFINE_FUNC(snd_config_update);
+_PA_DEFINE_FUNC(snd_config_search);
+_PA_DEFINE_FUNC(snd_config_iterator_entry);
+_PA_DEFINE_FUNC(snd_config_iterator_first);
+_PA_DEFINE_FUNC(snd_config_iterator_end);
+_PA_DEFINE_FUNC(snd_config_iterator_next);
+_PA_DEFINE_FUNC(snd_config_get_string);
+_PA_DEFINE_FUNC(snd_config_get_id);
+_PA_DEFINE_FUNC(snd_config_update_free_global);
+
+_PA_DEFINE_FUNC(snd_pcm_status);
+_PA_DEFINE_FUNC(snd_pcm_status_sizeof);
+_PA_DEFINE_FUNC(snd_pcm_status_get_tstamp);
+_PA_DEFINE_FUNC(snd_pcm_status_get_state);
+_PA_DEFINE_FUNC(snd_pcm_status_get_trigger_tstamp);
+_PA_DEFINE_FUNC(snd_pcm_status_get_delay);
+#define alsa_snd_pcm_status_alloca(ptr) __alsa_snd_alloca(ptr, snd_pcm_status)
+
+_PA_DEFINE_FUNC(snd_card_next);
+_PA_DEFINE_FUNC(snd_asoundlib_version);
+_PA_DEFINE_FUNC(snd_strerror);
+_PA_DEFINE_FUNC(snd_output_stdio_attach);
+
+#define alsa_snd_config_for_each(pos, next, node)\
+    for (pos = alsa_snd_config_iterator_first(node),\
+         next = alsa_snd_config_iterator_next(pos);\
+         pos != alsa_snd_config_iterator_end(node); pos = next, next = alsa_snd_config_iterator_next(pos))
+
+#undef _PA_DEFINE_FUNC
+
+/* Redefine 'PA_ALSA_PATHNAME' to a different Alsa library name if desired. */
+#ifndef PA_ALSA_PATHNAME
+    #define PA_ALSA_PATHNAME "libasound.so"
+#endif
+static const char *g_AlsaLibName = PA_ALSA_PATHNAME;
+
+/* Handle to dynamically loaded library. */
+static void *g_AlsaLib = NULL;
+
+#ifdef PA_ALSA_DYNAMIC
+
+#define _PA_LOCAL_IMPL(x) __pa_local_##x
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_set_rate_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+    int ret;
+
+    if(( ret = alsa_snd_pcm_hw_params_set_rate(pcm, params, (*val), (*dir)) ) < 0 )
+        return ret;
+
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_set_buffer_size_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+{
+    int ret;
+
+    if(( ret = alsa_snd_pcm_hw_params_set_buffer_size(pcm, params, (*val)) ) < 0 )
+        return ret;
+
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_set_period_size_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+{
+    int ret;
+
+    if(( ret = alsa_snd_pcm_hw_params_set_period_size(pcm, params, (*val), (*dir)) ) < 0 )
+        return ret;
+
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_channels_min) (const snd_pcm_hw_params_t *params, unsigned int *val)
+{
+    (*val) = 1;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_channels_max) (const snd_pcm_hw_params_t *params, unsigned int *val)
+{
+    (*val) = 2;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_periods_min) (const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+    (*val) = 2;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_periods_max) (const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+    (*val) = 8;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_period_size_min) (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir)
+{
+    (*frames) = 64;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_period_size_max) (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir)
+{
+    (*frames) = 512;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_buffer_size_max) (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+{
+    int ret;
+    int dir                = 0;
+    snd_pcm_uframes_t pmax = 0;
+    unsigned int      pcnt = 0;
+
+    if(( ret = _PA_LOCAL_IMPL(snd_pcm_hw_params_get_period_size_max)(params, &pmax, &dir) ) < 0 )
+        return ret;
+    if(( ret = _PA_LOCAL_IMPL(snd_pcm_hw_params_get_periods_max)(params, &pcnt, &dir) ) < 0 )
+        return ret;
+
+    (*val) = pmax * pcnt;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_rate_min) (const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+    (*val) = 44100;
+    return 0;
+}
+
+int _PA_LOCAL_IMPL(snd_pcm_hw_params_get_rate_max) (const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+    (*val) = 44100;
+    return 0;
+}
+
+#endif // PA_ALSA_DYNAMIC
+
+/* Trying to load Alsa library dynamically if 'PA_ALSA_DYNAMIC' is defined, othervise
+   will link during compilation.
+*/
+static int PaAlsa_LoadLibrary()
+{
+#ifdef PA_ALSA_DYNAMIC
+
+    PA_DEBUG(( "%s: loading ALSA library file - %s\n", __FUNCTION__, g_AlsaLibName ));
+
+    dlerror();
+    g_AlsaLib = dlopen(g_AlsaLibName, (RTLD_NOW|RTLD_GLOBAL) );
+    if (g_AlsaLib == NULL)
+    {
+        PA_DEBUG(( "%s: failed dlopen() ALSA library file - %s, error: %s\n", __FUNCTION__, g_AlsaLibName, dlerror() ));
+        return 0;
+    }
+
+    PA_DEBUG(( "%s: loading ALSA API\n", __FUNCTION__ ));
+
+    #define _PA_LOAD_FUNC(x) do {             \
+        alsa_##x = dlsym( g_AlsaLib, #x );      \
+        if( alsa_##x == NULL ) {               \
+            PA_DEBUG(( "%s: symbol [%s] not found in - %s, error: %s\n", __FUNCTION__, #x, g_AlsaLibName, dlerror() )); }\
+        } while(0)
+
+#else
+
+    #define _PA_LOAD_FUNC(x) alsa_##x = &x
+
+#endif
+
+    _PA_LOAD_FUNC(snd_pcm_open);
+    _PA_LOAD_FUNC(snd_pcm_close);
+    _PA_LOAD_FUNC(snd_pcm_nonblock);
+    _PA_LOAD_FUNC(snd_pcm_frames_to_bytes);
+    _PA_LOAD_FUNC(snd_pcm_prepare);
+    _PA_LOAD_FUNC(snd_pcm_start);
+    _PA_LOAD_FUNC(snd_pcm_resume);
+    _PA_LOAD_FUNC(snd_pcm_wait);
+    _PA_LOAD_FUNC(snd_pcm_state);
+    _PA_LOAD_FUNC(snd_pcm_avail_update);
+    _PA_LOAD_FUNC(snd_pcm_areas_silence);
+    _PA_LOAD_FUNC(snd_pcm_mmap_begin);
+    _PA_LOAD_FUNC(snd_pcm_mmap_commit);
+    _PA_LOAD_FUNC(snd_pcm_readi);
+    _PA_LOAD_FUNC(snd_pcm_readn);
+    _PA_LOAD_FUNC(snd_pcm_writei);
+    _PA_LOAD_FUNC(snd_pcm_writen);
+    _PA_LOAD_FUNC(snd_pcm_drain);
+    _PA_LOAD_FUNC(snd_pcm_recover);
+    _PA_LOAD_FUNC(snd_pcm_drop);
+    _PA_LOAD_FUNC(snd_pcm_area_copy);
+    _PA_LOAD_FUNC(snd_pcm_poll_descriptors);
+    _PA_LOAD_FUNC(snd_pcm_poll_descriptors_count);
+    _PA_LOAD_FUNC(snd_pcm_poll_descriptors_revents);
+    _PA_LOAD_FUNC(snd_pcm_format_size);
+    _PA_LOAD_FUNC(snd_pcm_link);
+    _PA_LOAD_FUNC(snd_pcm_delay);
+
+    _PA_LOAD_FUNC(snd_pcm_hw_params_sizeof);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_malloc);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_free);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_any);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_access);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_format);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_channels);
+//    _PA_LOAD_FUNC(snd_pcm_hw_params_set_periods_near);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_rate_near);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_rate);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_rate_resample);
+//    _PA_LOAD_FUNC(snd_pcm_hw_params_set_buffer_time_near);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_buffer_size);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_buffer_size_near);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_buffer_size_min);
+//    _PA_LOAD_FUNC(snd_pcm_hw_params_set_period_time_near);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_period_size_near);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_periods_integer);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_periods_min);
+
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_buffer_size);
+//    _PA_LOAD_FUNC(snd_pcm_hw_params_get_period_size);
+//    _PA_LOAD_FUNC(snd_pcm_hw_params_get_access);
+//    _PA_LOAD_FUNC(snd_pcm_hw_params_get_periods);
+//    _PA_LOAD_FUNC(snd_pcm_hw_params_get_rate);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_channels_min);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_channels_max);
+
+    _PA_LOAD_FUNC(snd_pcm_hw_params_test_period_size);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_test_format);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_test_access);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_dump);
+    _PA_LOAD_FUNC(snd_pcm_hw_params);
+
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_periods_min);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_periods_max);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_set_period_size);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_period_size_min);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_period_size_max);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_buffer_size_max);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_rate_min);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_rate_max);
+    _PA_LOAD_FUNC(snd_pcm_hw_params_get_rate_numden);
+
+    _PA_LOAD_FUNC(snd_pcm_sw_params_sizeof);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_malloc);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_current);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_set_avail_min);
+    _PA_LOAD_FUNC(snd_pcm_sw_params);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_free);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_set_start_threshold);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_set_stop_threshold);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_get_boundary);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_set_silence_threshold);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_set_silence_size);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_set_xfer_align);
+    _PA_LOAD_FUNC(snd_pcm_sw_params_set_tstamp_mode);
+
+    _PA_LOAD_FUNC(snd_pcm_info);
+    _PA_LOAD_FUNC(snd_pcm_info_sizeof);
+    _PA_LOAD_FUNC(snd_pcm_info_malloc);
+    _PA_LOAD_FUNC(snd_pcm_info_free);
+    _PA_LOAD_FUNC(snd_pcm_info_set_device);
+    _PA_LOAD_FUNC(snd_pcm_info_set_subdevice);
+    _PA_LOAD_FUNC(snd_pcm_info_set_stream);
+    _PA_LOAD_FUNC(snd_pcm_info_get_name);
+    _PA_LOAD_FUNC(snd_pcm_info_get_card);
+
+    _PA_LOAD_FUNC(snd_ctl_pcm_next_device);
+    _PA_LOAD_FUNC(snd_ctl_pcm_info);
+    _PA_LOAD_FUNC(snd_ctl_open);
+    _PA_LOAD_FUNC(snd_ctl_close);
+    _PA_LOAD_FUNC(snd_ctl_card_info_malloc);
+    _PA_LOAD_FUNC(snd_ctl_card_info_free);
+    _PA_LOAD_FUNC(snd_ctl_card_info);
+    _PA_LOAD_FUNC(snd_ctl_card_info_sizeof);
+    _PA_LOAD_FUNC(snd_ctl_card_info_get_name);
+
+    _PA_LOAD_FUNC(snd_config);
+    _PA_LOAD_FUNC(snd_config_update);
+    _PA_LOAD_FUNC(snd_config_search);
+    _PA_LOAD_FUNC(snd_config_iterator_entry);
+    _PA_LOAD_FUNC(snd_config_iterator_first);
+    _PA_LOAD_FUNC(snd_config_iterator_end);
+    _PA_LOAD_FUNC(snd_config_iterator_next);
+    _PA_LOAD_FUNC(snd_config_get_string);
+    _PA_LOAD_FUNC(snd_config_get_id);
+    _PA_LOAD_FUNC(snd_config_update_free_global);
+
+    _PA_LOAD_FUNC(snd_pcm_status);
+    _PA_LOAD_FUNC(snd_pcm_status_sizeof);
+    _PA_LOAD_FUNC(snd_pcm_status_get_tstamp);
+    _PA_LOAD_FUNC(snd_pcm_status_get_state);
+    _PA_LOAD_FUNC(snd_pcm_status_get_trigger_tstamp);
+    _PA_LOAD_FUNC(snd_pcm_status_get_delay);
+
+    _PA_LOAD_FUNC(snd_card_next);
+    _PA_LOAD_FUNC(snd_asoundlib_version);
+    _PA_LOAD_FUNC(snd_strerror);
+    _PA_LOAD_FUNC(snd_output_stdio_attach);
+#undef _PA_LOAD_FUNC
+
+#ifdef PA_ALSA_DYNAMIC
+    PA_DEBUG(( "%s: loaded ALSA API - ok\n", __FUNCTION__ ));
+
+#define _PA_VALIDATE_LOAD_REPLACEMENT(x)\
+    do {\
+        if( alsa_##x == NULL )\
+        {\
+            alsa_##x = &_PA_LOCAL_IMPL(x);\
+            PA_DEBUG(( "%s: replacing [%s] with local implementation\n", __FUNCTION__, #x ));\
+        }\
+    } while (0)
+
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_set_rate_near);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_set_buffer_size_near);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_set_period_size_near);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_channels_min);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_channels_max);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_periods_min);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_periods_max);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_period_size_min);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_period_size_max);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_buffer_size_max);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_rate_min);
+    _PA_VALIDATE_LOAD_REPLACEMENT(snd_pcm_hw_params_get_rate_max);
+
+#undef _PA_LOCAL_IMPL
+#undef _PA_VALIDATE_LOAD_REPLACEMENT
+
+#endif // PA_ALSA_DYNAMIC
+
+    return 1;
+}
+
+void PaAlsa_SetLibraryPathName( const char *pathName )
+{
+#ifdef PA_ALSA_DYNAMIC
+    g_AlsaLibName = pathName;
+#else
+    (void)pathName;
+#endif
+}
+
+/* Close handle to Alsa library. */
+static void PaAlsa_CloseLibrary()
+{
+#ifdef PA_ALSA_DYNAMIC
+    dlclose(g_AlsaLib);
+    g_AlsaLib = NULL;
+#endif
+}
+
 /* Check return value of ALSA function, and map it to PaError */
 #define ENSURE_(expr, code) \
     do { \
-        if( UNLIKELY( (aErr_ = (expr)) < 0 ) ) \
+        int __pa_unsure_error_id;\
+        if( UNLIKELY( (__pa_unsure_error_id = (expr)) < 0 ) ) \
         { \
             /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
             if( (code) == paUnanticipatedHostError && pthread_equal( pthread_self(), paUnixMainThread) ) \
             { \
-                PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \
+                PaUtil_SetLastHostErrorInfo( paALSA, __pa_unsure_error_id, alsa_snd_strerror( __pa_unsure_error_id ) ); \
             } \
             PaUtil_DebugPrint( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" ); \
             if( (code) == paUnanticipatedHostError ) \
-                PA_DEBUG(( "Host error description: %s\n", snd_strerror( aErr_ ) )); \
+                PA_DEBUG(( "Host error description: %s\n", alsa_snd_strerror( __pa_unsure_error_id ) )); \
             result = (code); \
             goto error; \
         } \
-    } while( 0 );
+    } while (0)
 
 #define ASSERT_CALL_(expr, success) \
-    aErr_ = (expr); \
-    assert( success == aErr_ );
+    do {\
+        int __pa_assert_error_id;\
+        __pa_assert_error_id = (expr);\
+        assert( success == __pa_assert_error_id );\
+    } while (0)
 
-static int aErr_;               /* Used with ENSURE_ */
 static int numPeriods_ = 4;
+static int busyRetries_ = 100;
 
 int PaAlsa_SetNumPeriods( int numPeriods )
 {
@@ -116,13 +603,17 @@ typedef enum
 typedef struct
 {
     PaSampleFormat hostSampleFormat;
-    unsigned long framesPerBuffer;
     int numUserChannels, numHostChannels;
     int userInterleaved, hostInterleaved;
+    int canMmap;
+    void *nonMmapBuffer;
+    unsigned int nonMmapBufferSize;
     PaDeviceIndex device;     /* Keep the device index */
+    int deviceIsPlug; /* Distinguish plug types from direct 'hw:' devices */
+    int useReventFix; /* Alsa older than 1.0.16, plug devices need a fix */
 
     snd_pcm_t *pcm;
-    snd_pcm_uframes_t bufferSize;
+    snd_pcm_uframes_t framesPerPeriod, alsaBufferSize;
     snd_pcm_format_t nativeFormat;
     unsigned int nfds;
     int ready;  /* Marked ready from poll */
@@ -145,7 +636,7 @@ typedef struct PaAlsaStream
 
     int primeBuffers;
     int callbackMode;              /* bool: are we running in callback mode? */
-    int pcmsSynced;	            /* Have we successfully synced pcms */
+    int pcmsSynced;                /* Have we successfully synced pcms */
     int rtSched;
 
     /* the callback thread uses these to poll the sound device(s), waiting
@@ -179,6 +670,7 @@ typedef struct PaAlsaHostApiRepresentation
     PaUtilAllocationGroup *allocations;
 
     PaHostApiIndex hostApiIndex;
+    PaUint32 alsaLibVersion; /* Retrieved from the library at run-time */
 }
 PaAlsaHostApiRepresentation;
 
@@ -219,6 +711,7 @@ static double GetStreamCpuLoad( PaStream* stream );
 static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi );
 static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate );
 static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate );
+static PaUint32 PaAlsaVersionNum(void);
 
 /* Callback prototypes */
 static void *CallbackThreadFunc( void *userData );
@@ -235,19 +728,27 @@ static const PaAlsaDeviceInfo *GetDeviceInfo( const PaUtilHostApiRepresentation
     return (const PaAlsaDeviceInfo *)hostApi->deviceInfos[device];
 }
 
-static void AlsaErrorHandler(const char *file, int line, const char *function, int err, const char *fmt, ...)
+/** Uncommented because AlsaErrorHandler is unused for anything good yet. If AlsaErrorHandler is
+    to be used, do not forget to register this callback in PaAlsa_Initialize, and unregister in Terminate.
+*/
+/*static void AlsaErrorHandler(const char *file, int line, const char *function, int err, const char *fmt, ...)
 {
-}
+}*/
 
 PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
 {
     PaError result = paNoError;
     PaAlsaHostApiRepresentation *alsaHostApi = NULL;
 
+    /* Try loading Alsa library. */
+    if (!PaAlsa_LoadLibrary())
+        return paHostApiNotFound;
+
     PA_UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory(
                 sizeof(PaAlsaHostApiRepresentation) ), paInsufficientMemory );
     PA_UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
     alsaHostApi->hostApiIndex = hostApiIndex;
+    alsaHostApi->alsaLibVersion = PaAlsaVersionNum();
 
     *hostApi = (PaUtilHostApiRepresentation*)alsaHostApi;
     (*hostApi)->info.structVersion = 1;
@@ -258,7 +759,10 @@ PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
     (*hostApi)->OpenStream = OpenStream;
     (*hostApi)->IsFormatSupported = IsFormatSupported;
 
-    ENSURE_( snd_lib_error_set_handler(AlsaErrorHandler), paUnanticipatedHostError );
+    /** If AlsaErrorHandler is to be used, do not forget to unregister callback pointer in
+        Terminate function.
+    */
+    /*ENSURE_( snd_lib_error_set_handler(AlsaErrorHandler), paUnanticipatedHostError );*/
 
     PA_ENSURE( BuildDeviceList( alsaHostApi ) );
 
@@ -305,6 +809,10 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
 
     assert( hostApi );
 
+    /** See AlsaErrorHandler and PaAlsa_Initialize for details.
+    */
+    /*snd_lib_error_set_handler(NULL);*/
+
     if( alsaHostApi->allocations )
     {
         PaUtil_FreeAllAllocations( alsaHostApi->allocations );
@@ -312,29 +820,35 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
     }
 
     PaUtil_FreeMemory( alsaHostApi );
-    snd_config_update_free_global();
+    alsa_snd_config_update_free_global();
+
+    /* Close Alsa library. */
+    PaAlsa_CloseLibrary();
 }
 
 /** Determine max channels and default latencies.
  *
- * This function provides functionality to grope an opened (might be opened for capture or playback) pcm device for 
+ * This function provides functionality to grope an opened (might be opened for capture or playback) pcm device for
  * traits like max channels, suitable default latencies and default sample rate. Upon error, max channels is set to zero,
  * and a suitable result returned. The device is closed before returning.
  */
 static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, int openBlocking,
-        PaAlsaDeviceInfo* devInfo, int* canMmap )
+        PaAlsaDeviceInfo* devInfo )
 {
     PaError result = paNoError;
     snd_pcm_hw_params_t *hwParams;
-    snd_pcm_uframes_t lowLatency = 512, highLatency = 2048;
+    snd_pcm_uframes_t alsaBufferFrames, alsaPeriodFrames;
     unsigned int minChans, maxChans;
     int* minChannels, * maxChannels;
     double * defaultLowLatency, * defaultHighLatency, * defaultSampleRate =
         &devInfo->baseDeviceInfo.defaultSampleRate;
     double defaultSr = *defaultSampleRate;
+    int dir;
 
     assert( pcm );
 
+    PA_DEBUG(( "%s: collecting info ..\n", __FUNCTION__ ));
+
     if( StreamDirection_In == mode )
     {
         minChannels = &devInfo->minInputChannels;
@@ -350,13 +864,10 @@ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, in
         defaultHighLatency = &devInfo->baseDeviceInfo.defaultHighOutputLatency;
     }
 
-    ENSURE_( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError );
-
-    snd_pcm_hw_params_alloca( &hwParams );
-    snd_pcm_hw_params_any( pcm, hwParams );
+    ENSURE_( alsa_snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError );
 
-    *canMmap = snd_pcm_hw_params_test_access( pcm, hwParams, SND_PCM_ACCESS_MMAP_INTERLEAVED ) >= 0 ||
-            snd_pcm_hw_params_test_access( pcm, hwParams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED ) >= 0;
+    alsa_snd_pcm_hw_params_alloca( &hwParams );
+    alsa_snd_pcm_hw_params_any( pcm, hwParams );
 
     if( defaultSr >= 0 )
     {
@@ -365,6 +876,7 @@ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, in
         if( SetApproximateSampleRate( pcm, hwParams, defaultSr ) < 0 )
         {
             defaultSr = -1.;
+            alsa_snd_pcm_hw_params_any( pcm, hwParams ); /* Clear any params (rate) that might have been set */
             PA_DEBUG(( "%s: Original default samplerate failed, trying again ..\n", __FUNCTION__ ));
         }
     }
@@ -372,16 +884,19 @@ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, in
     if( defaultSr < 0. )           /* Default sample rate not set */
     {
         unsigned int sampleRate = 44100;        /* Will contain approximate rate returned by alsa-lib */
-        if( snd_pcm_hw_params_set_rate_near( pcm, hwParams, &sampleRate, NULL ) < 0)
+
+        /* Don't allow rate resampling when probing for the default rate (but ignore if this call fails) */
+        alsa_snd_pcm_hw_params_set_rate_resample( pcm, hwParams, 0 );
+        if( alsa_snd_pcm_hw_params_set_rate_near( pcm, hwParams, &sampleRate, NULL ) < 0 )
         {
             result = paUnanticipatedHostError;
             goto error;
         }
         ENSURE_( GetExactSampleRate( hwParams, &defaultSr ), paUnanticipatedHostError );
     }
-    
-    ENSURE_( snd_pcm_hw_params_get_channels_min( hwParams, &minChans ), paUnanticipatedHostError );
-    ENSURE_( snd_pcm_hw_params_get_channels_max( hwParams, &maxChans ), paUnanticipatedHostError );
+
+    ENSURE_( alsa_snd_pcm_hw_params_get_channels_min( hwParams, &minChans ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_hw_params_get_channels_max( hwParams, &maxChans ), paUnanticipatedHostError );
     assert( maxChans <= INT_MAX );
     assert( maxChans > 0 );    /* Weird linking issue could cause wrong version of ALSA symbols to be called,
                                    resulting in zeroed values */
@@ -394,38 +909,37 @@ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, in
     }
 
     /* TWEAKME:
-     *
-     * Giving values for default min and max latency is not
-     * straightforward.  Here are our objectives:
-     *
-     *         * for low latency, we want to give the lowest value
-     *         that will work reliably.  This varies based on the
-     *         sound card, kernel, CPU, etc.  I think it is better
-     *         to give sub-optimal latency than to give a number
-     *         too low and cause dropouts.  My conservative
-     *         estimate at this point is to base it on 4096-sample
-     *         latency at 44.1 kHz, which gives a latency of 23ms.
-     *         * for high latency we want to give a large enough
-     *         value that dropouts are basically impossible.  This
-     *         doesn't really require as much tweaking, since
-     *         providing too large a number will just cause us to
-     *         select the nearest setting that will work at stream
-     *         config time.
+     * Giving values for default min and max latency is not straightforward.
+     *  * for low latency, we want to give the lowest value that will work reliably.
+     *      This varies based on the sound card, kernel, CPU, etc.  Better to give
+     *      sub-optimal latency than to give a number too low and cause dropouts.
+     *  * for high latency we want to give a large enough value that dropouts are basically impossible.
+     *      This doesn't really require as much tweaking, since providing too large a number will
+     *      just cause us to select the nearest setting that will work at stream config time.
      */
-    ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency ), paUnanticipatedHostError );
-
-    /* Have to reset hwParams, to set new buffer size */
-    ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); 
-    ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &highLatency ), paUnanticipatedHostError );
+    /* Try low latency values, (sometimes the buffer & period that result are larger) */
+    alsaBufferFrames = 512;
+    alsaPeriodFrames = 128;
+    ENSURE_( alsa_snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &alsaBufferFrames ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir ), paUnanticipatedHostError );
+    *defaultLowLatency = (double) (alsaBufferFrames - alsaPeriodFrames) / defaultSr;
+
+    /* Base the high latency case on values four times larger */
+    alsaBufferFrames = 2048;
+    alsaPeriodFrames = 512;
+    /* Have to reset hwParams, to set new buffer size; need to also set sample rate again */
+    ENSURE_( alsa_snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError );
+    ENSURE_( SetApproximateSampleRate( pcm, hwParams, defaultSr ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &alsaBufferFrames ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir ), paUnanticipatedHostError );
+    *defaultHighLatency = (double) (alsaBufferFrames - alsaPeriodFrames) / defaultSr;
 
     *minChannels = (int)minChans;
     *maxChannels = (int)maxChans;
     *defaultSampleRate = defaultSr;
-    *defaultLowLatency = (double) lowLatency / *defaultSampleRate;
-    *defaultHighLatency = (double) highLatency / *defaultSampleRate;
 
 end:
-    snd_pcm_close( pcm );
+    alsa_snd_pcm_close( pcm );
     return result;
 
 error:
@@ -433,7 +947,7 @@ error:
 }
 
 /* Initialize device info with invalid values (maxInputChannels and maxOutputChannels are set to zero since these indicate
- * wether input/output is available) */
+ * whether input/output is available) */
 static void InitializeDeviceInfo( PaDeviceInfo *deviceInfo )
 {
     deviceInfo->structVersion = -1;
@@ -448,6 +962,24 @@ static void InitializeDeviceInfo( PaDeviceInfo *deviceInfo )
     deviceInfo->defaultSampleRate = -1.;
 }
 
+
+/* Retrieve the version of the runtime Alsa-lib, as a single number equivalent to
+ * SND_LIB_VERSION.  Only a version string is available ("a.b.c") so this has to be converted.
+ * Assume 'a' and 'b' are single digits only.
+ */
+static PaUint32 PaAlsaVersionNum(void)
+{
+    char* verStr;
+    PaUint32 verNum;
+
+    verStr = (char*) alsa_snd_asoundlib_version();
+    verNum = ALSA_VERSION_INT( atoi(verStr), atoi(verStr + 2), atoi(verStr + 4) );
+    PA_DEBUG(( "ALSA version (build): " SND_LIB_VERSION_STR "\nALSA version (runtime): %s\n", verStr ));
+
+    return verNum;
+}
+
+
 /* Helper struct */
 typedef struct
 {
@@ -461,10 +993,10 @@ typedef struct
 
 HwDevInfo predefinedNames[] = {
     { "center_lfe", NULL, 0, 1, 0 },
-/* { "default", NULL, 0, 1, 0 }, */
-/* { "dmix", NULL, 0, 1, 0 }, */
+/* { "default", NULL, 0, 1, 1 }, */
+    { "dmix", NULL, 0, 1, 0 },
 /* { "dpl", NULL, 0, 1, 0 }, */
-/* { "dsnoop", NULL, 0, 1, 0 }, */
+/* { "dsnoop", NULL, 0, 0, 1 }, */
     { "front", NULL, 0, 1, 0 },
     { "iec958", NULL, 0, 1, 0 },
 /* { "modem", NULL, 0, 1, 0 }, */
@@ -476,6 +1008,23 @@ HwDevInfo predefinedNames[] = {
     { "surround50", NULL, 0, 1, 0 },
     { "surround51", NULL, 0, 1, 0 },
     { "surround71", NULL, 0, 1, 0 },
+
+    { "AndroidPlayback_Earpiece_normal",         NULL, 0, 1, 0 },
+    { "AndroidPlayback_Speaker_normal",          NULL, 0, 1, 0 },
+    { "AndroidPlayback_Bluetooth_normal",        NULL, 0, 1, 0 },
+    { "AndroidPlayback_Headset_normal",          NULL, 0, 1, 0 },
+    { "AndroidPlayback_Speaker_Headset_normal",  NULL, 0, 1, 0 },
+    { "AndroidPlayback_Bluetooth-A2DP_normal",   NULL, 0, 1, 0 },
+    { "AndroidPlayback_ExtraDockSpeaker_normal", NULL, 0, 1, 0 },
+    { "AndroidPlayback_TvOut_normal",            NULL, 0, 1, 0 },
+
+    { "AndroidRecord_Microphone",                NULL, 0, 0, 1 },
+    { "AndroidRecord_Earpiece_normal",           NULL, 0, 0, 1 },
+    { "AndroidRecord_Speaker_normal",            NULL, 0, 0, 1 },
+    { "AndroidRecord_Headset_normal",            NULL, 0, 0, 1 },
+    { "AndroidRecord_Bluetooth_normal",          NULL, 0, 0, 1 },
+    { "AndroidRecord_Speaker_Headset_normal",    NULL, 0, 0, 1 },
+
     { NULL, NULL, 0, 1, 0 }
 };
 
@@ -485,10 +1034,10 @@ static const HwDevInfo *FindDeviceName( const char *name )
 
     for( i = 0; predefinedNames[i].alsaName; i++ )
     {
-	if( strcmp( name, predefinedNames[i].alsaName ) == 0 )
-	{
-	    return &predefinedNames[i];
-	}
+        if( strcmp( name, predefinedNames[i].alsaName ) == 0 )
+        {
+            return &predefinedNames[i];
+        }
     }
 
     return NULL;
@@ -530,9 +1079,41 @@ static int IgnorePlugin( const char *pluginId )
     return 0;
 }
 
+/* Skip past parts at the beginning of a (pcm) info name that are already in the card name, to avoid duplication */
+static char *SkipCardDetailsInName( char *infoSkipName, char *cardRefName )
+{
+    char *lastSpacePosn = infoSkipName;
+
+    /* Skip matching chars; but only in chunks separated by ' ' (not part words etc), so track lastSpacePosn */
+    while( *cardRefName )
+    {
+        while( *infoSkipName && *cardRefName && *infoSkipName == *cardRefName)
+        {
+            infoSkipName++;
+            cardRefName++;
+            if( *infoSkipName == ' ' || *infoSkipName == '\0' )
+                lastSpacePosn = infoSkipName;
+        }
+        infoSkipName = lastSpacePosn;
+        /* Look for another chunk; post-increment means ends pointing to next char */
+        while( *cardRefName && ( *cardRefName++ != ' ' ));
+    }
+    if( *infoSkipName == '\0' )
+        return "-"; /* The 2 names were identical; instead of a nul-string, return a marker string */
+
+    /* Now want to move to the first char after any spaces */
+    while( *lastSpacePosn && *lastSpacePosn == ' ' )
+        lastSpacePosn++;
+    /* Skip a single separator char if present in the remaining pcm name; (pa will add its own) */
+    if(( *lastSpacePosn == '-' || *lastSpacePosn == ':' ) && *(lastSpacePosn + 1) == ' ' )
+        lastSpacePosn += 2;
+
+    return lastSpacePosn;
+}
+
 /** Open PCM device.
  *
- * Wrapper around snd_pcm_open which may repeatedly retry opening a device if it is busy, for
+ * Wrapper around alsa_snd_pcm_open which may repeatedly retry opening a device if it is busy, for
  * a certain time. This is because dmix may temporarily hold on to a device after it (dmix)
  * has been opened and closed.
  * @param mode: Open mode (e.g., SND_PCM_BLOCKING).
@@ -540,81 +1121,77 @@ static int IgnorePlugin( const char *pluginId )
  **/
 static int OpenPcm( snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode, int waitOnBusy )
 {
-    int tries = 0, maxTries = waitOnBusy ? 100 : 0;
-    int ret = snd_pcm_open( pcmp, name, stream, mode );
+    int ret, tries = 0, maxTries = waitOnBusy ? busyRetries_ : 0;
+
+    ret = alsa_snd_pcm_open( pcmp, name, stream, mode );
+
     for( tries = 0; tries < maxTries && -EBUSY == ret; ++tries )
     {
         Pa_Sleep( 10 );
-        ret = snd_pcm_open( pcmp, name, stream, mode );
+        ret = alsa_snd_pcm_open( pcmp, name, stream, mode );
         if( -EBUSY != ret )
         {
-            PA_DEBUG(( "%s: Successfully opened initially busy device after %d tries\n",
-                        __FUNCTION__, tries ));
+            PA_DEBUG(( "%s: Successfully opened initially busy device after %d tries\n", __FUNCTION__, tries ));
         }
     }
     if( -EBUSY == ret )
     {
-        PA_DEBUG(( "%s: Failed to open busy device '%s'\n",
-                    __FUNCTION__, name ));
+        PA_DEBUG(( "%s: Failed to open busy device '%s'\n", __FUNCTION__, name ));
+    }
+    else
+    {
+        if( ret < 0 )
+            PA_DEBUG(( "%s: Opened device '%s' ptr[%p] - result: [%d:%s]\n", __FUNCTION__, name, *pcmp, ret, alsa_snd_strerror(ret) ));
     }
 
     return ret;
 }
 
-static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* deviceName, int blocking,
+static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* deviceHwInfo, int blocking,
         PaAlsaDeviceInfo* devInfo, int* devIdx )
 {
     PaError result = 0;
     PaDeviceInfo *baseDeviceInfo = &devInfo->baseDeviceInfo;
-    snd_pcm_t *pcm;
-    int canMmap = -1;
+    snd_pcm_t *pcm = NULL;
     PaUtilHostApiRepresentation *baseApi = &alsaApi->baseHostApiRep;
 
+    PA_DEBUG(( "%s: Filling device info for: %s\n", __FUNCTION__, deviceHwInfo->name ));
+
     /* Zero fields */
     InitializeDeviceInfo( baseDeviceInfo );
 
-    /* to determine device capabilities, we must open the device and query the
+    /* To determine device capabilities, we must open the device and query the
      * hardware parameter configuration space */
 
     /* Query capture */
-    if( deviceName->hasCapture &&
-            OpenPcm( &pcm, deviceName->alsaName, SND_PCM_STREAM_CAPTURE, blocking, 0 )
-            >= 0 )
+    if( deviceHwInfo->hasCapture &&
+        OpenPcm( &pcm, deviceHwInfo->alsaName, SND_PCM_STREAM_CAPTURE, blocking, 0 ) >= 0 )
     {
-        if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_In, blocking, devInfo,
-                    &canMmap ) != paNoError )
+        if( GropeDevice( pcm, deviceHwInfo->isPlug, StreamDirection_In, blocking, devInfo ) != paNoError )
         {
             /* Error */
-            PA_DEBUG(("%s: Failed groping %s for capture\n", __FUNCTION__, deviceName->alsaName));
+            PA_DEBUG(( "%s: Failed groping %s for capture\n", __FUNCTION__, deviceHwInfo->alsaName ));
             goto end;
         }
     }
 
     /* Query playback */
-    if( deviceName->hasPlayback &&
-            OpenPcm( &pcm, deviceName->alsaName, SND_PCM_STREAM_PLAYBACK, blocking, 0 )
-            >= 0 )
+    if( deviceHwInfo->hasPlayback &&
+        OpenPcm( &pcm, deviceHwInfo->alsaName, SND_PCM_STREAM_PLAYBACK, blocking, 0 ) >= 0 )
     {
-        if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_Out, blocking, devInfo,
-                    &canMmap ) != paNoError )
+        if( GropeDevice( pcm, deviceHwInfo->isPlug, StreamDirection_Out, blocking, devInfo ) != paNoError )
         {
             /* Error */
-            PA_DEBUG(("%s: Failed groping %s for playback\n", __FUNCTION__, deviceName->alsaName));
+            PA_DEBUG(( "%s: Failed groping %s for playback\n", __FUNCTION__, deviceHwInfo->alsaName ));
             goto end;
         }
     }
 
-    if( 0 == canMmap )
-    {
-        PA_DEBUG(("%s: Device %s doesn't support mmap\n", __FUNCTION__, deviceName->alsaName));
-        goto end;
-    }
-
     baseDeviceInfo->structVersion = 2;
     baseDeviceInfo->hostApi = alsaApi->hostApiIndex;
-    baseDeviceInfo->name = deviceName->name;
-    devInfo->alsaName = deviceName->alsaName;
-    devInfo->isPlug = deviceName->isPlug;
+    baseDeviceInfo->name = deviceHwInfo->name;
+    devInfo->alsaName = deviceHwInfo->alsaName;
+    devInfo->isPlug = deviceHwInfo->isPlug;
 
     /* A: Storing pointer to PaAlsaDeviceInfo object as pointer to PaDeviceInfo object.
      * Should now be safe to add device info, unless the device supports neither capture nor playback
@@ -622,22 +1199,26 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d
     if( baseDeviceInfo->maxInputChannels > 0 || baseDeviceInfo->maxOutputChannels > 0 )
     {
         /* Make device default if there isn't already one or it is the ALSA "default" device */
-        if( (baseApi->info.defaultInputDevice == paNoDevice || !strcmp(deviceName->alsaName,
-                        "default" )) && baseDeviceInfo->maxInputChannels > 0 )
+        if( ( baseApi->info.defaultInputDevice == paNoDevice ||
+            !strcmp( deviceHwInfo->alsaName, "default" ) ) && baseDeviceInfo->maxInputChannels > 0 )
         {
             baseApi->info.defaultInputDevice = *devIdx;
-            PA_DEBUG(("Default input device: %s\n", deviceName->name));
+            PA_DEBUG(( "Default input device: %s\n", deviceHwInfo->name ));
         }
-        if( (baseApi->info.defaultOutputDevice == paNoDevice || !strcmp(deviceName->alsaName,
-                        "default" )) && baseDeviceInfo->maxOutputChannels > 0 )
+        if( ( baseApi->info.defaultOutputDevice == paNoDevice ||
+            !strcmp( deviceHwInfo->alsaName, "default" ) ) && baseDeviceInfo->maxOutputChannels > 0 )
         {
             baseApi->info.defaultOutputDevice = *devIdx;
-            PA_DEBUG(("Default output device: %s\n", deviceName->name));
+            PA_DEBUG(( "Default output device: %s\n", deviceHwInfo->name ));
         }
-        PA_DEBUG(("%s: Adding device %s: %d\n", __FUNCTION__, deviceName->name, *devIdx));
+        PA_DEBUG(( "%s: Adding device %s: %d\n", __FUNCTION__, deviceHwInfo->name, *devIdx ));
         baseApi->deviceInfos[*devIdx] = (PaDeviceInfo *) devInfo;
         (*devIdx) += 1;
     }
+    else
+    {
+        PA_DEBUG(( "%s: Skipped device: %s, all channels == 0\n", __FUNCTION__, deviceHwInfo->name ));
+    }
 
 end:
     return result;
@@ -657,6 +1238,8 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
     snd_pcm_info_t *pcmInfo;
     int res;
     int blocking = SND_PCM_NONBLOCK;
+    int usePlughw = 0;
+    char *hwPrefix = "";
     char alsaCardName[50];
 #ifdef PA_ENABLE_DEBUG_OUTPUT
     PaTime startTime = PaUtil_GetTime();
@@ -665,22 +1248,30 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
     if( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) && atoi( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) ) )
         blocking = 0;
 
+    /* If PA_ALSA_PLUGHW is 1 (non-zero), use the plughw: pcm throughout instead of hw: */
+    if( getenv( "PA_ALSA_PLUGHW" ) && atoi( getenv( "PA_ALSA_PLUGHW" ) ) )
+    {
+        usePlughw = 1;
+        hwPrefix = "plug";
+        PA_DEBUG(( "%s: Using Plughw\n", __FUNCTION__ ));
+    }
+
     /* These two will be set to the first working input and output device, respectively */
     baseApi->info.defaultInputDevice = paNoDevice;
     baseApi->info.defaultOutputDevice = paNoDevice;
 
     /* Gather info about hw devices
 
-     * snd_card_next() modifies the integer passed to it to be:
+     * alsa_snd_card_next() modifies the integer passed to it to be:
      *      the index of the first card if the parameter is -1
      *      the index of the next card if the parameter is the index of a card
      *      -1 if there are no more cards
      *
      * The function itself returns 0 if it succeeded. */
     cardIdx = -1;
-    snd_ctl_card_info_alloca( &cardInfo );
-    snd_pcm_info_alloca( &pcmInfo );
-    while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 )
+    alsa_snd_ctl_card_info_alloca( &cardInfo );
+    alsa_snd_pcm_info_alloca( &pcmInfo );
+    while( alsa_snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 )
     {
         char *cardName;
         int devIdx = -1;
@@ -690,34 +1281,35 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
         snprintf( alsaCardName, sizeof (alsaCardName), "hw:%d", cardIdx );
 
         /* Acquire name of card */
-        if( snd_ctl_open( &ctl, alsaCardName, 0 ) < 0 )
+        if( alsa_snd_ctl_open( &ctl, alsaCardName, 0 ) < 0 )
         {
             /* Unable to open card :( */
             PA_DEBUG(( "%s: Unable to open device %s\n", __FUNCTION__, alsaCardName ));
             continue;
         }
-        snd_ctl_card_info( ctl, cardInfo );
+        alsa_snd_ctl_card_info( ctl, cardInfo );
 
-        PA_ENSURE( PaAlsa_StrDup( alsaApi, &cardName, snd_ctl_card_info_get_name( cardInfo )) );
+        PA_ENSURE( PaAlsa_StrDup( alsaApi, &cardName, alsa_snd_ctl_card_info_get_name( cardInfo )) );
 
-        while( snd_ctl_pcm_next_device( ctl, &devIdx ) == 0 && devIdx >= 0 )
+        while( alsa_snd_ctl_pcm_next_device( ctl, &devIdx ) == 0 && devIdx >= 0 )
         {
-            char *alsaDeviceName, *deviceName;
+            char *alsaDeviceName, *deviceName, *infoName;
             size_t len;
             int hasPlayback = 0, hasCapture = 0;
-            snprintf( buf, sizeof (buf), "hw:%d,%d", cardIdx, devIdx );
+
+            snprintf( buf, sizeof (buf), "%s%s,%d", hwPrefix, alsaCardName, devIdx );
 
             /* Obtain info about this particular device */
-            snd_pcm_info_set_device( pcmInfo, devIdx );
-            snd_pcm_info_set_subdevice( pcmInfo, 0 );
-            snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_CAPTURE );
-            if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 )
+            alsa_snd_pcm_info_set_device( pcmInfo, devIdx );
+            alsa_snd_pcm_info_set_subdevice( pcmInfo, 0 );
+            alsa_snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_CAPTURE );
+            if( alsa_snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 )
             {
                 hasCapture = 1;
             }
-            
-            snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_PLAYBACK );
-            if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 )
+
+            alsa_snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_PLAYBACK );
+            if( alsa_snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 )
             {
                 hasPlayback = 1;
             }
@@ -728,12 +1320,13 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
                 continue;
             }
 
+            infoName = SkipCardDetailsInName( (char *)alsa_snd_pcm_info_get_name( pcmInfo ), cardName );
+
             /* The length of the string written by snprintf plus terminating 0 */
-            len = snprintf( NULL, 0, "%s: %s (%s)", cardName, snd_pcm_info_get_name( pcmInfo ), buf ) + 1;
+            len = snprintf( NULL, 0, "%s: %s (%s)", cardName, infoName, buf ) + 1;
             PA_UNLESS( deviceName = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ),
                     paInsufficientMemory );
-            snprintf( deviceName, len, "%s: %s (%s)", cardName,
-                    snd_pcm_info_get_name( pcmInfo ), buf );
+            snprintf( deviceName, len, "%s: %s (%s)", cardName, infoName, buf );
 
             ++numDeviceNames;
             if( !hwDevInfos || numDeviceNames > maxDeviceNames )
@@ -747,53 +1340,52 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
 
             hwDevInfos[ numDeviceNames - 1 ].alsaName = alsaDeviceName;
             hwDevInfos[ numDeviceNames - 1 ].name = deviceName;
-            hwDevInfos[ numDeviceNames - 1 ].isPlug = 0;
+            hwDevInfos[ numDeviceNames - 1 ].isPlug = usePlughw;
             hwDevInfos[ numDeviceNames - 1 ].hasPlayback = hasPlayback;
             hwDevInfos[ numDeviceNames - 1 ].hasCapture = hasCapture;
         }
-        snd_ctl_close( ctl );
+        alsa_snd_ctl_close( ctl );
     }
 
     /* Iterate over plugin devices */
-
-    if( NULL == snd_config )
+    if( NULL == (*alsa_snd_config) )
     {
-        /* snd_config_update is called implicitly by some functions, if this hasn't happened snd_config will be NULL (bleh) */
-        ENSURE_( snd_config_update(), paUnanticipatedHostError );
+        /* alsa_snd_config_update is called implicitly by some functions, if this hasn't happened snd_config will be NULL (bleh) */
+        ENSURE_( alsa_snd_config_update(), paUnanticipatedHostError );
         PA_DEBUG(( "Updating snd_config\n" ));
     }
-    assert( snd_config );
-    if( (res = snd_config_search( snd_config, "pcm", &topNode )) >= 0 )
+    assert( *alsa_snd_config );
+    if( ( res = alsa_snd_config_search( *alsa_snd_config, "pcm", &topNode ) ) >= 0 )
     {
         snd_config_iterator_t i, next;
 
-        snd_config_for_each( i, next, topNode )
+        alsa_snd_config_for_each( i, next, topNode )
         {
             const char *tpStr = "unknown", *idStr = NULL;
             int err = 0;
 
             char *alsaDeviceName, *deviceName;
-	    const HwDevInfo *predefined = NULL;
-            snd_config_t *n = snd_config_iterator_entry( i ), * tp = NULL;;
+            const HwDevInfo *predefined = NULL;
+            snd_config_t *n = alsa_snd_config_iterator_entry( i ), * tp = NULL;;
 
-            if( (err = snd_config_search( n, "type", &tp )) < 0 )
+            if( (err = alsa_snd_config_search( n, "type", &tp )) < 0 )
             {
                 if( -ENOENT != err )
                 {
                     ENSURE_(err, paUnanticipatedHostError);
                 }
             }
-            else 
+            else
             {
-                ENSURE_( snd_config_get_string( tp, &tpStr ), paUnanticipatedHostError );
+                ENSURE_( alsa_snd_config_get_string( tp, &tpStr ), paUnanticipatedHostError );
             }
-            ENSURE_( snd_config_get_id( n, &idStr ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_config_get_id( n, &idStr ), paUnanticipatedHostError );
             if( IgnorePlugin( idStr ) )
             {
-                PA_DEBUG(( "%s: Ignoring ALSA plugin device %s of type %s\n", __FUNCTION__, idStr, tpStr ));
+                PA_DEBUG(( "%s: Ignoring ALSA plugin device [%s] of type [%s]\n", __FUNCTION__, idStr, tpStr ));
                 continue;
             }
-            PA_DEBUG(( "%s: Found plugin %s of type %s\n", __FUNCTION__, idStr, tpStr ));
+            PA_DEBUG(( "%s: Found plugin [%s] of type [%s]\n", __FUNCTION__, idStr, tpStr ));
 
             PA_UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations,
                                                             strlen(idStr) + 6 ), paInsufficientMemory );
@@ -810,28 +1402,26 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
                         paInsufficientMemory );
             }
 
-	    predefined = FindDeviceName( alsaDeviceName );
+            predefined = FindDeviceName( alsaDeviceName );
 
             hwDevInfos[numDeviceNames - 1].alsaName = alsaDeviceName;
-            hwDevInfos[numDeviceNames - 1].name = deviceName;
-            hwDevInfos[numDeviceNames - 1].isPlug = 1;
-
-	    if( predefined )
-	    {
-		hwDevInfos[numDeviceNames - 1].hasPlayback =
-		    predefined->hasPlayback;
-		hwDevInfos[numDeviceNames - 1].hasCapture =
-		    predefined->hasCapture;
-	    }
-	    else
-	    {
-		hwDevInfos[numDeviceNames - 1].hasPlayback = 1;
-		hwDevInfos[numDeviceNames - 1].hasCapture = 1;
-	    }
+            hwDevInfos[numDeviceNames - 1].name     = deviceName;
+            hwDevInfos[numDeviceNames - 1].isPlug   = 1;
+
+            if( predefined )
+            {
+                hwDevInfos[numDeviceNames - 1].hasPlayback = predefined->hasPlayback;
+                hwDevInfos[numDeviceNames - 1].hasCapture  = predefined->hasCapture;
+            }
+            else
+            {
+                hwDevInfos[numDeviceNames - 1].hasPlayback = 1;
+                hwDevInfos[numDeviceNames - 1].hasCapture  = 1;
+            }
         }
     }
     else
-        PA_DEBUG(( "%s: Iterating over ALSA plugins failed: %s\n", __FUNCTION__, snd_strerror( res ) ));
+        PA_DEBUG(( "%s: Iterating over ALSA plugins failed: %s\n", __FUNCTION__, alsa_snd_strerror( res ) ));
 
     /* allocate deviceInfo memory based on the number of devices */
     PA_UNLESS( baseApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
@@ -849,7 +1439,7 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
      * (dmix) is closed. The 'default' plugin may also point to the dmix plugin, so the same goes
      * for this.
      */
-
+    PA_DEBUG(( "%s: Filling device info for %d devices\n", __FUNCTION__, numDeviceNames ));
     for( i = 0, devIdx = 0; i < numDeviceNames; ++i )
     {
         PaAlsaDeviceInfo* devInfo = &deviceInfoArray[i];
@@ -872,8 +1462,7 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
             continue;
         }
 
-        PA_ENSURE( FillInDevInfo( alsaApi, hwInfo, blocking, devInfo,
-                    &devIdx ) );
+        PA_ENSURE( FillInDevInfo( alsaApi, hwInfo, blocking, devInfo, &devIdx ) );
     }
     free( hwDevInfos );
 
@@ -920,8 +1509,8 @@ static PaError ValidateParameters( const PaStreamParameters *parameters, PaUtilH
 
     assert( deviceInfo );
     assert( parameters->hostApiSpecificStreamInfo == NULL );
-    maxChans = (StreamDirection_In == mode ? deviceInfo->baseDeviceInfo.maxInputChannels :
-        deviceInfo->baseDeviceInfo.maxOutputChannels);
+    maxChans = ( StreamDirection_In == mode ? deviceInfo->baseDeviceInfo.maxInputChannels :
+        deviceInfo->baseDeviceInfo.maxOutputChannels );
     PA_UNLESS( parameters->channelCount <= maxChans, paInvalidChannelCount );
 
 error:
@@ -933,36 +1522,158 @@ static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm )
 {
     PaSampleFormat available = 0;
     snd_pcm_hw_params_t *hwParams;
-    snd_pcm_hw_params_alloca( &hwParams );
+    alsa_snd_pcm_hw_params_alloca( &hwParams );
 
-    snd_pcm_hw_params_any( pcm, hwParams );
+    alsa_snd_pcm_hw_params_any( pcm, hwParams );
 
-    if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT ) >= 0)
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT ) >= 0)
         available |= paFloat32;
 
-    if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S32 ) >= 0)
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S32 ) >= 0)
         available |= paInt32;
 
 #ifdef PA_LITTLE_ENDIAN
-    if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_3LE ) >= 0)
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_3LE ) >= 0)
         available |= paInt24;
 #elif defined PA_BIG_ENDIAN
-    if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_3BE ) >= 0)
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_3BE ) >= 0)
         available |= paInt24;
 #endif
 
-    if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16 ) >= 0)
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16 ) >= 0)
         available |= paInt16;
 
-    if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U8 ) >= 0)
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U8 ) >= 0)
         available |= paUInt8;
 
-    if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S8 ) >= 0)
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S8 ) >= 0)
         available |= paInt8;
 
     return available;
 }
 
+/* Output to console all formats supported by device */
+static void LogAllAvailableFormats( snd_pcm_t *pcm )
+{
+    PaSampleFormat available = 0;
+    snd_pcm_hw_params_t *hwParams;
+    alsa_snd_pcm_hw_params_alloca( &hwParams );
+
+    alsa_snd_pcm_hw_params_any( pcm, hwParams );
+
+    PA_DEBUG(( " --- Supported Formats ---\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S8 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S8\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U8 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U8\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16_LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S16_LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16_BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S16_BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U16_LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U16_LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U16_BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U16_BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S24_LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S24_BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U24_LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U24_LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U24_BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U24_BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT_LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_FLOAT_LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT_BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_FLOAT_BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT64_LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_FLOAT64_LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT64_BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_FLOAT64_BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_IEC958_SUBFRAME_LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_IEC958_SUBFRAME_LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_IEC958_SUBFRAME_BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_IEC958_SUBFRAME_BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_MU_LAW ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_MU_LAW\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_A_LAW ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_A_LAW\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_IMA_ADPCM ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_IMA_ADPCM\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_MPEG ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_MPEG\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_GSM ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_GSM\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_SPECIAL ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_SPECIAL\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_3LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S24_3LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24_3BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S24_3BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U24_3LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U24_3LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U24_3BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U24_3BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S20_3LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S20_3LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S20_3BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S20_3BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U20_3LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U20_3LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U20_3BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U20_3BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S18_3LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S18_3LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S18_3BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S18_3BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U18_3LE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U18_3LE\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U18_3BE ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U18_3BE\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S16\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U16 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U16\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S24\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U24 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U24\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S32 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_S32\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U32 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_U32\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_FLOAT\n" ));
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT64 ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_FLOAT64\n" ));
+
+    if( alsa_snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_IEC958_SUBFRAME ) >= 0)
+        PA_DEBUG(( "SND_PCM_FORMAT_IEC958_SUBFRAME\n" ));
+
+    PA_DEBUG(( " -------------------------\n" ));
+}
+
 static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat )
 {
     switch( paFormat )
@@ -995,33 +1706,23 @@ static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat )
 }
 
 /** Open an ALSA pcm handle.
- * 
- * The device to be open can be specified in a custom PaAlsaStreamInfo struct, or it will be a device number. In case of a
- * device number, it maybe specified through an env variable (PA_ALSA_PLUGHW) that we should open the corresponding plugin
- * device.
+ *
+ * The device to be open can be specified by name in a custom PaAlsaStreamInfo struct, or it will be by
+ * the Portaudio device number supplied in the stream parameters.
  */
 static PaError AlsaOpen( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *params, StreamDirection
         streamDir, snd_pcm_t **pcm )
 {
     PaError result = paNoError;
     int ret;
-    char dnameArray[50];
-    const char* deviceName = dnameArray;
+    const char* deviceName = "";
     const PaAlsaDeviceInfo *deviceInfo = NULL;
     PaAlsaStreamInfo *streamInfo = (PaAlsaStreamInfo *)params->hostApiSpecificStreamInfo;
 
     if( !streamInfo )
     {
-        int usePlug = 0;
         deviceInfo = GetDeviceInfo( hostApi, params->device );
-        
-        /* If device name starts with hw: and PA_ALSA_PLUGHW is 1, we open the plughw device instead */
-        if( !strncmp( "hw:", deviceInfo->alsaName, 3 ) && getenv( "PA_ALSA_PLUGHW" ) )
-            usePlug = atoi( getenv( "PA_ALSA_PLUGHW" ) );
-        if( usePlug )
-            snprintf( dnameArray, 50, "plug%s", deviceInfo->alsaName );
-        else
-            deviceName = deviceInfo->alsaName;
+        deviceName = deviceInfo->alsaName;
     }
     else
         deviceName = streamInfo->deviceString;
@@ -1034,7 +1735,7 @@ static PaError AlsaOpen( const PaUtilHostApiRepresentation *hostApi, const PaStr
         *pcm = NULL;
         ENSURE_( ret, -EBUSY == ret ? paDeviceUnavailable : paBadIODeviceCombination );
     }
-    ENSURE_( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError );
 
 end:
     return result;
@@ -1053,8 +1754,8 @@ static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const
     unsigned int numHostChannels;
     PaSampleFormat hostFormat;
     snd_pcm_hw_params_t *hwParams;
-    snd_pcm_hw_params_alloca( &hwParams );
-    
+    alsa_snd_pcm_hw_params_alloca( &hwParams );
+
     if( !parameters->hostApiSpecificStreamInfo )
     {
         const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, parameters->device );
@@ -1066,7 +1767,7 @@ static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const
 
     PA_ENSURE( AlsaOpen( hostApi, parameters, streamDir, &pcm ) );
 
-    snd_pcm_hw_params_any( pcm, hwParams );
+    alsa_snd_pcm_hw_params_any( pcm, hwParams );
 
     if( SetApproximateSampleRate( pcm, hwParams, sampleRate ) < 0 )
     {
@@ -1074,7 +1775,7 @@ static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const
         goto error;
     }
 
-    if( snd_pcm_hw_params_set_channels( pcm, hwParams, numHostChannels ) < 0 )
+    if( alsa_snd_pcm_hw_params_set_channels( pcm, hwParams, numHostChannels ) < 0 )
     {
         result = paInvalidChannelCount;
         goto error;
@@ -1083,12 +1784,14 @@ static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const
     /* See if we can find a best possible match */
     availableFormats = GetAvailableFormats( pcm );
     PA_ENSURE( hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) );
-    ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, Pa2AlsaFormat( hostFormat ) ), paUnanticipatedHostError );
+
+    /* Some specific hardware (reported: Audio8 DJ) can fail with assertion during this step. */
+    ENSURE_( alsa_snd_pcm_hw_params_set_format( pcm, hwParams, Pa2AlsaFormat( hostFormat ) ), paUnanticipatedHostError );
 
     {
         /* It happens that this call fails because the device is busy */
         int ret = 0;
-        if( (ret = snd_pcm_hw_params( pcm, hwParams )) < 0)
+        if( ( ret = alsa_snd_pcm_hw_params( pcm, hwParams ) ) < 0 )
         {
             if( -EINVAL == ret )
             {
@@ -1113,7 +1816,7 @@ static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const
 end:
     if( pcm )
     {
-        snd_pcm_close( pcm );
+        alsa_snd_pcm_close( pcm );
     }
     return result;
 
@@ -1148,13 +1851,13 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 
     if( inputChannelCount )
     {
-        if( (result = TestParameters( hostApi, inputParameters, sampleRate, StreamDirection_In ))
+        if( ( result = TestParameters( hostApi, inputParameters, sampleRate, StreamDirection_In ) )
                 != paNoError )
             goto error;
     }
     if ( outputChannelCount )
     {
-        if( (result = TestParameters( hostApi, outputParameters, sampleRate, StreamDirection_Out ))
+        if( ( result = TestParameters( hostApi, outputParameters, sampleRate, StreamDirection_Out ) )
                 != paNoError )
             goto error;
     }
@@ -1165,11 +1868,12 @@ error:
     return result;
 }
 
+
 static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, PaAlsaHostApiRepresentation *alsaApi,
         const PaStreamParameters *params, StreamDirection streamDir, int callbackMode )
 {
     PaError result = paNoError;
-    PaSampleFormat userSampleFormat = params->sampleFormat, hostSampleFormat;
+    PaSampleFormat userSampleFormat = params->sampleFormat, hostSampleFormat = paNoError;
     assert( params->channelCount > 0 );
 
     /* Make sure things have an initial value */
@@ -1180,24 +1884,35 @@ static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, Pa
         const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( &alsaApi->baseHostApiRep, params->device );
         self->numHostChannels = PA_MAX( params->channelCount, StreamDirection_In == streamDir ? devInfo->minInputChannels
                 : devInfo->minOutputChannels );
+        self->deviceIsPlug = devInfo->isPlug;
+        PA_DEBUG(( "%s: Host Chans %c %i\n", __FUNCTION__, streamDir == StreamDirection_In ? 'C' : 'P', self->numHostChannels ));
     }
     else
     {
         /* We're blissfully unaware of the minimum channelCount */
         self->numHostChannels = params->channelCount;
+        /* Check if device name does not start with hw: to determine if it is a 'plug' device */
+        if( strncmp( "hw:", ((PaAlsaStreamInfo *)params->hostApiSpecificStreamInfo)->deviceString, 3 ) != 0  )
+            self->deviceIsPlug = 1; /* An Alsa plug device, not a direct hw device */
     }
+    if( self->deviceIsPlug && alsaApi->alsaLibVersion < ALSA_VERSION_INT( 1, 0, 16 ) )
+        self->useReventFix = 1; /* Prior to Alsa1.0.16, plug devices may stutter without this fix */
 
     self->device = params->device;
 
     PA_ENSURE( AlsaOpen( &alsaApi->baseHostApiRep, params, streamDir, &self->pcm ) );
-    self->nfds = snd_pcm_poll_descriptors_count( self->pcm );
-    hostSampleFormat = PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( self->pcm ), userSampleFormat );
+    self->nfds = alsa_snd_pcm_poll_descriptors_count( self->pcm );
+
+    PA_ENSURE( hostSampleFormat = PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( self->pcm ), userSampleFormat ) );
 
     self->hostSampleFormat = hostSampleFormat;
     self->nativeFormat = Pa2AlsaFormat( hostSampleFormat );
-    self->hostInterleaved = self->userInterleaved = !(userSampleFormat & paNonInterleaved);
+    self->hostInterleaved = self->userInterleaved = !( userSampleFormat & paNonInterleaved );
     self->numUserChannels = params->channelCount;
     self->streamDir = streamDir;
+    self->canMmap = 0;
+    self->nonMmapBuffer = NULL;
+    self->nonMmapBufferSize = 0;
 
     if( !callbackMode && !self->userInterleaved )
     {
@@ -1207,14 +1922,22 @@ static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, Pa
     }
 
 error:
+
+    /* Log all available formats. */
+    if ( hostSampleFormat == paSampleFormatNotSupported )
+    {
+        LogAllAvailableFormats( self->pcm );
+        PA_DEBUG(( "%s: Please provide the log output to PortAudio developers, your hardware does not have any sample format implemented yet.\n", __FUNCTION__ ));
+    }
+
     return result;
 }
 
 static void PaAlsaStreamComponent_Terminate( PaAlsaStreamComponent *self )
 {
-    snd_pcm_close( self->pcm );
-    if( self->userBuffers )
-        PaUtil_FreeMemory( self->userBuffers );
+    alsa_snd_pcm_close( self->pcm );
+    PaUtil_FreeMemory( self->userBuffers ); /* (Ptr can be NULL; PaUtil_FreeMemory includes a NULL check) */
+    PaUtil_FreeMemory( self->nonMmapBuffer );
 }
 
 /*
@@ -1245,60 +1968,88 @@ static PaError PaAlsaStreamComponent_InitialConfigure( PaAlsaStreamComponent *se
     double sr = *sampleRate;
     unsigned int minPeriods = 2;
 
-    /* self->framesPerBuffer = framesPerHostBuffer; */
+    /* self->framesPerPeriod = framesPerHostBuffer; */
 
     /* ... fill up the configuration space with all possibile
      * combinations of parameters this device will accept */
-    ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError );
 
-    ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError );
     /* I think there should be at least 2 periods (even though ALSA doesn't appear to enforce this) */
     dir = 0;
-    ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParams, &minPeriods, &dir ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_hw_params_set_periods_min( pcm, hwParams, &minPeriods, &dir ), paUnanticipatedHostError );
 
     if( self->userInterleaved )
     {
-        accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED;
+        accessMode          = SND_PCM_ACCESS_MMAP_INTERLEAVED;
         alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED;
+
+        /* test if MMAP supported */
+        self->canMmap = alsa_snd_pcm_hw_params_test_access( pcm, hwParams, accessMode ) >= 0 ||
+                        alsa_snd_pcm_hw_params_test_access( pcm, hwParams, alternateAccessMode ) >= 0;
+
+        PA_DEBUG(( "%s: device MMAP SND_PCM_ACCESS_MMAP_INTERLEAVED: %s\n", __FUNCTION__, ( alsa_snd_pcm_hw_params_test_access( pcm, hwParams, accessMode ) >= 0 ? "YES" : "NO" ) ));
+        PA_DEBUG(( "%s: device MMAP SND_PCM_ACCESS_MMAP_NONINTERLEAVED: %s\n", __FUNCTION__, ( alsa_snd_pcm_hw_params_test_access( pcm, hwParams, alternateAccessMode ) >= 0 ? "YES" : "NO" ) ));
+
+        if( !self->canMmap )
+        {
+            accessMode          = SND_PCM_ACCESS_RW_INTERLEAVED;
+            alternateAccessMode = SND_PCM_ACCESS_RW_NONINTERLEAVED;
+        }
     }
     else
     {
-        accessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED;
+        accessMode          = SND_PCM_ACCESS_MMAP_NONINTERLEAVED;
         alternateAccessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED;
+
+        /* test if MMAP supported */
+        self->canMmap = alsa_snd_pcm_hw_params_test_access( pcm, hwParams, accessMode ) >= 0 ||
+                        alsa_snd_pcm_hw_params_test_access( pcm, hwParams, alternateAccessMode ) >= 0;
+
+        PA_DEBUG((" %s: device MMAP SND_PCM_ACCESS_MMAP_NONINTERLEAVED: %s\n", __FUNCTION__, ( alsa_snd_pcm_hw_params_test_access( pcm, hwParams, accessMode ) >= 0 ? "YES" : "NO" ) ));
+        PA_DEBUG(( "%s: device MMAP SND_PCM_ACCESS_MMAP_INTERLEAVED: %s\n", __FUNCTION__, ( alsa_snd_pcm_hw_params_test_access( pcm, hwParams, alternateAccessMode ) >= 0 ? "YES" : "NO" ) ));
+
+        if( !self->canMmap )
+        {
+            accessMode          = SND_PCM_ACCESS_RW_NONINTERLEAVED;
+            alternateAccessMode = SND_PCM_ACCESS_RW_INTERLEAVED;
+        }
     }
+
+    PA_DEBUG(( "%s: device can MMAP: %s\n", __FUNCTION__, ( self->canMmap ? "YES" : "NO" ) ));
+
     /* If requested access mode fails, try alternate mode */
-    if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 )
+    if( alsa_snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 )
     {
         int err = 0;
-        if( (err = snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode )) < 0)
+        if( ( err = alsa_snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode )) < 0 )
         {
             result = paUnanticipatedHostError;
-            if( -EINVAL == err )
-            {
-                PaUtil_SetLastHostErrorInfo( paALSA, err, "PA ALSA requires that a device supports mmap access" );
-            }
-            else
-            {
-                PaUtil_SetLastHostErrorInfo( paALSA, err, snd_strerror( err ) );
-            }
+            PaUtil_SetLastHostErrorInfo( paALSA, err, alsa_snd_strerror( err ) );
             goto error;
         }
         /* Flip mode */
         self->hostInterleaved = !self->userInterleaved;
     }
 
-    ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError );
+    /* Some specific hardware (reported: Audio8 DJ) can fail with assertion during this step. */
+    ENSURE_( alsa_snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError );
 
-    ENSURE_( SetApproximateSampleRate( pcm, hwParams, sr ), paInvalidSampleRate );
-    ENSURE_( GetExactSampleRate( hwParams, &sr ), paUnanticipatedHostError );
-    /* reject if there's no sample rate within 1% of the one requested */
-    if( (fabs( *sampleRate - sr ) / *sampleRate) > 0.01 )
+    if( ( result = SetApproximateSampleRate( pcm, hwParams, sr )) != paUnanticipatedHostError )
+    {
+        ENSURE_( GetExactSampleRate( hwParams, &sr ), paUnanticipatedHostError );
+        if( result == paInvalidSampleRate ) /* From the SetApproximateSampleRate() call above */
+        { /* The sample rate was returned as 'out of tolerance' of the one requested */
+            PA_DEBUG(( "%s: Wanted %.3f, closest sample rate was %.3f\n", __FUNCTION__, sampleRate, sr ));
+            PA_ENSURE( paInvalidSampleRate );
+        }
+    }
+    else
     {
-        PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr ));                 
-        PA_ENSURE( paInvalidSampleRate );
+       PA_ENSURE( paUnanticipatedHostError );
     }
 
-    ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParams, self->numHostChannels ), paInvalidChannelCount );
+    ENSURE_( alsa_snd_pcm_hw_params_set_channels( pcm, hwParams, self->numHostChannels ), paInvalidChannelCount );
 
     *sampleRate = sr;
 
@@ -1312,7 +2063,7 @@ error:
 
 /** Finish the configuration of the component's ALSA device.
  *
- * As part of this method, the component's bufferSize attribute will be set.
+ * As part of this method, the component's alsaBufferSize attribute will be set.
  * @param latency: The latency for this component.
  */
 static PaError PaAlsaStreamComponent_FinishConfigure( PaAlsaStreamComponent *self, snd_pcm_hw_params_t* hwParams,
@@ -1323,49 +2074,57 @@ static PaError PaAlsaStreamComponent_FinishConfigure( PaAlsaStreamComponent *sel
     snd_pcm_uframes_t bufSz = 0;
     *latency = -1.;
 
-    snd_pcm_sw_params_alloca( &swParams );
+    alsa_snd_pcm_sw_params_alloca( &swParams );
 
-    bufSz = params->suggestedLatency * sampleRate;
-    ENSURE_( snd_pcm_hw_params_set_buffer_size_near( self->pcm, hwParams, &bufSz ), paUnanticipatedHostError );
+    bufSz = params->suggestedLatency * sampleRate + self->framesPerPeriod;
+    ENSURE_( alsa_snd_pcm_hw_params_set_buffer_size_near( self->pcm, hwParams, &bufSz ), paUnanticipatedHostError );
 
     /* Set the parameters! */
     {
-        int r = snd_pcm_hw_params( self->pcm, hwParams );
+        int r = alsa_snd_pcm_hw_params( self->pcm, hwParams );
 #ifdef PA_ENABLE_DEBUG_OUTPUT
         if( r < 0 )
         {
             snd_output_t *output = NULL;
-            snd_output_stdio_attach( &output, stderr, 0 );
-            snd_pcm_hw_params_dump( hwParams, output );
+            alsa_snd_output_stdio_attach( &output, stderr, 0 );
+            alsa_snd_pcm_hw_params_dump( hwParams, output );
         }
 #endif
-        ENSURE_(r, paUnanticipatedHostError );
+        ENSURE_( r, paUnanticipatedHostError );
     }
-    ENSURE_( snd_pcm_hw_params_get_buffer_size( hwParams, &self->bufferSize ), paUnanticipatedHostError );
+    if( alsa_snd_pcm_hw_params_get_buffer_size != NULL )
+    {
+        ENSURE_( alsa_snd_pcm_hw_params_get_buffer_size( hwParams, &self->alsaBufferSize ), paUnanticipatedHostError );
+    }
+    else
+    {
+        self->alsaBufferSize = bufSz;
+    }
+
     /* Latency in seconds */
-    *latency = self->bufferSize / sampleRate;
+    *latency = (self->alsaBufferSize - self->framesPerPeriod) / sampleRate;
 
     /* Now software parameters... */
-    ENSURE_( snd_pcm_sw_params_current( self->pcm, swParams ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_sw_params_current( self->pcm, swParams ), paUnanticipatedHostError );
 
-    ENSURE_( snd_pcm_sw_params_set_start_threshold( self->pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError );
-    ENSURE_( snd_pcm_sw_params_set_stop_threshold( self->pcm, swParams, self->bufferSize ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_sw_params_set_start_threshold( self->pcm, swParams, self->framesPerPeriod ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_sw_params_set_stop_threshold( self->pcm, swParams, self->alsaBufferSize ), paUnanticipatedHostError );
 
     /* Silence buffer in the case of underrun */
     if( !primeBuffers ) /* XXX: Make sense? */
     {
         snd_pcm_uframes_t boundary;
-        ENSURE_( snd_pcm_sw_params_get_boundary( swParams, &boundary ), paUnanticipatedHostError );
-        ENSURE_( snd_pcm_sw_params_set_silence_threshold( self->pcm, swParams, 0 ), paUnanticipatedHostError );
-        ENSURE_( snd_pcm_sw_params_set_silence_size( self->pcm, swParams, boundary ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_sw_params_get_boundary( swParams, &boundary ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_sw_params_set_silence_threshold( self->pcm, swParams, 0 ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_sw_params_set_silence_size( self->pcm, swParams, boundary ), paUnanticipatedHostError );
     }
-        
-    ENSURE_( snd_pcm_sw_params_set_avail_min( self->pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError );
-    ENSURE_( snd_pcm_sw_params_set_xfer_align( self->pcm, swParams, 1 ), paUnanticipatedHostError );
-    ENSURE_( snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError );
+
+    ENSURE_( alsa_snd_pcm_sw_params_set_avail_min( self->pcm, swParams, self->framesPerPeriod ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_sw_params_set_xfer_align( self->pcm, swParams, 1 ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_ENABLE ), paUnanticipatedHostError );
 
     /* Set the parameters! */
-    ENSURE_( snd_pcm_sw_params( self->pcm, swParams ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_sw_params( self->pcm, swParams ), paUnanticipatedHostError );
 
 error:
     return result;
@@ -1378,7 +2137,7 @@ static PaError PaAlsaStream_Initialize( PaAlsaStream *self, PaAlsaHostApiReprese
     PaError result = paNoError;
     assert( self );
 
-    memset( self, 0, sizeof (PaAlsaStream) );
+    memset( self, 0, sizeof( PaAlsaStream ) );
 
     if( NULL != callback )
     {
@@ -1414,8 +2173,8 @@ static PaError PaAlsaStream_Initialize( PaAlsaStream *self, PaAlsaHostApiReprese
 
     assert( self->capture.nfds || self->playback.nfds );
 
-    PA_UNLESS( self->pfds = (struct pollfd*)PaUtil_AllocateMemory( (self->capture.nfds +
-                    self->playback.nfds) * sizeof (struct pollfd) ), paInsufficientMemory );
+    PA_UNLESS( self->pfds = (struct pollfd*)PaUtil_AllocateMemory( ( self->capture.nfds +
+                    self->playback.nfds ) * sizeof( struct pollfd ) ), paInsufficientMemory );
 
     PaUtil_InitializeCpuLoadMeasurer( &self->cpuLoadMeasurer, sampleRate );
     ASSERT_CALL_( PaUnixMutex_Initialize( &self->stateMtx ), paNoError );
@@ -1459,9 +2218,43 @@ static int CalculatePollTimeout( const PaAlsaStream *stream, unsigned long frame
     return (int)ceil( 1000 * frames / stream->streamRepresentation.streamInfo.sampleRate );
 }
 
+/** Align value in backward direction.
+ *
+ * @param v: Value to align.
+ * @param align: Alignment.
+ */
+static unsigned long PaAlsa_AlignBackward(unsigned long v, unsigned long align)
+{
+    return ( v - ( align ? v % align : 0 ) );
+}
+
+/** Align value in forward direction.
+ *
+ * @param v: Value to align.
+ * @param align: Alignment.
+ */
+static unsigned long PaAlsa_AlignForward(unsigned long v, unsigned long align)
+{
+    unsigned long remainder = ( align ? ( v % align ) : 0);
+    return ( remainder != 0 ? v + ( align - remainder ) : v );
+}
+
+/** Get size of host buffer maintained from the number of user frames, sample rate and suggested latency. Minimum double buffering
+ *  is maintained to allow 100% CPU usage inside user callback.
+ *
+ * @param userFramesPerBuffer: User buffer size in number of frames.
+ * @param suggestedLatency: User provided desired latency.
+ * @param sampleRate: Sample rate.
+ */
+static unsigned long PaAlsa_GetFramesPerHostBuffer(unsigned long userFramesPerBuffer, PaTime suggestedLatency, double sampleRate)
+{
+    unsigned long frames = userFramesPerBuffer + PA_MAX( userFramesPerBuffer, (unsigned long)( suggestedLatency * sampleRate ) );
+    return frames;
+}
+
 /** Determine size per host buffer.
  *
- * During this method call, the component's framesPerBuffer attribute gets computed, and the corresponding period size
+ * During this method call, the component's framesPerPeriod attribute gets computed, and the corresponding period size
  * gets configured for the device.
  * @param accurate: If the configured period size is non-integer, this will be set to 0.
  */
@@ -1469,18 +2262,20 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo
         unsigned long framesPerUserBuffer, double sampleRate, snd_pcm_hw_params_t* hwParams, int* accurate )
 {
     PaError result = paNoError;
-    unsigned long bufferSize = params->suggestedLatency * sampleRate, framesPerHostBuffer;
+    unsigned long bufferSize, framesPerHostBuffer;
     int dir = 0;
-    
-    {
-        snd_pcm_uframes_t tmp;
-        snd_pcm_hw_params_get_buffer_size_min( hwParams, &tmp );
-        bufferSize = PA_MAX( bufferSize, tmp );
-        snd_pcm_hw_params_get_buffer_size_max( hwParams, &tmp );
-        bufferSize = PA_MIN( bufferSize, tmp );
-    }
 
-    assert( bufferSize > 0 );
+    /* Calculate host buffer size */
+    bufferSize = PaAlsa_GetFramesPerHostBuffer(framesPerUserBuffer, params->suggestedLatency, sampleRate);
+
+    /* Log */
+    PA_DEBUG(( "%s: user-buffer (frames)           = %lu\n", __FUNCTION__, framesPerUserBuffer ));
+    PA_DEBUG(( "%s: user-buffer (sec)              = %f\n",  __FUNCTION__, (double)(framesPerUserBuffer / sampleRate) ));
+    PA_DEBUG(( "%s: suggested latency (sec)        = %f\n",  __FUNCTION__, params->suggestedLatency ));
+    PA_DEBUG(( "%s: suggested host buffer (frames) = %lu\n", __FUNCTION__, bufferSize ));
+    PA_DEBUG(( "%s: suggested host buffer (sec)    = %f\n",  __FUNCTION__, (double)(bufferSize / sampleRate) ));
+
+#ifdef PA_ALSA_USE_OBSOLETE_HOST_BUFFER_CALC
 
     if( framesPerUserBuffer != paFramesPerBufferUnspecified )
     {
@@ -1522,15 +2317,60 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo
         }
     }
 
-    /* Using the base number of periods, we try to approximate the suggested latency (+1 period),
-       finding a combination of period/buffer size which best fits these constraints */
+#endif
+
     {
-        unsigned numPeriods = numPeriods_, maxPeriods = 0;
+        unsigned numPeriods = numPeriods_, maxPeriods = 0, minPeriods = numPeriods_;
+
         /* It may be that the device only supports 2 periods for instance */
         dir = 0;
-        ENSURE_( snd_pcm_hw_params_get_periods_max( hwParams, &maxPeriods, &dir ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_hw_params_get_periods_min( hwParams, &minPeriods, &dir ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_hw_params_get_periods_max( hwParams, &maxPeriods, &dir ), paUnanticipatedHostError );
         assert( maxPeriods > 1 );
-        numPeriods = PA_MIN( maxPeriods, numPeriods );
+
+        /* Clamp to min/max */
+        numPeriods = PA_MIN(maxPeriods, PA_MAX(minPeriods, numPeriods));
+
+        PA_DEBUG(( "%s: periods min = %lu, max = %lu, req = %lu \n", __FUNCTION__, minPeriods, maxPeriods, numPeriods ));
+
+#ifndef PA_ALSA_USE_OBSOLETE_HOST_BUFFER_CALC
+
+        /* Calculate period size */
+        framesPerHostBuffer = (bufferSize / numPeriods);
+
+        /* Align & test size */
+        if( framesPerUserBuffer != paFramesPerBufferUnspecified )
+        {
+            /* Align to user buffer size */
+            framesPerHostBuffer = PaAlsa_AlignForward(framesPerHostBuffer, framesPerUserBuffer);
+
+            /* Test (borrowed from older implementation) */
+            if( framesPerHostBuffer < framesPerUserBuffer )
+            {
+                assert( framesPerUserBuffer % framesPerHostBuffer == 0 );
+                if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 )
+                {
+                    if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer * 2, 0 ) == 0 )
+                        framesPerHostBuffer *= 2;
+                    else if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer / 2, 0 ) == 0 )
+                        framesPerHostBuffer /= 2;
+                }
+            }
+            else
+            {
+                assert( framesPerHostBuffer % framesPerUserBuffer == 0 );
+                if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 )
+                {
+                    if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer + framesPerUserBuffer, 0 ) == 0 )
+                        framesPerHostBuffer += framesPerUserBuffer;
+                    else if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer - framesPerUserBuffer, 0 ) == 0 )
+                        framesPerHostBuffer -= framesPerUserBuffer;
+                }
+            }
+        }
+#endif
+
+#ifdef PA_ALSA_USE_OBSOLETE_HOST_BUFFER_CALC
 
         if( framesPerUserBuffer != paFramesPerBufferUnspecified )
         {
@@ -1552,7 +2392,7 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo
             {
                 while( bufferSize / framesPerHostBuffer < numPeriods )
                 {
-                    if( framesPerUserBuffer % (framesPerHostBuffer / 2) != 0 )
+                    if( framesPerUserBuffer % ( framesPerHostBuffer / 2 ) != 0 )
                     {
                         /* Can't be divided any further */
                         break;
@@ -1564,22 +2404,22 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo
             if( framesPerHostBuffer < framesPerUserBuffer )
             {
                 assert( framesPerUserBuffer % framesPerHostBuffer == 0 );
-                if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 )
+                if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 )
                 {
-                    if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer * 2, 0 ) == 0 )
+                    if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer * 2, 0 ) == 0 )
                         framesPerHostBuffer *= 2;
-                    else if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer / 2, 0 ) == 0 )
+                    else if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer / 2, 0 ) == 0 )
                         framesPerHostBuffer /= 2;
                 }
             }
             else
             {
                 assert( framesPerHostBuffer % framesPerUserBuffer == 0 );
-                if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 )
+                if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 )
                 {
-                    if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer + framesPerUserBuffer, 0 ) == 0 )
+                    if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer + framesPerUserBuffer, 0 ) == 0 )
                         framesPerHostBuffer += framesPerUserBuffer;
-                    else if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer - framesPerUserBuffer, 0 ) == 0 )
+                    else if( alsa_snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer - framesPerUserBuffer, 0 ) == 0 )
                         framesPerHostBuffer -= framesPerUserBuffer;
                 }
             }
@@ -1588,38 +2428,49 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo
         {
             framesPerHostBuffer = bufferSize / numPeriods;
         }
+
+        /* non-mmap mode needs a reasonably-sized buffer or it'll stutter */
+        if( !self->canMmap && framesPerHostBuffer < 2048 )
+            framesPerHostBuffer = 2048;
+#endif
+        PA_DEBUG(( "%s: suggested host buffer period   = %lu \n", __FUNCTION__, framesPerHostBuffer ));
     }
 
-    assert( framesPerHostBuffer > 0 );
     {
-        snd_pcm_uframes_t min = 0, max = 0;
-        ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParams, &min, NULL ), paUnanticipatedHostError );
-        ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParams, &max, NULL ), paUnanticipatedHostError );
+        /* Get min/max period sizes and adjust our chosen */
+        snd_pcm_uframes_t min = 0, max = 0, minmax_diff;
+        ENSURE_( alsa_snd_pcm_hw_params_get_period_size_min( hwParams, &min, NULL ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_hw_params_get_period_size_max( hwParams, &max, NULL ), paUnanticipatedHostError );
+        minmax_diff = max - min;
 
         if( framesPerHostBuffer < min )
         {
-            PA_DEBUG(( "%s: The determined period size (%lu) is less than minimum (%lu)\n", __FUNCTION__,
-                        framesPerHostBuffer, min ));
-            framesPerHostBuffer = min;
+            PA_DEBUG(( "%s: The determined period size (%lu) is less than minimum (%lu)\n", __FUNCTION__, framesPerHostBuffer, min ));
+            framesPerHostBuffer = (( minmax_diff == 2 ) ? min + 1 : min );
         }
         else if( framesPerHostBuffer > max )
         {
-            PA_DEBUG(( "%s: The determined period size (%lu) is greater than maximum (%lu)\n", __FUNCTION__,
-                        framesPerHostBuffer, max ));
-            framesPerHostBuffer = max;
+            PA_DEBUG(( "%s: The determined period size (%lu) is greater than maximum (%lu)\n", __FUNCTION__, framesPerHostBuffer, max ));
+            framesPerHostBuffer = (( minmax_diff == 2 ) ? max - 1 : max );
         }
 
-        assert( framesPerHostBuffer >= min && framesPerHostBuffer <= max );
+        PA_DEBUG(( "%s: device period minimum          = %lu\n", __FUNCTION__, min ));
+        PA_DEBUG(( "%s: device period maximum          = %lu\n", __FUNCTION__, max ));
+        PA_DEBUG(( "%s: host buffer period             = %lu\n", __FUNCTION__, framesPerHostBuffer ));
+        PA_DEBUG(( "%s: host buffer period latency     = %f\n", __FUNCTION__, (double)( framesPerHostBuffer / sampleRate ) ));
+
+        /* Try setting period size */
         dir = 0;
-        ENSURE_( snd_pcm_hw_params_set_period_size_near( self->pcm, hwParams, &framesPerHostBuffer, &dir ),
-                paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_hw_params_set_period_size_near( self->pcm, hwParams, &framesPerHostBuffer, &dir ), paUnanticipatedHostError );
         if( dir != 0 )
         {
             PA_DEBUG(( "%s: The configured period size is non-integer.\n", __FUNCTION__, dir ));
             *accurate = 0;
         }
     }
-    self->framesPerBuffer = framesPerHostBuffer;
+
+    /* Set result */
+    self->framesPerPeriod = framesPerHostBuffer;
 
 error:
     return result;
@@ -1646,8 +2497,8 @@ error:
  * which should be fine if the period size is the same for capture and playback. In general, if there is a specified user
  * buffer size, this method tries it best to determine a period size which is a multiple of the user buffer size.
  *
- * The framesPerBuffer attributes of the individual capture and playback components of the stream are set to corresponding
- * values determined here. Since these should be reported as 
+ * The framesPerPeriod attributes of the individual capture and playback components of the stream are set to corresponding
+ * values determined here. Since these should be reported as
  *
  * This is one of those blocks of code that will just take a lot of
  * refinement to be any good.
@@ -1678,25 +2529,25 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double
                               minCapture, minPlayback, maxCapture, maxPlayback;
 
             dir = 0;
-            ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsCapture, &minCapture, &dir ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_hw_params_get_period_size_min( hwParamsCapture, &minCapture, &dir ), paUnanticipatedHostError );
             dir = 0;
-            ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsPlayback, &minPlayback, &dir ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_hw_params_get_period_size_min( hwParamsPlayback, &minPlayback, &dir ), paUnanticipatedHostError );
             dir = 0;
-            ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsCapture, &maxCapture, &dir ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_hw_params_get_period_size_max( hwParamsCapture, &maxCapture, &dir ), paUnanticipatedHostError );
             dir = 0;
-            ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsPlayback, &maxPlayback, &dir ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_hw_params_get_period_size_max( hwParamsPlayback, &maxPlayback, &dir ), paUnanticipatedHostError );
             minPeriodSize = PA_MAX( minPlayback, minCapture );
             maxPeriodSize = PA_MIN( maxPlayback, maxCapture );
             PA_UNLESS( minPeriodSize <= maxPeriodSize, paBadIODeviceCombination );
 
-            desiredBufSz = (snd_pcm_uframes_t)(PA_MIN( outputParameters->suggestedLatency, inputParameters->suggestedLatency )
-                    * sampleRate);
+            desiredBufSz = (snd_pcm_uframes_t)( PA_MIN( outputParameters->suggestedLatency, inputParameters->suggestedLatency )
+                    * sampleRate );
             /* Clamp desiredBufSz */
             {
                 snd_pcm_uframes_t maxBufferSize;
                 snd_pcm_uframes_t maxBufferSizeCapture, maxBufferSizePlayback;
-                ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &maxBufferSizeCapture ), paUnanticipatedHostError );
-                ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsPlayback, &maxBufferSizePlayback ), paUnanticipatedHostError );
+                ENSURE_( alsa_snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &maxBufferSizeCapture ), paUnanticipatedHostError );
+                ENSURE_( alsa_snd_pcm_hw_params_get_buffer_size_max( hwParamsPlayback, &maxBufferSizePlayback ), paUnanticipatedHostError );
                 maxBufferSize = PA_MIN( maxBufferSizeCapture, maxBufferSizePlayback );
 
                 desiredBufSz = PA_MIN( desiredBufSz, maxBufferSize );
@@ -1704,14 +2555,14 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double
 
             /* Find the closest power of 2 */
             e = ilogb( minPeriodSize );
-            if( minPeriodSize & (minPeriodSize - 1) )
+            if( minPeriodSize & ( minPeriodSize - 1 ) )
                 e += 1;
             periodSize = (snd_pcm_uframes_t)pow( 2, e );
 
             while( periodSize <= maxPeriodSize )
             {
-                if( snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, periodSize, 0 ) >= 0 &&
-                        snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, periodSize, 0 ) >= 0 )
+                if( alsa_snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, periodSize, 0 ) >= 0 &&
+                        alsa_snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, periodSize, 0 ) >= 0 )
                 {
                     /* OK! */
                     break;
@@ -1731,26 +2582,26 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double
 
             while( optimalPeriodSize >= periodSize )
             {
-                if( snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, optimalPeriodSize, 0 )
-                        >= 0 && snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback,
+                if( alsa_snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, optimalPeriodSize, 0 )
+                        >= 0 && alsa_snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback,
                             optimalPeriodSize, 0 ) >= 0 )
                 {
                     break;
                 }
                 optimalPeriodSize /= 2;
             }
-        
+
             if( optimalPeriodSize > periodSize )
                 periodSize = optimalPeriodSize;
 
             if( periodSize <= maxPeriodSize )
             {
                 /* Looks good, the periodSize _should_ be acceptable by both devices */
-                ENSURE_( snd_pcm_hw_params_set_period_size( self->capture.pcm, hwParamsCapture, periodSize, 0 ),
+                ENSURE_( alsa_snd_pcm_hw_params_set_period_size( self->capture.pcm, hwParamsCapture, periodSize, 0 ),
                         paUnanticipatedHostError );
-                ENSURE_( snd_pcm_hw_params_set_period_size( self->playback.pcm, hwParamsPlayback, periodSize, 0 ),
+                ENSURE_( alsa_snd_pcm_hw_params_set_period_size( self->playback.pcm, hwParamsPlayback, periodSize, 0 ),
                         paUnanticipatedHostError );
-                self->capture.framesPerBuffer = self->playback.framesPerBuffer = periodSize;
+                self->capture.framesPerPeriod = self->playback.framesPerPeriod = periodSize;
                 framesPerHostBuffer = periodSize;
             }
             else
@@ -1759,15 +2610,15 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double
                 optimalPeriodSize = PA_MAX( desiredBufSz / numPeriods, minPeriodSize );
                 optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize );
 
-                self->capture.framesPerBuffer = optimalPeriodSize;
+                self->capture.framesPerPeriod = optimalPeriodSize;
                 dir = 0;
-                ENSURE_( snd_pcm_hw_params_set_period_size_near( self->capture.pcm, hwParamsCapture, &self->capture.framesPerBuffer, &dir ),
+                ENSURE_( alsa_snd_pcm_hw_params_set_period_size_near( self->capture.pcm, hwParamsCapture, &self->capture.framesPerPeriod, &dir ),
                         paUnanticipatedHostError );
-                self->playback.framesPerBuffer = optimalPeriodSize;
+                self->playback.framesPerPeriod = optimalPeriodSize;
                 dir = 0;
-                ENSURE_( snd_pcm_hw_params_set_period_size_near( self->playback.pcm, hwParamsPlayback, &self->playback.framesPerBuffer, &dir ),
+                ENSURE_( alsa_snd_pcm_hw_params_set_period_size_near( self->playback.pcm, hwParamsPlayback, &self->playback.framesPerPeriod, &dir ),
                         paUnanticipatedHostError );
-                framesPerHostBuffer = PA_MAX( self->capture.framesPerBuffer, self->playback.framesPerBuffer );
+                framesPerHostBuffer = PA_MAX( self->capture.framesPerPeriod, self->playback.framesPerPeriod );
                 *hostBufferSizeMode = paUtilBoundedHostBufferSize;
             }
         }
@@ -1783,7 +2634,7 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double
             snd_pcm_hw_params_t* firstHwParams = hwParamsCapture, * secondHwParams = hwParamsPlayback;
 
             dir = 0;
-            ENSURE_( snd_pcm_hw_params_get_periods_max( hwParamsPlayback, &maxPeriods, &dir ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_hw_params_get_periods_max( hwParamsPlayback, &maxPeriods, &dir ), paUnanticipatedHostError );
             if( maxPeriods < numPeriods )
             {
                 /* The playback component is trickier to get right, try that first */
@@ -1797,17 +2648,17 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double
             PA_ENSURE( PaAlsaStreamComponent_DetermineFramesPerBuffer( first, firstStreamParams, framesPerUserBuffer,
                         sampleRate, firstHwParams, &accurate ) );
 
-            second->framesPerBuffer = first->framesPerBuffer;
+            second->framesPerPeriod = first->framesPerPeriod;
             dir = 0;
-            ENSURE_( snd_pcm_hw_params_set_period_size_near( second->pcm, secondHwParams, &second->framesPerBuffer, &dir ),
+            ENSURE_( alsa_snd_pcm_hw_params_set_period_size_near( second->pcm, secondHwParams, &second->framesPerPeriod, &dir ),
                     paUnanticipatedHostError );
-            if( self->capture.framesPerBuffer == self->playback.framesPerBuffer )
+            if( self->capture.framesPerPeriod == self->playback.framesPerPeriod )
             {
-                framesPerHostBuffer = self->capture.framesPerBuffer;
+                framesPerHostBuffer = self->capture.framesPerPeriod;
             }
             else
             {
-                framesPerHostBuffer = PA_MAX( self->capture.framesPerBuffer, self->playback.framesPerBuffer );
+                framesPerHostBuffer = PA_MAX( self->capture.framesPerPeriod, self->playback.framesPerPeriod );
                 *hostBufferSizeMode = paUtilBoundedHostBufferSize;
             }
         }
@@ -1818,26 +2669,27 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double
         {
             PA_ENSURE( PaAlsaStreamComponent_DetermineFramesPerBuffer( &self->capture, inputParameters, framesPerUserBuffer,
                         sampleRate, hwParamsCapture, &accurate) );
-            framesPerHostBuffer = self->capture.framesPerBuffer;
+            framesPerHostBuffer = self->capture.framesPerPeriod;
         }
         else
         {
             assert( self->playback.pcm );
             PA_ENSURE( PaAlsaStreamComponent_DetermineFramesPerBuffer( &self->playback, outputParameters, framesPerUserBuffer,
                         sampleRate, hwParamsPlayback, &accurate ) );
-            framesPerHostBuffer = self->playback.framesPerBuffer;
+            framesPerHostBuffer = self->playback.framesPerPeriod;
         }
     }
 
     PA_UNLESS( framesPerHostBuffer != 0, paInternalError );
     self->maxFramesPerHostBuffer = framesPerHostBuffer;
 
-    if( !accurate )
+    if( !self->playback.canMmap || !accurate )
     {
         /* Don't know the exact size per host buffer */
         *hostBufferSizeMode = paUtilBoundedHostBufferSize;
         /* Raise upper bound */
-        ++self->maxFramesPerHostBuffer;
+        if( !accurate )
+            ++self->maxFramesPerHostBuffer;
     }
 
 error:
@@ -1855,8 +2707,8 @@ static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParamet
     double realSr = sampleRate;
     snd_pcm_hw_params_t* hwParamsCapture, * hwParamsPlayback;
 
-    snd_pcm_hw_params_alloca( &hwParamsCapture );
-    snd_pcm_hw_params_alloca( &hwParamsPlayback );
+    alsa_snd_pcm_hw_params_alloca( &hwParamsCapture );
+    alsa_snd_pcm_hw_params_alloca( &hwParamsPlayback );
 
     if( self->capture.pcm )
         PA_ENSURE( PaAlsaStreamComponent_InitialConfigure( &self->capture, inParams, self->primeBuffers, hwParamsCapture,
@@ -1870,17 +2722,17 @@ static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParamet
 
     if( self->capture.pcm )
     {
-        assert( self->capture.framesPerBuffer != 0 );
+        assert( self->capture.framesPerPeriod != 0 );
         PA_ENSURE( PaAlsaStreamComponent_FinishConfigure( &self->capture, hwParamsCapture, inParams, self->primeBuffers, realSr,
                     inputLatency ) );
-        PA_DEBUG(( "%s: Capture period size: %lu, latency: %f\n", __FUNCTION__, self->capture.framesPerBuffer, *inputLatency ));
+        PA_DEBUG(( "%s: Capture period size: %lu, latency: %f\n", __FUNCTION__, self->capture.framesPerPeriod, *inputLatency ));
     }
     if( self->playback.pcm )
     {
-        assert( self->playback.framesPerBuffer != 0 );
+        assert( self->playback.framesPerPeriod != 0 );
         PA_ENSURE( PaAlsaStreamComponent_FinishConfigure( &self->playback, hwParamsPlayback, outParams, self->primeBuffers, realSr,
                     outputLatency ) );
-        PA_DEBUG(( "%s: Playback period size: %lu, latency: %f\n", __FUNCTION__, self->playback.framesPerBuffer, *outputLatency ));
+        PA_DEBUG(( "%s: Playback period size: %lu, latency: %f\n", __FUNCTION__, self->playback.framesPerPeriod, *outputLatency ));
     }
 
     /* Should be exact now */
@@ -1892,16 +2744,16 @@ static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParamet
      */
     if( self->callbackMode && self->capture.pcm && self->playback.pcm )
     {
-        int err = snd_pcm_link( self->capture.pcm, self->playback.pcm );
+        int err = alsa_snd_pcm_link( self->capture.pcm, self->playback.pcm );
         if( err == 0 )
             self->pcmsSynced = 1;
         else
-            PA_DEBUG(( "%s: Unable to sync pcms: %s\n", __FUNCTION__, snd_strerror( err ) ));
+            PA_DEBUG(( "%s: Unable to sync pcms: %s\n", __FUNCTION__, alsa_snd_strerror( err ) ));
     }
 
     {
-        unsigned long minFramesPerHostBuffer = PA_MIN( self->capture.pcm ? self->capture.framesPerBuffer : ULONG_MAX,
-            self->playback.pcm ? self->playback.framesPerBuffer : ULONG_MAX );
+        unsigned long minFramesPerHostBuffer = PA_MIN( self->capture.pcm ? self->capture.framesPerPeriod : ULONG_MAX,
+            self->playback.pcm ? self->playback.framesPerPeriod : ULONG_MAX );
         self->pollTimeout = CalculatePollTimeout( self, minFramesPerHostBuffer );    /* Period in msecs, rounded up */
 
         /* Time before watchdog unthrottles realtime thread == 1/4 of period time in msecs */
@@ -1911,7 +2763,7 @@ static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParamet
     if( self->callbackMode )
     {
         /* If the user expects a certain number of frames per callback we will either have to rely on block adaption
-         * (framesPerHostBuffer is not an integer multiple of framesPerBuffer) or we can simply align the number
+         * (framesPerHostBuffer is not an integer multiple of framesPerPeriod) or we can simply align the number
          * of host buffer frames with what the user specified */
         if( self->framesPerUserBuffer != paFramesPerBufferUnspecified )
         {
@@ -1920,8 +2772,8 @@ static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParamet
             /* Unless the ratio between number of host and user buffer frames is an integer we will have to rely
              * on block adaption */
         /*
-            if( framesPerHostBuffer % framesPerBuffer != 0 || (self->capture.pcm && self->playback.pcm &&
-                        self->capture.framesPerBuffer != self->playback.framesPerBuffer) )
+            if( framesPerHostBuffer % framesPerPeriod != 0 || (self->capture.pcm && self->playback.pcm &&
+                        self->capture.framesPerPeriod != self->playback.framesPerPeriod) )
                 self->useBlockAdaption = 1;
             else
                 self->alignFrames = 1;
@@ -1954,7 +2806,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     /* XXX: Use Bounded by default? Output tends to get stuttery with Fixed ... */
     PaUtilHostBufferSizeMode hostBufferSizeMode = paUtilFixedHostBufferSize;
 
-    if( (streamFlags & paPlatformSpecificFlags) != 0 )
+    if( ( streamFlags & paPlatformSpecificFlags ) != 0 )
         return paInvalidFlag;
 
     if( inputParameters )
@@ -1975,7 +2827,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     /* XXX: Why do we support this anyway? */
     if( framesPerBuffer == paFramesPerBufferUnspecified && getenv( "PA_ALSA_PERIODSIZE" ) != NULL )
     {
-        PA_DEBUG(( "%s: Getting framesPerBuffer from environment\n", __FUNCTION__ ));
+        PA_DEBUG(( "%s: Getting framesPerBuffer (Alsa period-size) from environment\n", __FUNCTION__ ));
         framesPerBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") );
     }
 
@@ -1985,8 +2837,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 
     PA_ENSURE( PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerBuffer,
                 &inputLatency, &outputLatency, &hostBufferSizeMode ) );
-    hostInputSampleFormat = stream->capture.hostSampleFormat;
-    hostOutputSampleFormat = stream->playback.hostSampleFormat;
+    hostInputSampleFormat = stream->capture.hostSampleFormat | (!stream->capture.hostInterleaved ? paNonInterleaved : 0);
+    hostOutputSampleFormat = stream->playback.hostSampleFormat | (!stream->playback.hostInterleaved ? paNonInterleaved : 0);
 
     PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
                     numInputChannels, inputSampleFormat, hostInputSampleFormat,
@@ -1996,11 +2848,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 
     /* Ok, buffer processor is initialized, now we can deduce it's latency */
     if( numInputChannels > 0 )
-        stream->streamRepresentation.streamInfo.inputLatency = inputLatency + PaUtil_GetBufferProcessorInputLatency(
-                &stream->bufferProcessor );
+        stream->streamRepresentation.streamInfo.inputLatency = inputLatency + (PaTime)(
+                PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor ) / sampleRate);
     if( numOutputChannels > 0 )
-        stream->streamRepresentation.streamInfo.outputLatency = outputLatency + PaUtil_GetBufferProcessorOutputLatency(
-                &stream->bufferProcessor );
+        stream->streamRepresentation.streamInfo.outputLatency = outputLatency + (PaTime)(
+                PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor ) / sampleRate);
+
+    PA_DEBUG(( "%s: Stream: framesPerBuffer = %lu, maxFramesPerHostBuffer = %lu, latency i=%f, o=%f\n", __FUNCTION__, framesPerBuffer, stream->maxFramesPerHostBuffer, stream->streamRepresentation.streamInfo.inputLatency, stream->streamRepresentation.streamInfo.outputLatency));
 
     *s = (PaStream*)stream;
 
@@ -2032,19 +2886,19 @@ static PaError CloseStream( PaStream* s )
 static void SilenceBuffer( PaAlsaStream *stream )
 {
     const snd_pcm_channel_area_t *areas;
-    snd_pcm_uframes_t frames = (snd_pcm_uframes_t)snd_pcm_avail_update( stream->playback.pcm ), offset;
+    snd_pcm_uframes_t frames = (snd_pcm_uframes_t)alsa_snd_pcm_avail_update( stream->playback.pcm ), offset;
 
-    snd_pcm_mmap_begin( stream->playback.pcm, &areas, &offset, &frames );
-    snd_pcm_areas_silence( areas, offset, stream->playback.numHostChannels, frames, stream->playback.nativeFormat );
-    snd_pcm_mmap_commit( stream->playback.pcm, offset, frames );
+    alsa_snd_pcm_mmap_begin( stream->playback.pcm, &areas, &offset, &frames );
+    alsa_snd_pcm_areas_silence( areas, offset, stream->playback.numHostChannels, frames, stream->playback.nativeFormat );
+    alsa_snd_pcm_mmap_commit( stream->playback.pcm, offset, frames );
 }
 
 /** Start/prepare pcm(s) for streaming.
  *
- * Depending on wether the stream is in callback or blocking mode, we will respectively start or simply
+ * Depending on whether the stream is in callback or blocking mode, we will respectively start or simply
  * prepare the playback pcm. If the buffer has _not_ been primed, we will in callback mode prepare and
  * silence the buffer before starting playback. In blocking mode we simply prepare, as the playback will
- * be started automatically as the user writes to output. 
+ * be started automatically as the user writes to output.
  *
  * The capture pcm, however, will simply be prepared and started.
  */
@@ -2059,19 +2913,21 @@ static PaError AlsaStart( PaAlsaStream *stream, int priming )
             if( !priming )
             {
                 /* Buffer isn't primed, so prepare and silence */
-                ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
-                SilenceBuffer( stream );
+                ENSURE_( alsa_snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
+                if( stream->playback.canMmap )
+                    SilenceBuffer( stream );
             }
-            ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError );
+            if( stream->playback.canMmap )
+                ENSURE_( alsa_snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError );
         }
         else
-            ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
     }
     if( stream->capture.pcm && !stream->pcmsSynced )
     {
-        ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError );
         /* For a blocking stream we want to start capture as well, since nothing will happen otherwise */
-        ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError );
     }
 
 end:
@@ -2091,7 +2947,7 @@ static int IsRunning( PaAlsaStream *stream )
     PA_ENSURE( PaUnixMutex_Lock( &stream->stateMtx ) );
     if( stream->capture.pcm )
     {
-        snd_pcm_state_t capture_state = snd_pcm_state( stream->capture.pcm );
+        snd_pcm_state_t capture_state = alsa_snd_pcm_state( stream->capture.pcm );
 
         if( capture_state == SND_PCM_STATE_RUNNING || capture_state == SND_PCM_STATE_XRUN
                 || capture_state == SND_PCM_STATE_DRAINING )
@@ -2103,7 +2959,7 @@ static int IsRunning( PaAlsaStream *stream )
 
     if( stream->playback.pcm )
     {
-        snd_pcm_state_t playback_state = snd_pcm_state( stream->playback.pcm );
+        snd_pcm_state_t playback_state = alsa_snd_pcm_state( stream->playback.pcm );
 
         if( playback_state == SND_PCM_STATE_RUNNING || playback_state == SND_PCM_STATE_XRUN
                 || playback_state == SND_PCM_STATE_DRAINING )
@@ -2125,7 +2981,7 @@ static PaError StartStream( PaStream *s )
 {
     PaError result = paNoError;
     PaAlsaStream* stream = (PaAlsaStream*)s;
-    int streamStarted = 0;  /* So we can know wether we need to take the stream down */
+    int streamStarted = 0;  /* So we can know whether we need to take the stream down */
 
     /* Ready the processor */
     PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
@@ -2151,7 +3007,7 @@ error:
         AbortStream( stream );
     }
     stream->isActive = 0;
-    
+
     goto end;
 }
 
@@ -2160,7 +3016,7 @@ error:
 static PaError AlsaStop( PaAlsaStream *stream, int abort )
 {
     PaError result = paNoError;
-    /* XXX: snd_pcm_drain tends to lock up, avoid it until we find out more */
+    /* XXX: alsa_snd_pcm_drain tends to lock up, avoid it until we find out more */
     abort = 1;
     /*
     if( stream->capture.pcm && !strcmp( Pa_GetDeviceInfo( stream->capture.device )->name,
@@ -2179,11 +3035,13 @@ static PaError AlsaStop( PaAlsaStream *stream, int abort )
     {
         if( stream->playback.pcm )
         {
-            ENSURE_( snd_pcm_drop( stream->playback.pcm ), paUnanticipatedHostError );
+PA_DEBUG(( "%s: Before dropping\n", __FUNCTION__ ));
+            ENSURE_( alsa_snd_pcm_drop( stream->playback.pcm ), paUnanticipatedHostError );
+PA_DEBUG(( "%s: After dropping\n", __FUNCTION__ ));
         }
         if( stream->capture.pcm && !stream->pcmsSynced )
         {
-            ENSURE_( snd_pcm_drop( stream->capture.pcm ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_drop( stream->capture.pcm ), paUnanticipatedHostError );
         }
 
         PA_DEBUG(( "%s: Dropped frames\n", __FUNCTION__ ));
@@ -2192,8 +3050,8 @@ static PaError AlsaStop( PaAlsaStream *stream, int abort )
     {
         if( stream->playback.pcm )
         {
-            ENSURE_( snd_pcm_nonblock( stream->playback.pcm, 0 ), paUnanticipatedHostError );
-            if( snd_pcm_drain( stream->playback.pcm ) < 0 )
+            ENSURE_( alsa_snd_pcm_nonblock( stream->playback.pcm, 0 ), paUnanticipatedHostError );
+            if( alsa_snd_pcm_drain( stream->playback.pcm ) < 0 )
             {
                 PA_DEBUG(( "%s: Draining playback handle failed!\n", __FUNCTION__ ));
             }
@@ -2201,7 +3059,7 @@ static PaError AlsaStop( PaAlsaStream *stream, int abort )
         if( stream->capture.pcm && !stream->pcmsSynced )
         {
             /* We don't need to retrieve any remaining frames */
-            if( snd_pcm_drain( stream->capture.pcm ) < 0 )
+            if( alsa_snd_pcm_drain( stream->capture.pcm ) < 0 )
             {
                 PA_DEBUG(( "%s: Draining capture handle failed!\n", __FUNCTION__ ));
             }
@@ -2216,7 +3074,7 @@ error:
 
 /** Stop or abort stream.
  *
- * If a stream is in callback mode we will have to inspect wether the background thread has
+ * If a stream is in callback mode we will have to inspect whether the background thread has
  * finished, or we will have to take it out. In either case we join the thread before
  * returning. In blocking mode, we simply tell ALSA to stop abruptly (abort) or finish
  * buffers (drain)
@@ -2299,7 +3157,7 @@ static PaTime GetStreamTime( PaStream *s )
 
     snd_timestamp_t timestamp;
     snd_pcm_status_t* status;
-    snd_pcm_status_alloca( &status );
+    alsa_snd_pcm_status_alloca( &status );
 
     /* TODO: what if we have both?  does it really matter? */
 
@@ -2309,14 +3167,14 @@ static PaTime GetStreamTime( PaStream *s )
 
     if( stream->capture.pcm )
     {
-        snd_pcm_status( stream->capture.pcm, status );
+        alsa_snd_pcm_status( stream->capture.pcm, status );
     }
     else if( stream->playback.pcm )
     {
-        snd_pcm_status( stream->playback.pcm, status );
+        alsa_snd_pcm_status( stream->playback.pcm, status );
     }
 
-    snd_pcm_status_get_tstamp( status, &timestamp );
+    alsa_snd_pcm_status_get_tstamp( status, &timestamp );
     return timestamp.tv_sec + (PaTime)timestamp.tv_usec / 1e6;
 }
 
@@ -2327,37 +3185,48 @@ static double GetStreamCpuLoad( PaStream* s )
     return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
 }
 
+/* Set the stream sample rate to a nominal value requested; allow only a defined tolerance range */
 static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate )
 {
-    unsigned long approx = (unsigned long) sampleRate;
-    int dir = 0;
-    double fraction = sampleRate - approx;
+    PaError result = paNoError;
+    unsigned int reqRate, setRate, deviation;
 
     assert( pcm && hwParams );
 
-    if( fraction > 0.0 )
+    /* The Alsa sample rate is set by integer value; also the actual rate may differ */
+    reqRate = setRate = (unsigned int) sampleRate;
+
+    ENSURE_( alsa_snd_pcm_hw_params_set_rate_near( pcm, hwParams, &setRate, NULL ), paUnanticipatedHostError );
+    /* The value actually set will be put in 'setRate' (may be way off); check the deviation as a proportion
+     * of the requested-rate with reference to the max-deviate-ratio (larger values allow less deviation) */
+    deviation = abs( setRate - reqRate );
+    if( deviation > 0 && deviation * RATE_MAX_DEVIATE_RATIO > reqRate )
+        result = paInvalidSampleRate;
+
+end:
+    return result;
+
+error:
+    /* Log */
     {
-        if( fraction > 0.5 )
-        {
-            ++approx;
-            dir = -1;
-        }
-        else
-            dir = 1;
+        unsigned int _min = 0, _max = 0;
+        int _dir = 0;
+        ENSURE_( alsa_snd_pcm_hw_params_get_rate_min( hwParams, &_min, &_dir ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_hw_params_get_rate_max( hwParams, &_max, &_dir ), paUnanticipatedHostError );
+        PA_DEBUG(( "%s: SR min = %u, max = %u, req = %u\n", __FUNCTION__, _min, _max, reqRate ));
     }
-
-    return snd_pcm_hw_params_set_rate( pcm, hwParams, approx, dir );
+    goto end;
 }
 
 /* Return exact sample rate in param sampleRate */
 static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate )
 {
-    unsigned int num, den;
-    int err; 
+    unsigned int num, den = 1;
+    int err;
 
     assert( hwParams );
 
-    err = snd_pcm_hw_params_get_rate_numden( hwParams, &num, &den );
+    err = alsa_snd_pcm_hw_params_get_rate_numden( hwParams, &num, &den );
     *sampleRate = (double) num / den;
 
     return err;
@@ -2391,29 +3260,56 @@ static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self )
     snd_pcm_status_t *st;
     PaTime now = PaUtil_GetTime();
     snd_timestamp_t t;
+    int restartAlsa = 0; /* do not restart Alsa by default */
 
-    snd_pcm_status_alloca( &st );
+    alsa_snd_pcm_status_alloca( &st );
 
     if( self->playback.pcm )
     {
-        snd_pcm_status( self->playback.pcm, st );
-        if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN )
+        alsa_snd_pcm_status( self->playback.pcm, st );
+        if( alsa_snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN )
         {
-            snd_pcm_status_get_trigger_tstamp( st, &t );
-            self->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000);
+            alsa_snd_pcm_status_get_trigger_tstamp( st, &t );
+            self->underrun = now * 1000 - ( (PaTime)t.tv_sec * 1000 + (PaTime)t.tv_usec / 1000 );
+
+            if( !self->playback.canMmap )
+            {
+                if( alsa_snd_pcm_recover( self->playback.pcm, -EPIPE, 0 ) < 0 )
+                {
+                    PA_DEBUG(( "%s: [playback] non-MMAP-PCM failed recovering from XRUN, will restart Alsa\n", __FUNCTION__ ));
+                    ++ restartAlsa; /* did not manage to recover */
+                }
+            }
+            else
+                ++ restartAlsa; /* always restart MMAPed device */
         }
     }
     if( self->capture.pcm )
     {
-        snd_pcm_status( self->capture.pcm, st );
-        if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN )
+        alsa_snd_pcm_status( self->capture.pcm, st );
+        if( alsa_snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN )
         {
-            snd_pcm_status_get_trigger_tstamp( st, &t );
+            alsa_snd_pcm_status_get_trigger_tstamp( st, &t );
             self->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000);
+
+            if (!self->capture.canMmap)
+            {
+                if (alsa_snd_pcm_recover( self->capture.pcm, -EPIPE, 0 ) < 0)
+                {
+                    PA_DEBUG(( "%s: [capture] non-MMAP-PCM failed recovering from XRUN, will restart Alsa\n", __FUNCTION__ ));
+                    ++ restartAlsa; /* did not manage to recover */
+                }
+            }
+            else
+                ++ restartAlsa; /* always restart MMAPed device */
         }
     }
 
-    PA_ENSURE( AlsaRestart( self ) );
+    if( restartAlsa )
+    {
+        PA_DEBUG(( "%s: restarting Alsa to recover from XRUN\n", __FUNCTION__ ));
+        PA_ENSURE( AlsaRestart( self ) );
+    }
 
 end:
     return result;
@@ -2422,7 +3318,7 @@ error:
 }
 
 /** Decide if we should continue polling for specified direction, eventually adjust the poll timeout.
- * 
+ *
  */
 static PaError ContinuePoll( const PaAlsaStream *stream, StreamDirection streamDir, int *pollTimeout, int *continuePoll )
 {
@@ -2444,8 +3340,8 @@ static PaError ContinuePoll( const PaAlsaStream *stream, StreamDirection streamD
         otherComponent = &stream->capture;
     }
 
-    /* ALSA docs say that negative delay should indicate xrun, but in my experience snd_pcm_delay returns -EPIPE */
-    if( (err = snd_pcm_delay( otherComponent->pcm, &delay )) < 0 )
+    /* ALSA docs say that negative delay should indicate xrun, but in my experience alsa_snd_pcm_delay returns -EPIPE */
+    if( ( err = alsa_snd_pcm_delay( otherComponent->pcm, &delay ) ) < 0 )
     {
         if( err == -EPIPE )
         {
@@ -2460,16 +3356,16 @@ static PaError ContinuePoll( const PaAlsaStream *stream, StreamDirection streamD
     if( StreamDirection_Out == streamDir )
     {
         /* Number of eligible frames before capture overrun */
-        delay = otherComponent->bufferSize - delay;
+        delay = otherComponent->alsaBufferSize - delay;
     }
-    margin = delay - otherComponent->framesPerBuffer / 2;
+    margin = delay - otherComponent->framesPerPeriod / 2;
 
     if( margin < 0 )
     {
         PA_DEBUG(( "%s: Stopping poll for %s\n", __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback" ));
         *continuePoll = 0;
     }
-    else if( margin < otherComponent->framesPerBuffer )
+    else if( margin < otherComponent->framesPerPeriod )
     {
         *pollTimeout = CalculatePollTimeout( stream, margin );
         PA_DEBUG(( "%s: Trying to poll again for %s frames, pollTimeout: %d\n",
@@ -2493,7 +3389,7 @@ static void OnExit( void *data )
     stream->callback_finished = 1;  /* Let the outside world know stream was stopped in callback */
     PA_DEBUG(( "%s: Stopping ALSA handles\n", __FUNCTION__ ));
     AlsaStop( stream, stream->callbackAbort );
-    
+
     PA_DEBUG(( "%s: Stoppage\n", __FUNCTION__ ));
 
     /* Eventually notify user all buffers have played */
@@ -2510,21 +3406,21 @@ static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *t
     snd_timestamp_t capture_timestamp, playback_timestamp;
     PaTime capture_time = 0., playback_time = 0.;
 
-    snd_pcm_status_alloca( &capture_status );
-    snd_pcm_status_alloca( &playback_status );
+    alsa_snd_pcm_status_alloca( &capture_status );
+    alsa_snd_pcm_status_alloca( &playback_status );
 
     if( stream->capture.pcm )
     {
         snd_pcm_sframes_t capture_delay;
 
-        snd_pcm_status( stream->capture.pcm, capture_status );
-        snd_pcm_status_get_tstamp( capture_status, &capture_timestamp );
+        alsa_snd_pcm_status( stream->capture.pcm, capture_status );
+        alsa_snd_pcm_status_get_tstamp( capture_status, &capture_timestamp );
 
         capture_time = capture_timestamp.tv_sec +
-            ((PaTime)capture_timestamp.tv_usec / 1000000.0);
+            ( (PaTime)capture_timestamp.tv_usec / 1000000.0 );
         timeInfo->currentTime = capture_time;
 
-        capture_delay = snd_pcm_status_get_delay( capture_status );
+        capture_delay = alsa_snd_pcm_status_get_delay( capture_status );
         timeInfo->inputBufferAdcTime = timeInfo->currentTime -
             (PaTime)capture_delay / stream->streamRepresentation.streamInfo.sampleRate;
     }
@@ -2532,8 +3428,8 @@ static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *t
     {
         snd_pcm_sframes_t playback_delay;
 
-        snd_pcm_status( stream->playback.pcm, playback_status );
-        snd_pcm_status_get_tstamp( playback_status, &playback_timestamp );
+        alsa_snd_pcm_status( stream->playback.pcm, playback_status );
+        alsa_snd_pcm_status_get_tstamp( playback_status, &playback_timestamp );
 
         playback_time = playback_timestamp.tv_sec +
             ((PaTime)playback_timestamp.tv_usec / 1000000.0);
@@ -2543,12 +3439,12 @@ static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *t
             /* Hmm, we have both a playback and a capture timestamp.
              * Hopefully they are the same... */
             if( fabs( capture_time - playback_time ) > 0.01 )
-                PA_DEBUG(("Capture time and playback time differ by %f\n", fabs(capture_time-playback_time)));
+                PA_DEBUG(( "Capture time and playback time differ by %f\n", fabs( capture_time-playback_time ) ));
         }
         else
             timeInfo->currentTime = playback_time;
 
-        playback_delay = snd_pcm_status_get_delay( playback_status );
+        playback_delay = alsa_snd_pcm_status_get_delay( playback_status );
         timeInfo->outputBufferDacTime = timeInfo->currentTime +
             (PaTime)playback_delay / stream->streamRepresentation.streamInfo.sampleRate;
     }
@@ -2564,7 +3460,7 @@ static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *t
 static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, unsigned long numFrames, int *xrun )
 {
     PaError result = paNoError;
-    int res;
+    int res = 0;
 
     /* @concern FullDuplex It is possible that only one direction is marked ready after polling, and processed
      * afterwards
@@ -2572,7 +3468,29 @@ static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self,
     if( !self->ready )
         goto end;
 
-    res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames );
+    if( !self->canMmap && StreamDirection_Out == self->streamDir )
+    {
+        /* Play sound */
+        if( self->hostInterleaved )
+            res = alsa_snd_pcm_writei( self->pcm, self->nonMmapBuffer, numFrames );
+        else
+        {
+            void *bufs[self->numHostChannels];
+            int bufsize = alsa_snd_pcm_format_size( self->nativeFormat, self->framesPerPeriod + 1 );
+            unsigned char *buffer = self->nonMmapBuffer;
+            int i;
+            for( i = 0; i < self->numHostChannels; ++i )
+            {
+                bufs[i] = buffer;
+                buffer += bufsize;
+            }
+            res = alsa_snd_pcm_writen( self->pcm, bufs, numFrames );
+        }
+    }
+
+    if( self->canMmap )
+        res = alsa_snd_pcm_mmap_commit( self->pcm, self->offset, numFrames );
+
     if( res == -EPIPE || res == -ESTRPIPE )
     {
         *xrun = 1;
@@ -2590,7 +3508,7 @@ error:
 /* Extract buffer from channel area */
 static unsigned char *ExtractAddress( const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset )
 {
-    return (unsigned char *) area->addr + (area->first + offset * area->step) / 8;
+    return (unsigned char *) area->addr + ( area->first + offset * area->step ) / 8;
 }
 
 /** Do necessary adaption between user and host channels.
@@ -2605,14 +3523,14 @@ static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *s
     int i;
     int unusedChans = self->numHostChannels - self->numUserChannels;
     unsigned char *src, *dst;
-    int convertMono = (self->numHostChannels % 2) == 0 && (self->numUserChannels % 2) != 0;
+    int convertMono = ( self->numHostChannels % 2 ) == 0 && ( self->numUserChannels % 2 ) != 0;
 
     assert( StreamDirection_Out == self->streamDir );
 
     if( self->hostInterleaved )
     {
-        int swidth = snd_pcm_format_size( self->nativeFormat, 1 );
-        unsigned char *buffer = ExtractAddress( self->channelAreas, self->offset );
+        int swidth = alsa_snd_pcm_format_size( self->nativeFormat, 1 );
+        unsigned char *buffer = self->canMmap ? ExtractAddress( self->channelAreas, self->offset ) : self->nonMmapBuffer;
 
         /* Start after the last user channel */
         p = buffer + self->numUserChannels * swidth;
@@ -2620,7 +3538,7 @@ static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *s
         if( convertMono )
         {
             /* Convert the last user channel into stereo pair */
-            src = buffer + (self->numUserChannels - 1) * swidth;
+            src = buffer + ( self->numUserChannels - 1 ) * swidth;
             for( i = 0; i < numFrames; ++i )
             {
                 dst = src + swidth;
@@ -2648,13 +3566,13 @@ static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *s
         /* We extract the last user channel */
         if( convertMono )
         {
-            ENSURE_( snd_pcm_area_copy( self->channelAreas + self->numUserChannels, self->offset, self->channelAreas +
-                    (self->numUserChannels - 1), self->offset, numFrames, self->nativeFormat ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_area_copy( self->channelAreas + self->numUserChannels, self->offset, self->channelAreas +
+                    ( self->numUserChannels - 1 ), self->offset, numFrames, self->nativeFormat ), paUnanticipatedHostError );
             --unusedChans;
         }
         if( unusedChans > 0 )
         {
-            snd_pcm_areas_silence( self->channelAreas + (self->numHostChannels - unusedChans), self->offset, unusedChans, numFrames,
+            alsa_snd_pcm_areas_silence( self->channelAreas + ( self->numHostChannels - unusedChans ), self->offset, unusedChans, numFrames,
                     self->nativeFormat );
         }
     }
@@ -2692,7 +3610,7 @@ error:
 static PaError PaAlsaStreamComponent_GetAvailableFrames( PaAlsaStreamComponent *self, unsigned long *numFrames, int *xrunOccurred )
 {
     PaError result = paNoError;
-    snd_pcm_sframes_t framesAvail = snd_pcm_avail_update( self->pcm );
+    snd_pcm_sframes_t framesAvail = alsa_snd_pcm_avail_update( self->pcm );
     *xrunOccurred = 0;
 
     if( -EPIPE == framesAvail )
@@ -2716,7 +3634,7 @@ error:
 static PaError PaAlsaStreamComponent_BeginPolling( PaAlsaStreamComponent* self, struct pollfd* pfds )
 {
     PaError result = paNoError;
-    int ret = snd_pcm_poll_descriptors( self->pcm, pfds, self->nfds );
+    int ret = alsa_snd_pcm_poll_descriptors( self->pcm, pfds, self->nfds );
     (void)ret;  /* Prevent unused variable warning if asserts are turned off */
     assert( ret == self->nfds );
 
@@ -2736,18 +3654,33 @@ static PaError PaAlsaStreamComponent_EndPolling( PaAlsaStreamComponent* self, st
     PaError result = paNoError;
     unsigned short revents;
 
-    ENSURE_( snd_pcm_poll_descriptors_revents( self->pcm, pfds, self->nfds, &revents ), paUnanticipatedHostError );
+    ENSURE_( alsa_snd_pcm_poll_descriptors_revents( self->pcm, pfds, self->nfds, &revents ), paUnanticipatedHostError );
     if( revents != 0 )
     {
         if( revents & POLLERR )
         {
             *xrun = 1;
         }
+        else if( revents & POLLHUP )
+        {
+            *xrun = 1;
+            PA_DEBUG(( "%s: revents has POLLHUP, processing as XRUN\n", __FUNCTION__ ));
+        }
         else
             self->ready = 1;
 
         *shouldPoll = 0;
     }
+    else /* (A zero revent occurred) */
+        /* Work around an issue with Alsa older than 1.0.16 using some plugins (eg default with plug + dmix) where
+         * POLLIN or POLLOUT are zeroed by Alsa-lib if _mmap_avail() is a few frames short of avail_min at period
+         * boundary, possibly due to erratic dma interrupts at period boundary?  Treat as a valid event.
+         */
+        if( self->useReventFix )
+        {
+            self->ready = 1;
+            *shouldPoll = 0;
+        }
 
 error:
     return result;
@@ -2819,13 +3752,14 @@ error:
  *
  * @param framesAvail Return the number of available frames
  * @param xrunOccurred Return whether an xrun has occurred
- */ 
+ */
 static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *framesAvail, int *xrunOccurred )
 {
     PaError result = paNoError;
     int pollPlayback = self->playback.pcm != NULL, pollCapture = self->capture.pcm != NULL;
     int pollTimeout = self->pollTimeout;
-    int xrun = 0;
+    int xrun = 0, timeouts = 0;
+    int pollResults;
 
     assert( self );
     assert( framesAvail );
@@ -2857,8 +3791,9 @@ static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *fr
         int totalFds = 0;
         struct pollfd *capturePfds = NULL, *playbackPfds = NULL;
 
+#ifdef PTHREAD_CANCELED
         pthread_testcancel();
-
+#endif
         if( pollCapture )
         {
             capturePfds = self->pfds;
@@ -2867,36 +3802,70 @@ static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *fr
         }
         if( pollPlayback )
         {
-            playbackPfds = self->pfds + (self->capture.pcm ? self->capture.nfds : 0);
+            /* self->pfds is in effect an array of fds; if necessary, index past the capture fds */
+            playbackPfds = self->pfds + (pollCapture ? self->capture.nfds : 0);
             PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->playback, playbackPfds ) );
             totalFds += self->playback.nfds;
         }
-        
-        if( poll( self->pfds, totalFds, pollTimeout ) < 0 )
+
+        pollResults = poll( self->pfds, totalFds, pollTimeout );
+
+        if( pollResults < 0 )
         {
             /*  XXX: Depend on preprocessor condition? */
             if( errno == EINTR )
             {
                 /* gdb */
+                Pa_Sleep( 1 ); /* avoid hot loop */
                 continue;
             }
 
             /* TODO: Add macro for checking system calls */
             PA_ENSURE( paInternalError );
         }
-
-        /* check the return status of our pfds */
-        if( pollCapture )
-        {
-            PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->capture, capturePfds, &pollCapture, &xrun ) );
-        }
-        if( pollPlayback )
+        else if( pollResults == 0 )
         {
-            PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->playback, playbackPfds, &pollPlayback, &xrun ) );
+           /* Suspended, paused or failed device can provide 0 poll results. To avoid deadloop in such situation
+            * we simply run counter 'timeouts' which detects 0 poll result and accumulates. As soon as 2048 timouts (around 2 seconds)
+            * are achieved we simply fail function with paTimedOut to notify waiting methods that device is not capable
+            * of providing audio data anymore and needs some corresponding recovery action.
+            * Note that 'timeouts' is reset to 0 if poll() managed to return non 0 results.
+            */
+
+            /*PA_DEBUG(( "%s: poll == 0 results, timed out, %d times left\n", __FUNCTION__, 2048 - timeouts ));*/
+            ++ timeouts;
+            if( timeouts > 1 ) /* sometimes device times out, but normally once, so we do not sleep any time */
+            {
+                Pa_Sleep( 1 ); /* avoid hot loop */
+            }
+            /* not else ! */
+            if( timeouts >= 2048 ) /* audio device not working, shall return error to notify waiters */
+            {
+                *framesAvail = 0; /* no frames available for processing */
+                xrun = 1; /* try recovering device */
+
+                PA_DEBUG(( "%s: poll timed out\n", __FUNCTION__, timeouts ));
+                goto end;/*PA_ENSURE( paTimedOut );*/
+            }
         }
-        if( xrun )
+        else if( pollResults > 0 )
         {
-            break;
+            /* reset timouts counter */
+            timeouts = 0;
+
+            /* check the return status of our pfds */
+            if( pollCapture )
+            {
+                PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->capture, capturePfds, &pollCapture, &xrun ) );
+            }
+            if( pollPlayback )
+            {
+                PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->playback, playbackPfds, &pollPlayback, &xrun ) );
+            }
+            if( xrun )
+            {
+                break;
+            }
         }
 
         /* @concern FullDuplex If only one of two pcms is ready we may want to compromise between the two.
@@ -2933,7 +3902,7 @@ static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *fr
             {
                 /* Drop input, a period's worth */
                 assert( self->capture.ready );
-                PaAlsaStreamComponent_EndProcessing( &self->capture, PA_MIN( self->capture.framesPerBuffer,
+                PaAlsaStreamComponent_EndProcessing( &self->capture, PA_MIN( self->capture.framesPerPeriod,
                             *framesAvail ), &xrun );
                 *framesAvail = 0;
                 self->capture.ready = 0;
@@ -2970,7 +3939,7 @@ error:
  *
  * Mmapped buffer space is acquired from ALSA, and registered with the buffer processor. Differences between the
  * number of host and user channels is taken into account.
- * 
+ *
  * @param numFrames On entrance the number of requested frames, on exit the number of contiguously accessible frames.
  */
 static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent* self, PaUtilBufferProcessor* bp,
@@ -2992,13 +3961,31 @@ static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent* se
         goto end;
     }
 
-    ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError );
+    if( self->canMmap )
+    {
+        ENSURE_( alsa_snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError );
+        /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */
+        self->channelAreas = (snd_pcm_channel_area_t *)areas;
+    }
+    else
+    {
+        unsigned int bufferSize = self->numHostChannels * alsa_snd_pcm_format_size( self->nativeFormat, *numFrames );
+        if( bufferSize > self->nonMmapBufferSize )
+        {
+            self->nonMmapBuffer = realloc( self->nonMmapBuffer, ( self->nonMmapBufferSize = bufferSize ) );
+            if( !self->nonMmapBuffer )
+            {
+                result = paInsufficientMemory;
+                goto error;
+            }
+        }
+    }
 
     if( self->hostInterleaved )
     {
-        int swidth = snd_pcm_format_size( self->nativeFormat, 1 );
+        int swidth = alsa_snd_pcm_format_size( self->nativeFormat, 1 );
 
-        p = buffer = ExtractAddress( areas, self->offset );
+        p = buffer = self->canMmap ? ExtractAddress( areas, self->offset ) : self->nonMmapBuffer;
         for( i = 0; i < self->numUserChannels; ++i )
         {
             /* We're setting the channels up to userChannels, but the stride will be hostChannels samples */
@@ -3008,16 +3995,52 @@ static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent* se
     }
     else
     {
-        for( i = 0; i < self->numUserChannels; ++i )
+        if( self->canMmap )
         {
-            area = areas + i;
-            buffer = ExtractAddress( area, self->offset );
-            setChannel( bp, i, buffer, 1 );
+            for( i = 0; i < self->numUserChannels; ++i )
+            {
+                area = areas + i;
+                buffer = ExtractAddress( area, self->offset );
+                setChannel( bp, i, buffer, 1 );
+            }
+        }
+        else
+        {
+            unsigned int buf_per_ch_size = self->nonMmapBufferSize / self->numHostChannels;
+            buffer = self->nonMmapBuffer;
+            for( i = 0; i < self->numUserChannels; ++i )
+            {
+                setChannel( bp, i, buffer, 1 );
+                buffer += buf_per_ch_size;
+            }
         }
     }
 
-    /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */
-    self->channelAreas = (snd_pcm_channel_area_t *)areas;
+    if( !self->canMmap && StreamDirection_In == self->streamDir )
+    {
+        /* Read sound */
+        int res;
+        if( self->hostInterleaved )
+            res = alsa_snd_pcm_readi( self->pcm, self->nonMmapBuffer, *numFrames );
+        else
+        {
+            void *bufs[self->numHostChannels];
+            unsigned int buf_per_ch_size = self->nonMmapBufferSize / self->numHostChannels;
+            unsigned char *buffer = self->nonMmapBuffer;
+            int i;
+            for( i = 0; i < self->numHostChannels; ++i )
+            {
+                bufs[i] = buffer;
+                buffer += buf_per_ch_size;
+            }
+            res = alsa_snd_pcm_readn( self->pcm, bufs, *numFrames );
+        }
+        if( res == -EPIPE || res == -ESTRPIPE )
+        {
+            *xrun = 1;
+            *numFrames = 0;
+        }
+    }
 
 end:
 error:
@@ -3054,13 +4077,13 @@ static PaError PaAlsaStream_SetUpBuffers( PaAlsaStream* self, unsigned long* num
     if( self->capture.pcm && self->capture.ready )
     {
         captureFrames = *numFrames;
-        PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->capture, &self->bufferProcessor, &captureFrames, 
+        PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->capture, &self->bufferProcessor, &captureFrames,
                     &xrun ) );
     }
     if( self->playback.pcm && self->playback.ready )
     {
         playbackFrames = *numFrames;
-        PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->playback, &self->bufferProcessor, &playbackFrames, 
+        PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->playback, &self->bufferProcessor, &playbackFrames,
                     &xrun ) );
     }
     if( xrun )
@@ -3085,7 +4108,7 @@ static PaError PaAlsaStream_SetUpBuffers( PaAlsaStream* self, unsigned long* num
         {
             PA_DEBUG(( "%s: playbackFrames: %lu, playback.ready: %d\n", __FUNCTION__, playbackFrames, self->playback.ready ));
         }
-        
+
         commonFrames = 0;
         goto end;
     }
@@ -3122,7 +4145,7 @@ static PaError PaAlsaStream_SetUpBuffers( PaAlsaStream* self, unsigned long* num
             PaUtil_SetNoOutput( &self->bufferProcessor );
         }
     }
-    
+
 end:
     *numFrames = commonFrames;
 error:
@@ -3168,17 +4191,17 @@ static void *CallbackThreadFunc( void *userData )
     if( stream->primeBuffers )
     {
         snd_pcm_sframes_t avail;
-        
+
         if( stream->playback.pcm )
-            ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
         if( stream->capture.pcm && !stream->pcmsSynced )
-            ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError );
 
         /* We can't be certain that the whole ring buffer is available for priming, but there should be
          * at least one period */
-        avail = snd_pcm_avail_update( stream->playback.pcm );
-        startThreshold = avail - (avail % stream->playback.framesPerBuffer);
-        assert( startThreshold >= stream->playback.framesPerBuffer );
+        avail = alsa_snd_pcm_avail_update( stream->playback.pcm );
+        startThreshold = avail - (avail % stream->playback.framesPerPeriod);
+        assert( startThreshold >= stream->playback.framesPerPeriod );
     }
     else
     {
@@ -3195,7 +4218,9 @@ static void *CallbackThreadFunc( void *userData )
         unsigned long framesAvail, framesGot;
         int xrun = 0;
 
+#ifdef PTHREAD_CANCELED
         pthread_testcancel();
+#endif
 
         /* @concern StreamStop if the main thread has requested a stop and the stream has not been effectively
          * stopped we signal this condition by modifying callbackResult (we'll want to flush buffered output).
@@ -3208,10 +4233,10 @@ static void *CallbackThreadFunc( void *userData )
 
         if( paContinue != callbackResult )
         {
-            stream->callbackAbort = (paAbort == callbackResult);
+            stream->callbackAbort = ( paAbort == callbackResult );
             if( stream->callbackAbort ||
                     /** @concern BlockAdaption: Go on if adaption buffers are empty */
-                    PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) 
+                    PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
             {
                 goto end;
             }
@@ -3243,7 +4268,9 @@ static void *CallbackThreadFunc( void *userData )
         {
             xrun = 0;
 
-            pthread_testcancel();
+#ifdef PTHREAD_CANCELED
+           pthread_testcancel();
+#endif
 
             /** @concern Xruns Under/overflows are to be reported to the callback */
             if( stream->underrun > 0.0 )
@@ -3318,13 +4345,16 @@ static void *CallbackThreadFunc( void *userData )
         }
     }
 
+end:
+    ; /* Hack to fix "label at end of compound statement" error caused by pthread_cleanup_pop(1) macro. */
     /* Match pthread_cleanup_push */
     pthread_cleanup_pop( 1 );
 
-end:
     PA_DEBUG(( "%s: Thread %d exiting\n ", __FUNCTION__, pthread_self() ));
     PaUnixThreading_EXIT( result );
+
 error:
+    PA_DEBUG(( "%s: Thread %d is canceled due to error %d\n ", __FUNCTION__, pthread_self(), result ));
     goto end;
 }
 
@@ -3363,9 +4393,9 @@ static PaError ReadStream( PaStream* s, void *buffer, unsigned long frames )
     }
 
     /* Start stream if in prepared state */
-    if( snd_pcm_state( stream->capture.pcm ) == SND_PCM_STATE_PREPARED )
+    if( alsa_snd_pcm_state( stream->capture.pcm ) == SND_PCM_STATE_PREPARED )
     {
-        ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError );
+        ENSURE_( alsa_snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError );
     }
 
     while( frames > 0 )
@@ -3398,7 +4428,7 @@ static PaError WriteStream( PaStream* s, const void *buffer, unsigned long frame
     snd_pcm_uframes_t framesGot, framesAvail;
     const void *userBuffer;
     snd_pcm_t *save = stream->capture.pcm;
-    
+
     assert( stream );
 
     PA_UNLESS( stream->playback.pcm, paCanNotWriteToAnInputOnlyStream );
@@ -3441,12 +4471,12 @@ static PaError WriteStream( PaStream* s, const void *buffer, unsigned long frame
         /* Frames residing in buffer */
         PA_ENSURE( err = GetStreamWriteAvailable( stream ) );
         framesAvail = err;
-        hwAvail = stream->playback.bufferSize - framesAvail;
+        hwAvail = stream->playback.alsaBufferSize - framesAvail;
 
-        if( snd_pcm_state( stream->playback.pcm ) == SND_PCM_STATE_PREPARED &&
-                hwAvail >= stream->playback.framesPerBuffer )
+        if( alsa_snd_pcm_state( stream->playback.pcm ) == SND_PCM_STATE_PREPARED &&
+                hwAvail >= stream->playback.framesPerPeriod )
         {
-            ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError );
+            ENSURE_( alsa_snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError );
         }
     }
 
@@ -3493,7 +4523,7 @@ static signed long GetStreamWriteAvailable( PaStream* s )
         snd_pcm_sframes_t savail;
 
         PA_ENSURE( PaAlsaStream_HandleXrun( stream ) );
-        savail = snd_pcm_avail_update( stream->playback.pcm );
+        savail = alsa_snd_pcm_avail_update( stream->playback.pcm );
 
         /* savail should not contain -EPIPE now, since PaAlsaStream_HandleXrun will only prepare the pcm */
         ENSURE_( savail, paUnanticipatedHostError );
@@ -3531,35 +4561,66 @@ void PaAlsa_EnableWatchdog( PaStream *s, int enable )
 }
 #endif
 
-PaError PaAlsa_GetStreamInputCard(PaStream* s, int* card) {
-    PaAlsaStream *stream = (PaAlsaStream *) s;
-    snd_pcm_info_t* pcmInfo;
+static PaError GetAlsaStreamPointer( PaStream* s, PaAlsaStream** stream )
+{
+    PaError result = paNoError;
+    PaUtilHostApiRepresentation* hostApi;
+    PaAlsaHostApiRepresentation* alsaHostApi;
+
+    PA_ENSURE( PaUtil_ValidateStreamPointer( s ) );
+    PA_ENSURE( PaUtil_GetHostApiRepresentation( &hostApi, paALSA ) );
+    alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi;
+
+    PA_UNLESS( PA_STREAM_REP( s )->streamInterface == &alsaHostApi->callbackStreamInterface
+            || PA_STREAM_REP( s )->streamInterface == &alsaHostApi->blockingStreamInterface,
+        paIncompatibleStreamHostApi );
+
+    *stream = (PaAlsaStream*)s;
+error:
+    return paNoError;
+}
+
+PaError PaAlsa_GetStreamInputCard( PaStream* s, int* card )
+{
+    PaAlsaStream *stream;
     PaError result = paNoError;
+    snd_pcm_info_t* pcmInfo;
+
+    PA_ENSURE( GetAlsaStreamPointer( s, &stream ) );
 
     /* XXX: More descriptive error? */
     PA_UNLESS( stream->capture.pcm, paDeviceUnavailable );
 
-    snd_pcm_info_alloca( &pcmInfo );
-    PA_ENSURE( snd_pcm_info( stream->capture.pcm, pcmInfo ) );
-    *card = snd_pcm_info_get_card( pcmInfo );
+    alsa_snd_pcm_info_alloca( &pcmInfo );
+    PA_ENSURE( alsa_snd_pcm_info( stream->capture.pcm, pcmInfo ) );
+    *card = alsa_snd_pcm_info_get_card( pcmInfo );
 
 error:
     return result;
 }
 
-PaError PaAlsa_GetStreamOutputCard(PaStream* s, int* card) {
-    PaAlsaStream *stream = (PaAlsaStream *) s;
-    snd_pcm_info_t* pcmInfo;
+PaError PaAlsa_GetStreamOutputCard( PaStream* s, int* card )
+{
+    PaAlsaStream *stream;
     PaError result = paNoError;
+    snd_pcm_info_t* pcmInfo;
+
+    PA_ENSURE( GetAlsaStreamPointer( s, &stream ) );
 
     /* XXX: More descriptive error? */
     PA_UNLESS( stream->playback.pcm, paDeviceUnavailable );
 
-    snd_pcm_info_alloca( &pcmInfo );
-    PA_ENSURE( snd_pcm_info( stream->playback.pcm, pcmInfo ) );
-    *card = snd_pcm_info_get_card( pcmInfo );
+    alsa_snd_pcm_info_alloca( &pcmInfo );
+    PA_ENSURE( alsa_snd_pcm_info( stream->playback.pcm, pcmInfo ) );
+    *card = alsa_snd_pcm_info_get_card( pcmInfo );
 
 error:
     return result;
 }
+
+PaError PaAlsa_SetRetriesBusy( int retries )
+{
+    busyRetries_ = retries;
+    return paNoError;
+}
 #endif
\ No newline at end of file
diff --git a/external/portaudio/pa_linux_alsa.h b/external/portaudio/pa_linux_alsa.h
index 5e7e8d4..5f74ad7 100644
--- a/external/portaudio/pa_linux_alsa.h
+++ b/external/portaudio/pa_linux_alsa.h
@@ -2,7 +2,7 @@
 #define PA_LINUX_ALSA_H
 
 /*
- * $Id: pa_linux_alsa.h 1236 2007-06-24 20:39:26Z aknudsen $
+ * $Id: pa_linux_alsa.h 1597 2011-02-11 00:15:51Z dmitrykos $
  * PortAudio Portable Real-Time Audio Library
  * ALSA-specific extensions
  *
@@ -40,8 +40,10 @@
  */
 
 /** @file
- * ALSA-specific PortAudio API extension header file.
+ *  @ingroup public_header
+ *  @brief ALSA-specific PortAudio API extension header file.
  */
+
 #include "portaudio.h"
 
 #ifdef __cplusplus
@@ -86,7 +88,19 @@ PaError PaAlsa_GetStreamOutputCard( PaStream *s, int *card );
  */
 PaError PaAlsa_SetNumPeriods( int numPeriods );
 
-PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex );
+/** Set the maximum number of times to retry opening busy device (sleeping for a
+ * short interval inbetween).
+ */
+PaError PaAlsa_SetRetriesBusy( int retries );
+
+/** Set the path and name of ALSA library file if PortAudio is configured to load it dynamically (see
+ *  PA_ALSA_DYNAMIC). This setting will overwrite the default name set by PA_ALSA_PATHNAME define.
+ * @param pathName Full path with filename. Only filename can be used, but dlopen() will lookup default
+ *                 searchable directories (/usr/lib;/usr/local/lib) then.
+ */
+void PaAlsa_SetLibraryPathName( const char *pathName );
+
+PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex );   // ppgb
 
 #ifdef __cplusplus
 }
diff --git a/external/portaudio/pa_linux_asihpi.c b/external/portaudio/pa_linux_asihpi.c
new file mode 100644
index 0000000..244a00c
--- /dev/null
+++ b/external/portaudio/pa_linux_asihpi.c
@@ -0,0 +1,2893 @@
+/*
+ * $Id:$
+ * PortAudio Portable Real-Time Audio Library
+ * Latest Version at: http://www.portaudio.com
+ * AudioScience HPI implementation by Fred Gleason, Ludwig Schwardt and
+ * Eliot Blennerhassett
+ *
+ * Copyright (c) 2003 Fred Gleason <fredg at salemradiolabs.com>
+ * Copyright (c) 2005,2006 Ludwig Schwardt <schwardt at sun.ac.za>
+ * Copyright (c) 2011 Eliot Blennerhassett <eblennerhassett at audioscience.com>
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2008 Ross Bencina, Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/*
+ * Modification History
+ * 12/2003 - Initial version
+ * 09/2005 - v19 version [rewrite]
+ */
+
+/** @file
+ @ingroup hostapi_src
+ @brief Host API implementation supporting AudioScience cards
+        via the Linux HPI interface.
+
+ <h3>Overview</h3>
+
+ This is a PortAudio implementation for the AudioScience HPI Audio API
+ on the Linux platform. AudioScience makes a range of audio adapters customised
+ for the broadcasting industry, with support for both Windows and Linux.
+ More information on their products can be found on their website:
+
+     http://www.audioscience.com
+
+ Documentation for the HPI API can be found at:
+
+     http://www.audioscience.com/internet/download/sdk/hpi_usermanual_html/html/index.html
+
+ The Linux HPI driver itself (a kernel module + library) can be downloaded from:
+
+     http://www.audioscience.com/internet/download/linux_drivers.htm
+
+ <h3>Implementation strategy</h3>
+
+ *Note* Ideally, AudioScience cards should be handled by the PortAudio ALSA
+ implementation on Linux, as ALSA is the preferred Linux soundcard API. The existence
+ of this host API implementation might therefore seem a bit flawed. Unfortunately, at
+ the time of the creation of this implementation (June 2006), the PA ALSA implementation
+ could not make use of the existing AudioScience ALSA driver. PA ALSA uses the
+ "memory-mapped" (mmap) ALSA access mode to interact with the ALSA library, while the
+ AudioScience ALSA driver only supports the "read-write" access mode. The appropriate
+ solution to this problem is to add "read-write" support to PortAudio ALSA, thereby
+ extending the range of soundcards it supports (AudioScience cards are not the only
+ ones with this problem). Given the author's limited knowledge of ALSA and the
+ simplicity of the HPI API, the second-best solution was born...
+
+ The following mapping between HPI and PA was followed:
+ HPI subsystem => PortAudio host API
+ HPI adapter => nothing specific
+ HPI stream => PortAudio device
+
+ Each HPI stream is either input or output (not both), and can support
+ different channel counts, sampling rates and sample formats. It is therefore
+ a more natural fit to a PA device. A PA stream can therefore combine two
+ HPI streams (one input and one output) into a "full-duplex" stream. These
+ HPI streams can even be on different physical adapters. The two streams ought to be
+ sample-synchronised when they reside on the same adapter, as most AudioScience adapters
+ derive their ADC and DAC clocks from one master clock. When combining two adapters
+ into one full-duplex stream, however, the use of a word clock connection between the
+ adapters is strongly recommended.
+
+ The HPI interface is inherently blocking, making use of read and write calls to
+ transfer data between user buffers and driver buffers. The callback interface therefore
+ requires a helper thread ("callback engine") which periodically transfers data (one thread
+ per PA stream, in fact). The current implementation explicitly sleeps via Pa_Sleep() until
+ enough samples can be transferred (select() or poll() would be better, but currently seems
+ impossible...). The thread implementation makes use of the Unix thread helper functions
+ and some pthread calls here and there. If a unified PA thread exists, this host API
+ implementation might also compile on Windows, as this is the only real Linux-specific
+ part of the code.
+
+ There is no inherent fixed buffer size in the HPI interface, as in some other host APIs.
+ The PortAudio implementation contains a buffer that is allocated during OpenStream and
+ used to transfer data between the callback and the HPI driver buffer. The size of this
+ buffer is quite flexible and is derived from latency suggestions and matched to the
+ requested callback buffer size as far as possible. It can become quite huge, as the
+ AudioScience cards are typically geared towards higher-latency applications and contain
+ large hardware buffers.
+
+ The HPI interface natively supports most common sample formats and sample rates (some
+ conversion is done on the adapter itself).
+
+ Stream time is measured based on the number of processed frames, which is adjusted by the
+ number of frames currently buffered by the HPI driver.
+
+ There is basic support for detecting overflow and underflow. The HPI interface does not
+ explicitly indicate this, so thresholds on buffer levels are used in combination with
+ stream state. Recovery from overflow and underflow is left to the PA client.
+
+ Blocking streams are also implemented. It makes use of the same polling routines that
+ the callback interface uses, in order to prevent the allocation of variable-sized
+ buffers during reading and writing. The framesPerBuffer parameter is therefore still
+ relevant, and this can be increased in the blocking case to improve efficiency.
+
+ The implementation contains extensive reporting macros (slightly modified PA_ENSURE and
+ PA_UNLESS versions) and a useful stream dump routine to provide debugging feedback.
+
+ Output buffer priming via the user callback (i.e. paPrimeOutputBuffersUsingStreamCallback
+ and friends) is not implemented yet. All output is primed with silence.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>          /* strlen() */
+#include <pthread.h>         /* pthreads and friends */
+#include <assert.h>          /* assert */
+#include <math.h>            /* ceil, floor */
+
+#include <asihpi/hpi.h>      /* HPI API */
+
+#include "portaudio.h"       /* PortAudio API */
+#include "pa_util.h"         /* PA_DEBUG, other small utilities */
+#include "pa_unix_util.h"    /* Unix threading utilities */
+#include "pa_allocation.h"   /* Group memory allocation */
+#include "pa_hostapi.h"      /* Host API structs */
+#include "pa_stream.h"       /* Stream interface structs */
+#include "pa_cpuload.h"      /* CPU load measurer */
+#include "pa_process.h"      /* Buffer processor */
+#include "pa_converters.h"   /* PaUtilZeroer */
+#include "pa_debugprint.h"
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Defines
+ */
+
+/* Error reporting and assertions */
+
+/** Evaluate expression, and return on any PortAudio errors */
+#define PA_ENSURE_(expr) \
+    do { \
+        PaError paError = (expr); \
+        if( UNLIKELY( paError < paNoError ) ) \
+        { \
+            PA_DEBUG(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+            result = paError; \
+            goto error; \
+        } \
+    } while (0);
+
+/** Assert expression, else return the provided PaError */
+#define PA_UNLESS_(expr, paError) \
+    do { \
+        if( UNLIKELY( (expr) == 0 ) ) \
+        { \
+            PA_DEBUG(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+            result = (paError); \
+            goto error; \
+        } \
+    } while( 0 );
+
+/** Check return value of HPI function, and map it to PaError */
+#define PA_ASIHPI_UNLESS_(expr, paError) \
+    do { \
+        hpi_err_t hpiError = (expr); \
+        /* If HPI error occurred */ \
+        if( UNLIKELY( hpiError ) ) \
+        { \
+	    char szError[256]; \
+	    HPI_GetErrorText( hpiError, szError ); \
+	    PA_DEBUG(( "HPI error %d occurred: %s\n", hpiError, szError )); \
+	    /* This message will always be displayed, even if debug info is disabled */ \
+            PA_DEBUG(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+            if( (paError) == paUnanticipatedHostError ) \
+	    { \
+	        PA_DEBUG(( "Host error description: %s\n", szError )); \
+	        /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
+	        if( pthread_equal( pthread_self(), paUnixMainThread ) ) \
+                { \
+		    PaUtil_SetLastHostErrorInfo( paInDevelopment, hpiError, szError ); \
+                } \
+	    } \
+	    /* If paNoError is specified, continue as usual */ \
+            /* (useful if you only want to print out the debug messages above) */ \
+	    if( (paError) < 0 ) \
+	    { \
+	        result = (paError); \
+	        goto error; \
+	    } \
+        } \
+    } while( 0 );
+
+/** Report HPI error code and text */
+#define PA_ASIHPI_REPORT_ERROR_(hpiErrorCode) \
+    do { \
+        char szError[256]; \
+        HPI_GetErrorText( hpiError, szError ); \
+        PA_DEBUG(( "HPI error %d occurred: %s\n", hpiError, szError )); \
+        /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
+        if( pthread_equal( pthread_self(), paUnixMainThread ) ) \
+	{ \
+	    PaUtil_SetLastHostErrorInfo( paInDevelopment, (hpiErrorCode), szError ); \
+	} \
+    } while( 0 );
+
+/* Defaults */
+
+/** Sample formats available natively on AudioScience hardware */
+#define PA_ASIHPI_AVAILABLE_FORMATS_ (paFloat32 | paInt32 | paInt24 | paInt16 | paUInt8)
+/** Enable background bus mastering (BBM) for buffer transfers, if available (see HPI docs) */
+#define PA_ASIHPI_USE_BBM_ 1
+/** Minimum number of frames in HPI buffer (for either data or available space).
+ If buffer contains less data/space, it indicates xrun or completion. */
+#define PA_ASIHPI_MIN_FRAMES_ 1152
+/** Minimum polling interval in milliseconds, which determines minimum host buffer size */
+#define PA_ASIHPI_MIN_POLLING_INTERVAL_ 10
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Structures
+ */
+
+/** Host API global data */
+typedef struct PaAsiHpiHostApiRepresentation
+{
+    /* PortAudio "base class" - keep the baseRep first! (C-style inheritance) */
+    PaUtilHostApiRepresentation baseHostApiRep;
+    PaUtilStreamInterface callbackStreamInterface;
+    PaUtilStreamInterface blockingStreamInterface;
+
+    PaUtilAllocationGroup *allocations;
+
+    /* implementation specific data goes here */
+
+    PaHostApiIndex hostApiIndex;
+}
+PaAsiHpiHostApiRepresentation;
+
+
+/** Device data */
+typedef struct PaAsiHpiDeviceInfo
+{
+    /* PortAudio "base class" - keep the baseRep first! (C-style inheritance) */
+    /** Common PortAudio device information */
+    PaDeviceInfo baseDeviceInfo;
+
+    /* implementation specific data goes here */
+
+    /** Adapter index */
+    uint16_t adapterIndex;
+    /** Adapter model number (hex) */
+    uint16_t adapterType;
+    /** Adapter HW/SW version */
+    uint16_t adapterVersion;
+    /** Adapter serial number */
+    uint32_t adapterSerialNumber;
+    /** Stream number */
+    uint16_t streamIndex;
+    /** 0=Input, 1=Output (HPI streams are either input or output but not both) */
+    uint16_t streamIsOutput;
+}
+PaAsiHpiDeviceInfo;
+
+
+/** Stream state as defined by PortAudio.
+ It seems that the host API implementation has to keep track of the PortAudio stream state.
+ Please note that this is NOT the same as the state of the underlying HPI stream. By separating
+ these two concepts, a lot of flexibility is gained. There is a rough match between the two,
+ of course, but forcing a precise match is difficult. For example, HPI_STATE_DRAINED can occur
+ during the Active state of PortAudio (due to underruns) and also during CallBackFinished in
+ the case of an output stream. Similarly, HPI_STATE_STOPPED mostly coincides with the Stopped
+ PortAudio state, by may also occur in the CallbackFinished state when recording is finished.
+
+ Here is a rough match-up:
+
+ PortAudio state   =>     HPI state
+ ---------------          ---------
+ Active            =>     HPI_STATE_RECORDING, HPI_STATE_PLAYING, (HPI_STATE_DRAINED)
+ Stopped           =>     HPI_STATE_STOPPED
+ CallbackFinished  =>     HPI_STATE_STOPPED, HPI_STATE_DRAINED */
+typedef enum PaAsiHpiStreamState
+{
+    paAsiHpiStoppedState=0,
+    paAsiHpiActiveState=1,
+    paAsiHpiCallbackFinishedState=2
+}
+PaAsiHpiStreamState;
+
+
+/** Stream component data (associated with one direction, i.e. either input or output) */
+typedef struct PaAsiHpiStreamComponent
+{
+    /** Device information (HPI handles, etc) */
+    PaAsiHpiDeviceInfo *hpiDevice;
+    /** Stream handle, as passed to HPI interface. */
+    hpi_handle_t hpiStream;
+    /** Stream format, as passed to HPI interface */
+    struct hpi_format hpiFormat;
+    /** Number of bytes per frame, derived from hpiFormat and saved for convenience */
+    uint32_t bytesPerFrame;
+    /** Size of hardware (on-card) buffer of stream in bytes */
+    uint32_t hardwareBufferSize;
+    /** Size of host (BBM) buffer of stream in bytes (if used) */
+    uint32_t hostBufferSize;
+    /** Upper limit on the utilization of output stream buffer (both hardware and host).
+     This prevents large latencies in an output-only stream with a potentially huge buffer
+     and a fast data generator, which would otherwise keep the hardware buffer filled to
+     capacity. See also the "Hardware Buffering=off" option in the AudioScience WAV driver. */
+    uint32_t outputBufferCap;
+    /** Sample buffer (halfway station between HPI and buffer processor) */
+    uint8_t *tempBuffer;
+    /** Sample buffer size, in bytes */
+    uint32_t tempBufferSize;
+}
+PaAsiHpiStreamComponent;
+
+
+/** Stream data */
+typedef struct PaAsiHpiStream
+{
+    /* PortAudio "base class" - keep the baseRep first! (C-style inheritance) */
+    PaUtilStreamRepresentation baseStreamRep;
+    PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+    PaUtilBufferProcessor bufferProcessor;
+
+    PaUtilAllocationGroup *allocations;
+
+    /* implementation specific data goes here */
+
+    /** Separate structs for input and output sides of stream */
+    PaAsiHpiStreamComponent *input, *output;
+
+    /** Polling interval (in milliseconds) */
+    uint32_t pollingInterval;
+    /** Are we running in callback mode? */
+    int callbackMode;
+    /** Number of frames to transfer at a time to/from HPI */
+    unsigned long maxFramesPerHostBuffer;
+    /** Indicates that the stream is in the paNeverDropInput mode */
+    int neverDropInput;
+    /** Contains copy of user buffers, used by blocking interface to transfer non-interleaved data.
+     It went here instead of to each stream component, as the stream component buffer setup in
+     PaAsiHpi_SetupBuffers doesn't know the stream details such as callbackMode.
+     (Maybe a problem later if ReadStream and WriteStream happens concurrently on same stream.) */
+    void **blockingUserBufferCopy;
+
+    /* Thread-related variables */
+
+    /** Helper thread which will deliver data to user callback */
+    PaUnixThread thread;
+    /** PortAudio stream state (Active/Stopped/CallbackFinished) */
+    volatile sig_atomic_t state;
+    /** Hard abort, i.e. drop frames? */
+    volatile sig_atomic_t callbackAbort;
+    /** True if stream stopped via exiting callback with paComplete/paAbort flag
+     (as opposed to explicit call to StopStream/AbortStream) */
+    volatile sig_atomic_t callbackFinished;
+}
+PaAsiHpiStream;
+
+
+/** Stream state information, collected together for convenience */
+typedef struct PaAsiHpiStreamInfo
+{
+    /** HPI stream state (HPI_STATE_STOPPED, HPI_STATE_PLAYING, etc.) */
+    uint16_t state;
+    /** Size (in bytes) of recording/playback data buffer in HPI driver */
+    uint32_t bufferSize;
+    /** Amount of data (in bytes) available in the buffer */
+    uint32_t dataSize;
+    /** Number of frames played/recorded since last stream reset */
+    uint32_t frameCounter;
+    /** Amount of data (in bytes) in hardware (on-card) buffer.
+     This differs from dataSize if bus mastering (BBM) is used, which introduces another
+     driver-level buffer to which dataSize/bufferSize then refers. */
+    uint32_t auxDataSize;
+    /** Total number of data frames currently buffered by HPI driver (host + hw buffers) */
+    uint32_t totalBufferedData;
+    /** Size of immediately available data (for input) or space (for output) in frames.
+     This only checks the first-level buffer (typically host buffer). This amount can be
+     transferred immediately. */
+    uint32_t availableFrames;
+    /** Indicates that hardware buffer is getting too full */
+    int overflow;
+    /** Indicates that hardware buffer is getting too empty */
+    int underflow;
+}
+PaAsiHpiStreamInfo;
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Function prototypes
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+    /* The only exposed function in the entire host API implementation */
+    PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate );
+
+/* Stream prototypes */
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                           PaStream **s,
+                           const PaStreamParameters *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           double sampleRate,
+                           unsigned long framesPerBuffer,
+                           PaStreamFlags streamFlags,
+                           PaStreamCallback *streamCallback,
+                           void *userData );
+static PaError CloseStream( PaStream *s );
+static PaError StartStream( PaStream *s );
+static PaError StopStream( PaStream *s );
+static PaError AbortStream( PaStream *s );
+static PaError IsStreamStopped( PaStream *s );
+static PaError IsStreamActive( PaStream *s );
+static PaTime GetStreamTime( PaStream *s );
+static double GetStreamCpuLoad( PaStream *s );
+
+/* Blocking prototypes */
+static PaError ReadStream( PaStream *s, void *buffer, unsigned long frames );
+static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frames );
+static signed long GetStreamReadAvailable( PaStream *s );
+static signed long GetStreamWriteAvailable( PaStream *s );
+
+/* Callback prototypes */
+static void *CallbackThreadFunc( void *userData );
+
+/* Functions specific to this API */
+static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostApi );
+static uint16_t PaAsiHpi_PaToHpiFormat( PaSampleFormat paFormat );
+static PaSampleFormat PaAsiHpi_HpiToPaFormat( uint16_t hpiFormat );
+static PaError PaAsiHpi_CreateFormat( struct PaUtilHostApiRepresentation *hostApi,
+                                      const PaStreamParameters *parameters, double sampleRate,
+                                      PaAsiHpiDeviceInfo **hpiDevice, struct hpi_format *hpiFormat );
+static PaError PaAsiHpi_OpenInput( struct PaUtilHostApiRepresentation *hostApi,
+                                   const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+                                   hpi_handle_t *hpiStream );
+static PaError PaAsiHpi_OpenOutput( struct PaUtilHostApiRepresentation *hostApi,
+                                    const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+                                    hpi_handle_t *hpiStream );
+static PaError PaAsiHpi_GetStreamInfo( PaAsiHpiStreamComponent *streamComp, PaAsiHpiStreamInfo *info );
+static void PaAsiHpi_StreamComponentDump( PaAsiHpiStreamComponent *streamComp, PaAsiHpiStream *stream );
+static void PaAsiHpi_StreamDump( PaAsiHpiStream *stream );
+static PaError PaAsiHpi_SetupBuffers( PaAsiHpiStreamComponent *streamComp, uint32_t pollingInterval,
+                                      unsigned long framesPerPaHostBuffer, PaTime suggestedLatency );
+static PaError PaAsiHpi_PrimeOutputWithSilence( PaAsiHpiStream *stream );
+static PaError PaAsiHpi_StartStream( PaAsiHpiStream *stream, int outputPrimed );
+static PaError PaAsiHpi_StopStream( PaAsiHpiStream *stream, int abort );
+static PaError PaAsiHpi_ExplicitStop( PaAsiHpiStream *stream, int abort );
+static void PaAsiHpi_OnThreadExit( void *userData );
+static PaError PaAsiHpi_WaitForFrames( PaAsiHpiStream *stream, unsigned long *framesAvail,
+                                       PaStreamCallbackFlags *cbFlags );
+static void PaAsiHpi_CalculateTimeInfo( PaAsiHpiStream *stream, PaStreamCallbackTimeInfo *timeInfo );
+static PaError PaAsiHpi_BeginProcessing( PaAsiHpiStream* stream, unsigned long* numFrames,
+        PaStreamCallbackFlags *cbFlags );
+static PaError PaAsiHpi_EndProcessing( PaAsiHpiStream *stream, unsigned long numFrames,
+                                       PaStreamCallbackFlags *cbFlags );
+
+/* ==========================================================================
+ * ============================= IMPLEMENTATION =============================
+ * ========================================================================== */
+
+/* --------------------------- Host API Interface --------------------------- */
+
+/** Enumerate all PA devices (= HPI streams).
+ This compiles a list of all HPI adapters, and registers a PA device for each input and
+ output stream it finds. Most errors are ignored, as missing or erroneous devices are
+ simply skipped.
+
+ @param hpiHostApi Pointer to HPI host API struct
+
+ @return PortAudio error code (only paInsufficientMemory in practice)
+ */
+static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostApi )
+{
+    PaError result = paNoError;
+    PaUtilHostApiRepresentation *hostApi = &hpiHostApi->baseHostApiRep;
+    PaHostApiInfo *baseApiInfo = &hostApi->info;
+    PaAsiHpiDeviceInfo *hpiDeviceList;
+    int numAdapters;
+    hpi_err_t hpiError = 0;
+    int i, j, deviceCount = 0, deviceIndex = 0;
+
+    assert( hpiHostApi );
+
+    /* Errors not considered critical here (subsystem may report 0 devices), but report them */
+    /* in debug mode. */
+    PA_ASIHPI_UNLESS_( HPI_SubSysGetNumAdapters( NULL, &numAdapters), paNoError );
+
+    for( i=0; i < numAdapters; ++i )
+    {
+        uint16_t inStreams, outStreams;
+        uint16_t version;
+        uint32_t serial;
+        uint16_t type;
+        uint32_t idx;
+
+        hpiError = HPI_SubSysGetAdapter(NULL, i, &idx, &type);
+        if (hpiError)
+            continue;
+
+        /* Try to open adapter */
+        hpiError = HPI_AdapterOpen( NULL, idx );
+        /* Report error and skip to next device on failure */
+        if( hpiError )
+        {
+            PA_ASIHPI_REPORT_ERROR_( hpiError );
+            continue;
+        }
+        hpiError = HPI_AdapterGetInfo( NULL, idx, &outStreams, &inStreams,
+					&version, &serial, &type );
+        /* Skip to next device on failure */
+        if( hpiError )
+        {
+            PA_ASIHPI_REPORT_ERROR_( hpiError );
+            continue;
+        }
+        else
+        {
+            /* Assign default devices if available and increment device count */
+            if( (baseApiInfo->defaultInputDevice == paNoDevice) && (inStreams > 0) )
+                baseApiInfo->defaultInputDevice = deviceCount;
+            deviceCount += inStreams;
+            if( (baseApiInfo->defaultOutputDevice == paNoDevice) && (outStreams > 0) )
+                baseApiInfo->defaultOutputDevice = deviceCount;
+            deviceCount += outStreams;
+        }
+    }
+
+    /* Register any discovered devices */
+    if( deviceCount > 0 )
+    {
+        /* Memory allocation */
+        PA_UNLESS_( hostApi->deviceInfos = (PaDeviceInfo**) PaUtil_GroupAllocateMemory(
+                                               hpiHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ),
+                    paInsufficientMemory );
+        /* Allocate all device info structs in a contiguous block */
+        PA_UNLESS_( hpiDeviceList = (PaAsiHpiDeviceInfo*) PaUtil_GroupAllocateMemory(
+                                        hpiHostApi->allocations, sizeof(PaAsiHpiDeviceInfo) * deviceCount ),
+                    paInsufficientMemory );
+
+        /* Now query devices again for information */
+        for( i=0; i < numAdapters; ++i )
+        {
+            uint16_t inStreams, outStreams;
+            uint16_t version;
+            uint32_t serial;
+            uint16_t type;
+            uint32_t idx;
+
+            hpiError = HPI_SubSysGetAdapter( NULL, i, &idx, &type );
+            if (hpiError)
+                continue;
+
+            /* Assume adapter is still open from previous round */
+            hpiError = HPI_AdapterGetInfo( NULL, idx,
+                                           &outStreams, &inStreams, &version, &serial, &type );
+            /* Report error and skip to next device on failure */
+            if( hpiError )
+            {
+                PA_ASIHPI_REPORT_ERROR_( hpiError );
+                continue;
+            }
+            else
+            {
+                PA_DEBUG(( "Found HPI Adapter ID=%4X Idx=%d #In=%d #Out=%d S/N=%d HWver=%c%d DSPver=%03d\n",
+                           type, idx, inStreams, outStreams, serial,
+                           ((version>>3)&0xf)+'A',                  /* Hw version major */
+                           version&0x7,                             /* Hw version minor */
+                           ((version>>13)*100)+((version>>7)&0x3f)  /* DSP code version */
+                         ));
+            }
+
+            /* First add all input streams as devices */
+            for( j=0; j < inStreams; ++j )
+            {
+                PaAsiHpiDeviceInfo *hpiDevice = &hpiDeviceList[deviceIndex];
+                PaDeviceInfo *baseDeviceInfo = &hpiDevice->baseDeviceInfo;
+                char srcName[72];
+                char *deviceName;
+
+                memset( hpiDevice, 0, sizeof(PaAsiHpiDeviceInfo) );
+                /* Set implementation-specific device details */
+                hpiDevice->adapterIndex = idx;
+                hpiDevice->adapterType = type;
+                hpiDevice->adapterVersion = version;
+                hpiDevice->adapterSerialNumber = serial;
+                hpiDevice->streamIndex = j;
+                hpiDevice->streamIsOutput = 0;
+                /* Set common PortAudio device stats */
+                baseDeviceInfo->structVersion = 2;
+                /* Make sure name string is owned by API info structure */
+                sprintf( srcName,
+                         "Adapter %d (%4X) - Input Stream %d", i+1, type, j+1 );
+                PA_UNLESS_( deviceName = (char *) PaUtil_GroupAllocateMemory(
+                                             hpiHostApi->allocations, strlen(srcName) + 1 ), paInsufficientMemory );
+                strcpy( deviceName, srcName );
+                baseDeviceInfo->name = deviceName;
+                baseDeviceInfo->hostApi = hpiHostApi->hostApiIndex;
+                baseDeviceInfo->maxInputChannels = HPI_MAX_CHANNELS;
+                baseDeviceInfo->maxOutputChannels = 0;
+                /* Default latency values for interactive performance */
+                baseDeviceInfo->defaultLowInputLatency = 0.01;
+                baseDeviceInfo->defaultLowOutputLatency = -1.0;
+                /* Default latency values for robust non-interactive applications (eg. playing sound files) */
+                baseDeviceInfo->defaultHighInputLatency = 0.2;
+                baseDeviceInfo->defaultHighOutputLatency = -1.0;
+                /* HPI interface can actually handle any sampling rate to 1 Hz accuracy,
+                * so this default is as good as any */
+                baseDeviceInfo->defaultSampleRate = 44100;
+
+                /* Store device in global PortAudio list */
+                hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice;
+            }
+
+            /* Now add all output streams as devices (I know, the repetition is painful) */
+            for( j=0; j < outStreams; ++j )
+            {
+                PaAsiHpiDeviceInfo *hpiDevice = &hpiDeviceList[deviceIndex];
+                PaDeviceInfo *baseDeviceInfo = &hpiDevice->baseDeviceInfo;
+                char srcName[72];
+                char *deviceName;
+
+                memset( hpiDevice, 0, sizeof(PaAsiHpiDeviceInfo) );
+                /* Set implementation-specific device details */
+                hpiDevice->adapterIndex = idx;
+                hpiDevice->adapterType = type;
+                hpiDevice->adapterVersion = version;
+                hpiDevice->adapterSerialNumber = serial;
+                hpiDevice->streamIndex = j;
+                hpiDevice->streamIsOutput = 1;
+                /* Set common PortAudio device stats */
+                baseDeviceInfo->structVersion = 2;
+                /* Make sure name string is owned by API info structure */
+                sprintf( srcName,
+                         "Adapter %d (%4X) - Output Stream %d", i+1, type, j+1 );
+                PA_UNLESS_( deviceName = (char *) PaUtil_GroupAllocateMemory(
+                                             hpiHostApi->allocations, strlen(srcName) + 1 ), paInsufficientMemory );
+                strcpy( deviceName, srcName );
+                baseDeviceInfo->name = deviceName;
+                baseDeviceInfo->hostApi = hpiHostApi->hostApiIndex;
+                baseDeviceInfo->maxInputChannels = 0;
+                baseDeviceInfo->maxOutputChannels = HPI_MAX_CHANNELS;
+                /* Default latency values for interactive performance. */
+                baseDeviceInfo->defaultLowInputLatency = -1.0;
+                baseDeviceInfo->defaultLowOutputLatency = 0.01;
+                /* Default latency values for robust non-interactive applications (eg. playing sound files). */
+                baseDeviceInfo->defaultHighInputLatency = -1.0;
+                baseDeviceInfo->defaultHighOutputLatency = 0.2;
+                /* HPI interface can actually handle any sampling rate to 1 Hz accuracy,
+                * so this default is as good as any */
+                baseDeviceInfo->defaultSampleRate = 44100;
+
+                /* Store device in global PortAudio list */
+                hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice;
+            }
+        }
+    }
+
+    /* Finally acknowledge checked devices */
+    baseApiInfo->deviceCount = deviceIndex;
+
+error:
+    return result;
+}
+
+
+/** Initialize host API implementation.
+ This is the only function exported beyond this file. It is called by PortAudio to initialize
+ the host API. It stores API info, finds and registers all devices, and sets up callback and
+ blocking interfaces.
+
+ @param hostApi Pointer to host API struct
+
+ @param hostApiIndex Index of current (HPI) host API
+
+ @return PortAudio error code
+ */
+PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
+{
+    PaError result = paNoError;
+    PaAsiHpiHostApiRepresentation *hpiHostApi = NULL;
+    PaHostApiInfo *baseApiInfo;
+
+    /* Try to initialize HPI subsystem */
+    if (!HPI_SubSysCreate())
+    {
+        /* the V19 development docs say that if an implementation
+         * detects that it cannot be used, it should return a NULL
+         * interface and paNoError */
+        PA_DEBUG(( "Could not open HPI interface\n" ));
+
+	*hostApi = NULL;
+        return paNoError;
+    }
+    else
+    {
+        uint32_t hpiVersion;
+        PA_ASIHPI_UNLESS_( HPI_SubSysGetVersionEx( NULL, &hpiVersion ), paUnanticipatedHostError );
+        PA_DEBUG(( "HPI interface v%d.%02d.%02d\n",
+                   hpiVersion >> 16,  (hpiVersion >> 8) & 0x0F, (hpiVersion & 0x0F) ));
+    }
+
+    /* Allocate host API structure */
+    PA_UNLESS_( hpiHostApi = (PaAsiHpiHostApiRepresentation*) PaUtil_AllocateMemory(
+                                 sizeof(PaAsiHpiHostApiRepresentation) ), paInsufficientMemory );
+    PA_UNLESS_( hpiHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
+
+    hpiHostApi->hostApiIndex = hostApiIndex;
+
+    *hostApi = &hpiHostApi->baseHostApiRep;
+    baseApiInfo = &((*hostApi)->info);
+    /* Fill in common API details */
+    baseApiInfo->structVersion = 1;
+    baseApiInfo->type = paAudioScienceHPI;
+    baseApiInfo->name = "AudioScience HPI";
+    baseApiInfo->deviceCount = 0;
+    baseApiInfo->defaultInputDevice = paNoDevice;
+    baseApiInfo->defaultOutputDevice = paNoDevice;
+
+    PA_ENSURE_( PaAsiHpi_BuildDeviceList( hpiHostApi ) );
+
+    (*hostApi)->Terminate = Terminate;
+    (*hostApi)->OpenStream = OpenStream;
+    (*hostApi)->IsFormatSupported = IsFormatSupported;
+
+    PaUtil_InitializeStreamInterface( &hpiHostApi->callbackStreamInterface, CloseStream, StartStream,
+                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, GetStreamCpuLoad,
+                                      PaUtil_DummyRead, PaUtil_DummyWrite,
+                                      PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
+
+    PaUtil_InitializeStreamInterface( &hpiHostApi->blockingStreamInterface, CloseStream, StartStream,
+                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, PaUtil_DummyGetCpuLoad,
+                                      ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
+
+    /* Store identity of main thread */
+    PA_ENSURE_( PaUnixThreading_Initialize() );
+
+    return result;
+error:
+    if (hpiHostApi)
+        PaUtil_FreeMemory( hpiHostApi );
+    return result;
+}
+
+
+/** Terminate host API implementation.
+ This closes all HPI adapters and frees the HPI subsystem. It also frees the host API struct
+ memory. It should be called once for every PaAsiHpi_Initialize call.
+
+ @param Pointer to host API struct
+ */
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+    PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+    int i;
+    PaError result = paNoError;
+
+    if( hpiHostApi )
+    {
+        /* Get rid of HPI-specific structures */
+        uint16_t lastAdapterIndex = HPI_MAX_ADAPTERS;
+        /* Iterate through device list and close adapters */
+        for( i=0; i < hostApi->info.deviceCount; ++i )
+        {
+            PaAsiHpiDeviceInfo *hpiDevice = (PaAsiHpiDeviceInfo *) hostApi->deviceInfos[ i ];
+            /* Close adapter only if it differs from previous one */
+            if( hpiDevice->adapterIndex != lastAdapterIndex )
+            {
+                /* Ignore errors (report only during debugging) */
+                PA_ASIHPI_UNLESS_( HPI_AdapterClose( NULL,
+                                                     hpiDevice->adapterIndex ), paNoError );
+                lastAdapterIndex = hpiDevice->adapterIndex;
+            }
+        }
+        /* Finally dismantle HPI subsystem */
+        HPI_SubSysFree( NULL );
+
+        if( hpiHostApi->allocations )
+        {
+            PaUtil_FreeAllAllocations( hpiHostApi->allocations );
+            PaUtil_DestroyAllocationGroup( hpiHostApi->allocations );
+        }
+
+        PaUtil_FreeMemory( hpiHostApi );
+    }
+error:
+    return;
+}
+
+
+/** Converts PortAudio sample format to equivalent HPI format.
+
+ @param paFormat PortAudio sample format
+
+ @return HPI sample format
+ */
+static uint16_t PaAsiHpi_PaToHpiFormat( PaSampleFormat paFormat )
+{
+    /* Ignore interleaving flag */
+    switch( paFormat & ~paNonInterleaved )
+    {
+    case paFloat32:
+        return HPI_FORMAT_PCM32_FLOAT;
+
+    case paInt32:
+        return HPI_FORMAT_PCM32_SIGNED;
+
+    case paInt24:
+        return HPI_FORMAT_PCM24_SIGNED;
+
+    case paInt16:
+        return HPI_FORMAT_PCM16_SIGNED;
+
+    case paUInt8:
+        return HPI_FORMAT_PCM8_UNSIGNED;
+
+        /* Default is 16-bit signed */
+    case paInt8:
+    default:
+        return HPI_FORMAT_PCM16_SIGNED;
+    }
+}
+
+
+/** Converts HPI sample format to equivalent PortAudio format.
+
+ @param paFormat HPI sample format
+
+ @return PortAudio sample format
+ */
+static PaSampleFormat PaAsiHpi_HpiToPaFormat( uint16_t hpiFormat )
+{
+    switch( hpiFormat )
+    {
+    case HPI_FORMAT_PCM32_FLOAT:
+        return paFloat32;
+
+    case HPI_FORMAT_PCM32_SIGNED:
+        return paInt32;
+
+    case HPI_FORMAT_PCM24_SIGNED:
+        return paInt24;
+
+    case HPI_FORMAT_PCM16_SIGNED:
+        return paInt16;
+
+    case HPI_FORMAT_PCM8_UNSIGNED:
+        return paUInt8;
+
+        /* Default is custom format (e.g. for HPI MP3 format) */
+    default:
+        return paCustomFormat;
+    }
+}
+
+
+/** Creates HPI format struct based on PortAudio parameters.
+ This also does some checks to see whether the desired format is valid, and whether
+ the device allows it. This only checks the format of one half (input or output) of the
+ PortAudio stream.
+
+ @param hostApi Pointer to host API struct
+
+ @param parameters Pointer to stream parameter struct
+
+ @param sampleRate Desired sample rate
+
+ @param hpiDevice Pointer to HPI device struct
+
+ @param hpiFormat Resulting HPI format returned here
+
+ @return PortAudio error code (typically indicating a problem with stream format)
+ */
+static PaError PaAsiHpi_CreateFormat( struct PaUtilHostApiRepresentation *hostApi,
+                                      const PaStreamParameters *parameters, double sampleRate,
+                                      PaAsiHpiDeviceInfo **hpiDevice, struct hpi_format *hpiFormat )
+{
+    int maxChannelCount = 0;
+    PaSampleFormat hostSampleFormat = 0;
+    hpi_err_t hpiError = 0;
+
+    /* Unless alternate device specification is supported, reject the use of
+       paUseHostApiSpecificDeviceSpecification */
+    if( parameters->device == paUseHostApiSpecificDeviceSpecification )
+        return paInvalidDevice;
+    else
+    {
+        assert( parameters->device < hostApi->info.deviceCount );
+        *hpiDevice = (PaAsiHpiDeviceInfo*) hostApi->deviceInfos[ parameters->device ];
+    }
+
+    /* Validate streamInfo - this implementation doesn't use custom stream info */
+    if( parameters->hostApiSpecificStreamInfo )
+        return paIncompatibleHostApiSpecificStreamInfo;
+
+    /* Check that device can support channel count */
+    if( (*hpiDevice)->streamIsOutput )
+    {
+        maxChannelCount = (*hpiDevice)->baseDeviceInfo.maxOutputChannels;
+    }
+    else
+    {
+        maxChannelCount = (*hpiDevice)->baseDeviceInfo.maxInputChannels;
+    }
+    if( (maxChannelCount == 0) || (parameters->channelCount > maxChannelCount) )
+        return paInvalidChannelCount;
+
+    /* All standard sample formats are supported by the buffer adapter,
+       and this implementation doesn't support any custom sample formats */
+    if( parameters->sampleFormat & paCustomFormat )
+        return paSampleFormatNotSupported;
+
+    /* Switch to closest HPI native format */
+    hostSampleFormat = PaUtil_SelectClosestAvailableFormat(PA_ASIHPI_AVAILABLE_FORMATS_,
+                       parameters->sampleFormat );
+    /* Setup format + info objects */
+    hpiError = HPI_FormatCreate( hpiFormat, (uint16_t)parameters->channelCount,
+                                 PaAsiHpi_PaToHpiFormat( hostSampleFormat ),
+                                 (uint32_t)sampleRate, 0, 0 );
+    if( hpiError )
+    {
+        PA_ASIHPI_REPORT_ERROR_( hpiError );
+        switch( hpiError )
+        {
+        case HPI_ERROR_INVALID_FORMAT:
+            return paSampleFormatNotSupported;
+
+        case HPI_ERROR_INVALID_SAMPLERATE:
+        case HPI_ERROR_INCOMPATIBLE_SAMPLERATE:
+            return paInvalidSampleRate;
+
+        case HPI_ERROR_INVALID_CHANNELS:
+            return paInvalidChannelCount;
+        }
+    }
+
+    return paNoError;
+}
+
+
+/** Open HPI input stream with given format.
+ This attempts to open HPI input stream with desired format. If the format is not supported
+ or the device is unavailable, the stream is closed and a PortAudio error code is returned.
+
+ @param hostApi Pointer to host API struct
+
+ @param hpiDevice Pointer to HPI device struct
+
+ @param hpiFormat Pointer to HPI format struct
+
+ @return PortAudio error code (typically indicating a problem with stream format or device)
+*/
+static PaError PaAsiHpi_OpenInput( struct PaUtilHostApiRepresentation *hostApi,
+                                   const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+                                   hpi_handle_t *hpiStream )
+{
+    PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+    PaError result = paNoError;
+    hpi_err_t hpiError = 0;
+
+    /* Catch misplaced output devices, as they typically have 0 input channels */
+    PA_UNLESS_( !hpiDevice->streamIsOutput, paInvalidChannelCount );
+    /* Try to open input stream */
+    PA_ASIHPI_UNLESS_( HPI_InStreamOpen( NULL, hpiDevice->adapterIndex,
+                                         hpiDevice->streamIndex, hpiStream ), paDeviceUnavailable );
+    /* Set input format (checking it in the process) */
+    /* Could also use HPI_InStreamQueryFormat, but this economizes the process */
+    hpiError = HPI_InStreamSetFormat( NULL, *hpiStream, (struct hpi_format*)hpiFormat );
+    if( hpiError )
+    {
+        PA_ASIHPI_REPORT_ERROR_( hpiError );
+        PA_ASIHPI_UNLESS_( HPI_InStreamClose( NULL, *hpiStream ), paNoError );
+        switch( hpiError )
+        {
+        case HPI_ERROR_INVALID_FORMAT:
+            return paSampleFormatNotSupported;
+
+        case HPI_ERROR_INVALID_SAMPLERATE:
+        case HPI_ERROR_INCOMPATIBLE_SAMPLERATE:
+            return paInvalidSampleRate;
+
+        case HPI_ERROR_INVALID_CHANNELS:
+            return paInvalidChannelCount;
+
+        default:
+            /* In case anything else went wrong */
+            return paInvalidDevice;
+        }
+    }
+
+error:
+    return result;
+}
+
+
+/** Open HPI output stream with given format.
+ This attempts to open HPI output stream with desired format. If the format is not supported
+ or the device is unavailable, the stream is closed and a PortAudio error code is returned.
+
+ @param hostApi Pointer to host API struct
+
+ @param hpiDevice Pointer to HPI device struct
+
+ @param hpiFormat Pointer to HPI format struct
+
+ @return PortAudio error code (typically indicating a problem with stream format or device)
+*/
+static PaError PaAsiHpi_OpenOutput( struct PaUtilHostApiRepresentation *hostApi,
+                                    const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+                                    hpi_handle_t *hpiStream )
+{
+    PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+    PaError result = paNoError;
+    hpi_err_t hpiError = 0;
+
+    /* Catch misplaced input devices, as they typically have 0 output channels */
+    PA_UNLESS_( hpiDevice->streamIsOutput, paInvalidChannelCount );
+    /* Try to open output stream */
+    PA_ASIHPI_UNLESS_( HPI_OutStreamOpen( NULL, hpiDevice->adapterIndex,
+                                          hpiDevice->streamIndex, hpiStream ), paDeviceUnavailable );
+
+    /* Check output format (format is set on first write to output stream) */
+    hpiError = HPI_OutStreamQueryFormat( NULL, *hpiStream, (struct hpi_format*)hpiFormat );
+    if( hpiError )
+    {
+        PA_ASIHPI_REPORT_ERROR_( hpiError );
+        PA_ASIHPI_UNLESS_( HPI_OutStreamClose( NULL, *hpiStream ), paNoError );
+        switch( hpiError )
+        {
+        case HPI_ERROR_INVALID_FORMAT:
+            return paSampleFormatNotSupported;
+
+        case HPI_ERROR_INVALID_SAMPLERATE:
+        case HPI_ERROR_INCOMPATIBLE_SAMPLERATE:
+            return paInvalidSampleRate;
+
+        case HPI_ERROR_INVALID_CHANNELS:
+            return paInvalidChannelCount;
+
+        default:
+            /* In case anything else went wrong */
+            return paInvalidDevice;
+        }
+    }
+
+error:
+    return result;
+}
+
+
+/** Checks whether the desired stream formats and devices are supported
+ (for both input and output).
+ This is done by actually opening the appropriate HPI streams and closing them again.
+
+ @param hostApi Pointer to host API struct
+
+ @param inputParameters Pointer to stream parameter struct for input side of stream
+
+ @param outputParameters Pointer to stream parameter struct for output side of stream
+
+ @param sampleRate Desired sample rate
+
+ @return PortAudio error code (paFormatIsSupported on success)
+ */
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate )
+{
+    PaError result = paFormatIsSupported;
+    PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+    PaAsiHpiDeviceInfo *hpiDevice = NULL;
+    struct hpi_format hpiFormat;
+
+    /* Input stream */
+    if( inputParameters )
+    {
+        hpi_handle_t hpiStream;
+        PA_DEBUG(( "%s: Checking input params: dev=%d, sr=%d, chans=%d, fmt=%d\n",
+                   __FUNCTION__, inputParameters->device, (int)sampleRate,
+                   inputParameters->channelCount, inputParameters->sampleFormat ));
+        /* Create and validate format */
+        PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, inputParameters, sampleRate,
+                                           &hpiDevice, &hpiFormat ) );
+        /* Open stream to further check format */
+        PA_ENSURE_( PaAsiHpi_OpenInput( hostApi, hpiDevice, &hpiFormat, &hpiStream ) );
+        /* Close stream again */
+        PA_ASIHPI_UNLESS_( HPI_InStreamClose( NULL, hpiStream ), paNoError );
+    }
+
+    /* Output stream */
+    if( outputParameters )
+    {
+        hpi_handle_t hpiStream;
+        PA_DEBUG(( "%s: Checking output params: dev=%d, sr=%d, chans=%d, fmt=%d\n",
+                   __FUNCTION__, outputParameters->device, (int)sampleRate,
+                   outputParameters->channelCount, outputParameters->sampleFormat ));
+        /* Create and validate format */
+        PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, outputParameters, sampleRate,
+                                           &hpiDevice, &hpiFormat ) );
+        /* Open stream to further check format */
+        PA_ENSURE_( PaAsiHpi_OpenOutput( hostApi, hpiDevice, &hpiFormat, &hpiStream ) );
+        /* Close stream again */
+        PA_ASIHPI_UNLESS_( HPI_OutStreamClose( NULL, hpiStream ), paNoError );
+    }
+
+error:
+    return result;
+}
+
+/* ---------------------------- Stream Interface ---------------------------- */
+
+/** Obtain HPI stream information.
+ This obtains info such as stream state and available data/space in buffers. It also
+ estimates whether an underflow or overflow occurred.
+
+ @param streamComp Pointer to stream component (input or output) to query
+
+ @param info Pointer to stream info struct that will contain result
+
+ @return PortAudio error code (either paNoError, paDeviceUnavailable or paUnanticipatedHostError)
+ */
+static PaError PaAsiHpi_GetStreamInfo( PaAsiHpiStreamComponent *streamComp, PaAsiHpiStreamInfo *info )
+{
+    PaError result = paDeviceUnavailable;
+    uint16_t state;
+    uint32_t bufferSize, dataSize, frameCounter, auxDataSize, threshold;
+    uint32_t hwBufferSize, hwDataSize;
+
+    assert( streamComp );
+    assert( info );
+
+    /* First blank the stream info struct, in case something goes wrong below.
+       This saves the caller from initializing the struct. */
+    info->state = 0;
+    info->bufferSize = 0;
+    info->dataSize = 0;
+    info->frameCounter = 0;
+    info->auxDataSize = 0;
+    info->totalBufferedData = 0;
+    info->availableFrames = 0;
+    info->underflow = 0;
+    info->overflow = 0;
+
+    if( streamComp->hpiDevice && streamComp->hpiStream )
+    {
+        /* Obtain detailed stream info (either input or output) */
+        if( streamComp->hpiDevice->streamIsOutput )
+        {
+            PA_ASIHPI_UNLESS_( HPI_OutStreamGetInfoEx( NULL,
+                               streamComp->hpiStream,
+                               &state, &bufferSize, &dataSize, &frameCounter,
+                               &auxDataSize ), paUnanticipatedHostError );
+        }
+        else
+        {
+            PA_ASIHPI_UNLESS_( HPI_InStreamGetInfoEx( NULL,
+                               streamComp->hpiStream,
+                               &state, &bufferSize, &dataSize, &frameCounter,
+                               &auxDataSize ), paUnanticipatedHostError );
+        }
+        /* Load stream info */
+        info->state = state;
+        info->bufferSize = bufferSize;
+        info->dataSize = dataSize;
+        info->frameCounter = frameCounter;
+        info->auxDataSize = auxDataSize;
+        /* Determine total buffered data */
+        info->totalBufferedData = dataSize;
+        if( streamComp->hostBufferSize > 0 )
+            info->totalBufferedData += auxDataSize;
+        info->totalBufferedData /= streamComp->bytesPerFrame;
+        /* Determine immediately available frames */
+        info->availableFrames = streamComp->hpiDevice->streamIsOutput ?
+                                bufferSize - dataSize : dataSize;
+        info->availableFrames /= streamComp->bytesPerFrame;
+        /* Minimum space/data required in buffers */
+        threshold = PA_MIN( streamComp->tempBufferSize,
+                            streamComp->bytesPerFrame * PA_ASIHPI_MIN_FRAMES_ );
+        /* Obtain hardware buffer stats first, to simplify things */
+        hwBufferSize = streamComp->hardwareBufferSize;
+        hwDataSize = streamComp->hostBufferSize > 0 ? auxDataSize : dataSize;
+        /* Underflow is a bit tricky */
+        info->underflow = streamComp->hpiDevice->streamIsOutput ?
+                          /* Stream seems to start in drained state sometimes, so ignore initial underflow */
+                          (frameCounter > 0) && ( (state == HPI_STATE_DRAINED) || (hwDataSize == 0) ) :
+                          /* Input streams check the first-level (host) buffer for underflow */
+                          (state != HPI_STATE_STOPPED) && (dataSize < threshold);
+        /* Check for overflow in second-level (hardware) buffer for both input and output */
+        info->overflow = (state != HPI_STATE_STOPPED) && (hwBufferSize - hwDataSize < threshold);
+
+        return paNoError;
+    }
+
+error:
+    return result;
+}
+
+
+/** Display stream component information for debugging purposes.
+
+ @param streamComp Pointer to stream component (input or output) to query
+
+ @param stream Pointer to stream struct which contains the component above
+ */
+static void PaAsiHpi_StreamComponentDump( PaAsiHpiStreamComponent *streamComp,
+        PaAsiHpiStream *stream )
+{
+    PaAsiHpiStreamInfo streamInfo;
+
+    assert( streamComp );
+    assert( stream );
+
+    /* Name of soundcard/device used by component */
+    PA_DEBUG(( "device: %s\n", streamComp->hpiDevice->baseDeviceInfo.name ));
+    /* Unfortunately some overlap between input and output here */
+    if( streamComp->hpiDevice->streamIsOutput )
+    {
+        /* Settings on the user side (as experienced by user callback) */
+        PA_DEBUG(( "user: %d-bit, %d ",
+                   8*stream->bufferProcessor.bytesPerUserOutputSample,
+                   stream->bufferProcessor.outputChannelCount));
+        if( stream->bufferProcessor.userOutputIsInterleaved )
+        {
+            PA_DEBUG(( "interleaved channels, " ));
+        }
+        else
+        {
+            PA_DEBUG(( "non-interleaved channels, " ));
+        }
+        PA_DEBUG(( "%d frames/buffer, latency = %5.1f ms\n",
+                   stream->bufferProcessor.framesPerUserBuffer,
+                   1000*stream->baseStreamRep.streamInfo.outputLatency ));
+        /* Settings on the host side (internal to PortAudio host API) */
+        PA_DEBUG(( "host: %d-bit, %d interleaved channels, %d frames/buffer ",
+                   8*stream->bufferProcessor.bytesPerHostOutputSample,
+                   stream->bufferProcessor.outputChannelCount,
+                   stream->bufferProcessor.framesPerHostBuffer ));
+    }
+    else
+    {
+        /* Settings on the user side (as experienced by user callback) */
+        PA_DEBUG(( "user: %d-bit, %d ",
+                   8*stream->bufferProcessor.bytesPerUserInputSample,
+                   stream->bufferProcessor.inputChannelCount));
+        if( stream->bufferProcessor.userInputIsInterleaved )
+        {
+            PA_DEBUG(( "interleaved channels, " ));
+        }
+        else
+        {
+            PA_DEBUG(( "non-interleaved channels, " ));
+        }
+        PA_DEBUG(( "%d frames/buffer, latency = %5.1f ms\n",
+                   stream->bufferProcessor.framesPerUserBuffer,
+                   1000*stream->baseStreamRep.streamInfo.inputLatency ));
+        /* Settings on the host side (internal to PortAudio host API) */
+        PA_DEBUG(( "host: %d-bit, %d interleaved channels, %d frames/buffer ",
+                   8*stream->bufferProcessor.bytesPerHostInputSample,
+                   stream->bufferProcessor.inputChannelCount,
+                   stream->bufferProcessor.framesPerHostBuffer ));
+    }
+    switch( stream->bufferProcessor.hostBufferSizeMode )
+    {
+    case paUtilFixedHostBufferSize:
+        PA_DEBUG(( "[fixed] " ));
+        break;
+    case paUtilBoundedHostBufferSize:
+        PA_DEBUG(( "[bounded] " ));
+        break;
+    case paUtilUnknownHostBufferSize:
+        PA_DEBUG(( "[unknown] " ));
+        break;
+    case paUtilVariableHostBufferSizePartialUsageAllowed:
+        PA_DEBUG(( "[variable] " ));
+        break;
+    }
+    PA_DEBUG(( "(%d max)\n", streamComp->tempBufferSize / streamComp->bytesPerFrame ));
+    /* HPI hardware settings */
+    PA_DEBUG(( "HPI: adapter %d stream %d, %d-bit, %d-channel, %d Hz\n",
+               streamComp->hpiDevice->adapterIndex, streamComp->hpiDevice->streamIndex,
+               8 * streamComp->bytesPerFrame / streamComp->hpiFormat.wChannels,
+               streamComp->hpiFormat.wChannels,
+               streamComp->hpiFormat.dwSampleRate ));
+    /* Stream state and buffer levels */
+    PA_DEBUG(( "HPI: " ));
+    PaAsiHpi_GetStreamInfo( streamComp, &streamInfo );
+    switch( streamInfo.state )
+    {
+    case HPI_STATE_STOPPED:
+        PA_DEBUG(( "[STOPPED] " ));
+        break;
+    case HPI_STATE_PLAYING:
+        PA_DEBUG(( "[PLAYING] " ));
+        break;
+    case HPI_STATE_RECORDING:
+        PA_DEBUG(( "[RECORDING] " ));
+        break;
+    case HPI_STATE_DRAINED:
+        PA_DEBUG(( "[DRAINED] " ));
+        break;
+    default:
+        PA_DEBUG(( "[unknown state] " ));
+        break;
+    }
+    if( streamComp->hostBufferSize )
+    {
+        PA_DEBUG(( "host = %d/%d B, ", streamInfo.dataSize, streamComp->hostBufferSize ));
+        PA_DEBUG(( "hw = %d/%d (%d) B, ", streamInfo.auxDataSize,
+                   streamComp->hardwareBufferSize, streamComp->outputBufferCap ));
+    }
+    else
+    {
+        PA_DEBUG(( "hw = %d/%d B, ", streamInfo.dataSize, streamComp->hardwareBufferSize ));
+    }
+    PA_DEBUG(( "count = %d", streamInfo.frameCounter ));
+    if( streamInfo.overflow )
+    {
+        PA_DEBUG(( " [overflow]" ));
+    }
+    else if( streamInfo.underflow )
+    {
+        PA_DEBUG(( " [underflow]" ));
+    }
+    PA_DEBUG(( "\n" ));
+}
+
+
+/** Display stream information for debugging purposes.
+
+ @param stream Pointer to stream to query
+ */
+static void PaAsiHpi_StreamDump( PaAsiHpiStream *stream )
+{
+    assert( stream );
+
+    PA_DEBUG(( "\n------------------------- STREAM INFO FOR %p ---------------------------\n", stream ));
+    /* General stream info (input+output) */
+    if( stream->baseStreamRep.streamCallback )
+    {
+        PA_DEBUG(( "[callback] " ));
+    }
+    else
+    {
+        PA_DEBUG(( "[blocking] " ));
+    }
+    PA_DEBUG(( "sr=%d Hz, poll=%d ms, max %d frames/buf ",
+               (int)stream->baseStreamRep.streamInfo.sampleRate,
+               stream->pollingInterval, stream->maxFramesPerHostBuffer ));
+    switch( stream->state )
+    {
+    case paAsiHpiStoppedState:
+        PA_DEBUG(( "[stopped]\n" ));
+        break;
+    case paAsiHpiActiveState:
+        PA_DEBUG(( "[active]\n" ));
+        break;
+    case paAsiHpiCallbackFinishedState:
+        PA_DEBUG(( "[cb fin]\n" ));
+        break;
+    default:
+        PA_DEBUG(( "[unknown state]\n" ));
+        break;
+    }
+    if( stream->callbackMode )
+    {
+        PA_DEBUG(( "cb info: thread=%p, cbAbort=%d, cbFinished=%d\n",
+                   stream->thread.thread, stream->callbackAbort, stream->callbackFinished ));
+    }
+
+    PA_DEBUG(( "----------------------------------- Input  ------------------------------------\n" ));
+    if( stream->input )
+    {
+        PaAsiHpi_StreamComponentDump( stream->input, stream );
+    }
+    else
+    {
+        PA_DEBUG(( "*none*\n" ));
+    }
+
+    PA_DEBUG(( "----------------------------------- Output ------------------------------------\n" ));
+    if( stream->output )
+    {
+        PaAsiHpi_StreamComponentDump( stream->output, stream );
+    }
+    else
+    {
+        PA_DEBUG(( "*none*\n" ));
+    }
+    PA_DEBUG(( "-------------------------------------------------------------------------------\n\n" ));
+
+}
+
+
+/** Determine buffer sizes and allocate appropriate stream buffers.
+ This attempts to allocate a BBM (host) buffer for the HPI stream component (either input
+ or output, as both have similar buffer needs). Not all AudioScience adapters support BBM,
+ in which case the hardware buffer has to suffice. The size of the HPI host buffer is chosen
+ as a multiple of framesPerPaHostBuffer, and also influenced by the suggested latency and the
+ estimated minimum polling interval. The HPI host and hardware buffer sizes are stored, and an
+ appropriate cap for the hardware buffer is also calculated. Finally, the temporary stream
+ buffer which serves as the PortAudio host buffer for this implementation is allocated.
+ This buffer contains an integer number of user buffers, to simplify buffer adaption in the
+ buffer processor. The function returns paBufferTooBig if the HPI interface cannot allocate
+ an HPI host buffer of the desired size.
+
+ @param streamComp Pointer to stream component struct
+
+ @param pollingInterval Polling interval for stream, in milliseconds
+
+ @param framesPerPaHostBuffer Size of PortAudio host buffer, in frames
+
+ @param suggestedLatency Suggested latency for stream component, in seconds
+
+ @return PortAudio error code (possibly paBufferTooBig or paInsufficientMemory)
+ */
+static PaError PaAsiHpi_SetupBuffers( PaAsiHpiStreamComponent *streamComp, uint32_t pollingInterval,
+                                      unsigned long framesPerPaHostBuffer, PaTime suggestedLatency )
+{
+    PaError result = paNoError;
+    PaAsiHpiStreamInfo streamInfo;
+    unsigned long hpiBufferSize = 0, paHostBufferSize = 0;
+
+    assert( streamComp );
+    assert( streamComp->hpiDevice );
+
+    /* Obtain size of hardware buffer of HPI stream, since we will be activating BBM shortly
+       and afterwards the buffer size will refer to the BBM (host-side) buffer.
+       This is necessary to enable reliable detection of xruns. */
+    PA_ENSURE_( PaAsiHpi_GetStreamInfo( streamComp, &streamInfo ) );
+    streamComp->hardwareBufferSize = streamInfo.bufferSize;
+    hpiBufferSize = streamInfo.bufferSize;
+
+    /* Check if BBM (background bus mastering) is to be enabled */
+    if( PA_ASIHPI_USE_BBM_ )
+    {
+        uint32_t bbmBufferSize = 0, preLatencyBufferSize = 0;
+        hpi_err_t hpiError = 0;
+        PaTime pollingOverhead;
+
+        /* Check overhead of Pa_Sleep() call (minimum sleep duration in ms -> OS dependent) */
+        pollingOverhead = PaUtil_GetTime();
+        Pa_Sleep( 0 );
+        pollingOverhead = 1000*(PaUtil_GetTime() - pollingOverhead);
+        PA_DEBUG(( "polling overhead = %f ms (length of 0-second sleep)\n", pollingOverhead ));
+        /* Obtain minimum recommended size for host buffer (in bytes) */
+        PA_ASIHPI_UNLESS_( HPI_StreamEstimateBufferSize( &streamComp->hpiFormat,
+                           pollingInterval + (uint32_t)ceil( pollingOverhead ),
+                           &bbmBufferSize ), paUnanticipatedHostError );
+        /* BBM places more stringent requirements on buffer size (see description */
+        /* of HPI_StreamEstimateBufferSize in HPI API document) */
+        bbmBufferSize *= 3;
+        /* Make sure the BBM buffer contains multiple PA host buffers */
+        if( bbmBufferSize < 3 * streamComp->bytesPerFrame * framesPerPaHostBuffer )
+            bbmBufferSize = 3 * streamComp->bytesPerFrame * framesPerPaHostBuffer;
+        /* Try to honor latency suggested by user by growing buffer (no decrease possible) */
+        if( suggestedLatency > 0.0 )
+        {
+            PaTime bufferDuration = ((PaTime)bbmBufferSize) / streamComp->bytesPerFrame
+                                    / streamComp->hpiFormat.dwSampleRate;
+            /* Don't decrease buffer */
+            if( bufferDuration < suggestedLatency )
+            {
+                /* Save old buffer size, to be retried if new size proves too big */
+                preLatencyBufferSize = bbmBufferSize;
+                bbmBufferSize = (uint32_t)ceil( suggestedLatency * streamComp->bytesPerFrame
+                                            * streamComp->hpiFormat.dwSampleRate );
+            }
+        }
+        /* Choose closest memory block boundary (HPI API document states that
+        "a buffer size of Nx4096 - 20 makes the best use of memory"
+        (under the entry for HPI_StreamEstimateBufferSize)) */
+        bbmBufferSize = ((uint32_t)ceil((bbmBufferSize + 20)/4096.0))*4096 - 20;
+        streamComp->hostBufferSize = bbmBufferSize;
+        /* Allocate BBM host buffer (this enables bus mastering transfers in background) */
+        if( streamComp->hpiDevice->streamIsOutput )
+            hpiError = HPI_OutStreamHostBufferAllocate( NULL,
+                       streamComp->hpiStream,
+                       bbmBufferSize );
+        else
+            hpiError = HPI_InStreamHostBufferAllocate( NULL,
+                       streamComp->hpiStream,
+                       bbmBufferSize );
+        if( hpiError )
+        {
+            /* Indicate that BBM is disabled */
+            streamComp->hostBufferSize = 0;
+            /* Retry with smaller buffer size (transfers will still work, but not via BBM) */
+            if( hpiError == HPI_ERROR_INVALID_DATASIZE )
+            {
+                /* Retry BBM allocation with smaller size if requested latency proved too big */
+                if( preLatencyBufferSize > 0 )
+                {
+                    PA_DEBUG(( "Retrying BBM allocation with smaller size (%d vs. %d bytes)\n",
+                               preLatencyBufferSize, bbmBufferSize ));
+                    bbmBufferSize = preLatencyBufferSize;
+                    if( streamComp->hpiDevice->streamIsOutput )
+                        hpiError = HPI_OutStreamHostBufferAllocate( NULL,
+                                   streamComp->hpiStream,
+                                   bbmBufferSize );
+                    else
+                        hpiError = HPI_InStreamHostBufferAllocate( NULL,
+                                   streamComp->hpiStream,
+                                   bbmBufferSize );
+                    /* Another round of error checking */
+                    if( hpiError )
+                    {
+                        PA_ASIHPI_REPORT_ERROR_( hpiError );
+                        /* No escapes this time */
+                        if( hpiError == HPI_ERROR_INVALID_DATASIZE )
+                        {
+                            result = paBufferTooBig;
+                            goto error;
+                        }
+                        else if( hpiError != HPI_ERROR_INVALID_OPERATION )
+                        {
+                            result = paUnanticipatedHostError;
+                            goto error;
+                        }
+                    }
+                    else
+                    {
+                        streamComp->hostBufferSize = bbmBufferSize;
+                        hpiBufferSize = bbmBufferSize;
+                    }
+                }
+                else
+                {
+                    result = paBufferTooBig;
+                    goto error;
+                }
+            }
+            /* If BBM not supported, foreground transfers will be used, but not a show-stopper */
+            /* Anything else is an error */
+            else if (( hpiError != HPI_ERROR_INVALID_OPERATION ) &&
+		     ( hpiError != HPI_ERROR_INVALID_FUNC ))
+            {
+                PA_ASIHPI_REPORT_ERROR_( hpiError );
+                result = paUnanticipatedHostError;
+                goto error;
+            }
+        }
+        else
+        {
+            hpiBufferSize = bbmBufferSize;
+        }
+    }
+
+    /* Final check of buffer size */
+    paHostBufferSize = streamComp->bytesPerFrame * framesPerPaHostBuffer;
+    if( hpiBufferSize < 3*paHostBufferSize )
+    {
+        result = paBufferTooBig;
+        goto error;
+    }
+    /* Set cap on output buffer size, based on latency suggestions */
+    if( streamComp->hpiDevice->streamIsOutput )
+    {
+        PaTime latency = suggestedLatency > 0.0 ? suggestedLatency :
+                         streamComp->hpiDevice->baseDeviceInfo.defaultHighOutputLatency;
+        streamComp->outputBufferCap =
+            (uint32_t)ceil( latency * streamComp->bytesPerFrame * streamComp->hpiFormat.dwSampleRate );
+        /* The cap should not be too small, to prevent underflow */
+        if( streamComp->outputBufferCap < 4*paHostBufferSize )
+            streamComp->outputBufferCap = 4*paHostBufferSize;
+    }
+    else
+    {
+        streamComp->outputBufferCap = 0;
+    }
+    /* Temp buffer size should be multiple of PA host buffer size (or 1x, if using fixed blocks) */
+    streamComp->tempBufferSize = paHostBufferSize;
+    /* Allocate temp buffer */
+    PA_UNLESS_( streamComp->tempBuffer = (uint8_t *)PaUtil_AllocateMemory( streamComp->tempBufferSize ),
+                paInsufficientMemory );
+error:
+    return result;
+}
+
+
+/** Opens PortAudio stream.
+ This determines a suitable value for framesPerBuffer, if the user didn't specify it,
+ based on the suggested latency. It then opens each requested stream direction with the
+ appropriate stream format, and allocates the required stream buffers. It sets up the
+ various PortAudio structures dealing with streams, and estimates the stream latency.
+
+ See pa_hostapi.h for a list of validity guarantees made about OpenStream parameters.
+
+ @param hostApi Pointer to host API struct
+
+ @param s List of open streams, where successfully opened stream will go
+
+ @param inputParameters Pointer to stream parameter struct for input side of stream
+
+ @param outputParameters Pointer to stream parameter struct for output side of stream
+
+ @param sampleRate Desired sample rate
+
+ @param framesPerBuffer Desired number of frames per buffer passed to user callback
+                        (or chunk size for blocking stream)
+
+ @param streamFlags Stream flags
+
+ @param streamCallback Pointer to user callback function (zero for blocking interface)
+
+ @param userData Pointer to user data that will be passed to callback function along with data
+
+ @return PortAudio error code
+*/
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                           PaStream **s,
+                           const PaStreamParameters *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           double sampleRate,
+                           unsigned long framesPerBuffer,
+                           PaStreamFlags streamFlags,
+                           PaStreamCallback *streamCallback,
+                           void *userData )
+{
+    PaError result = paNoError;
+    PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+    PaAsiHpiStream *stream = NULL;
+    unsigned long framesPerHostBuffer = framesPerBuffer;
+    int inputChannelCount = 0, outputChannelCount = 0;
+    PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0;
+    PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0;
+    PaTime maxSuggestedLatency = 0.0;
+
+    /* Validate platform-specific flags -> none expected for HPI */
+    if( (streamFlags & paPlatformSpecificFlags) != 0 )
+        return paInvalidFlag; /* unexpected platform-specific flag */
+
+    /* Create blank stream structure */
+    PA_UNLESS_( stream = (PaAsiHpiStream *)PaUtil_AllocateMemory( sizeof(PaAsiHpiStream) ),
+                paInsufficientMemory );
+    memset( stream, 0, sizeof(PaAsiHpiStream) );
+
+    /* If the number of frames per buffer is unspecified, we have to come up with one. */
+    if( framesPerHostBuffer == paFramesPerBufferUnspecified )
+    {
+        if( inputParameters )
+            maxSuggestedLatency = inputParameters->suggestedLatency;
+        if( outputParameters && (outputParameters->suggestedLatency > maxSuggestedLatency) )
+            maxSuggestedLatency = outputParameters->suggestedLatency;
+        /* Use suggested latency if available */
+        if( maxSuggestedLatency > 0.0 )
+            framesPerHostBuffer = (unsigned long)ceil( maxSuggestedLatency * sampleRate );
+        else
+            /* AudioScience cards like BIG buffers by default */
+            framesPerHostBuffer = 4096;
+    }
+    /* Lower bounds on host buffer size, due to polling and HPI constraints */
+    if( 1000.0*framesPerHostBuffer/sampleRate < PA_ASIHPI_MIN_POLLING_INTERVAL_ )
+        framesPerHostBuffer = (unsigned long)ceil( sampleRate * PA_ASIHPI_MIN_POLLING_INTERVAL_ / 1000.0 );
+    /*    if( framesPerHostBuffer < PA_ASIHPI_MIN_FRAMES_ )
+            framesPerHostBuffer = PA_ASIHPI_MIN_FRAMES_; */
+    /* Efficient if host buffer size is integer multiple of user buffer size */
+    if( framesPerBuffer > 0 )
+        framesPerHostBuffer = (unsigned long)ceil( (double)framesPerHostBuffer / framesPerBuffer ) * framesPerBuffer;
+    /* Buffer should always be a multiple of 4 bytes to facilitate 32-bit PCI transfers.
+     By keeping the frames a multiple of 4, this is ensured even for 8-bit mono sound. */
+    framesPerHostBuffer = (framesPerHostBuffer / 4) * 4;
+    /* Polling is based on time length (in milliseconds) of user-requested block size */
+    stream->pollingInterval = (uint32_t)ceil( 1000.0*framesPerHostBuffer/sampleRate );
+    assert( framesPerHostBuffer > 0 );
+
+    /* Open underlying streams, check formats and allocate buffers */
+    if( inputParameters )
+    {
+        /* Create blank stream component structure */
+        PA_UNLESS_( stream->input = (PaAsiHpiStreamComponent *)PaUtil_AllocateMemory( sizeof(PaAsiHpiStreamComponent) ),
+                    paInsufficientMemory );
+        memset( stream->input, 0, sizeof(PaAsiHpiStreamComponent) );
+        /* Create/validate format */
+        PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, inputParameters, sampleRate,
+                                           &stream->input->hpiDevice, &stream->input->hpiFormat ) );
+        /* Open stream and set format */
+        PA_ENSURE_( PaAsiHpi_OpenInput( hostApi, stream->input->hpiDevice, &stream->input->hpiFormat,
+                                        &stream->input->hpiStream ) );
+        inputChannelCount = inputParameters->channelCount;
+        inputSampleFormat = inputParameters->sampleFormat;
+        hostInputSampleFormat = PaAsiHpi_HpiToPaFormat( stream->input->hpiFormat.wFormat );
+        stream->input->bytesPerFrame = inputChannelCount * Pa_GetSampleSize( hostInputSampleFormat );
+        assert( stream->input->bytesPerFrame > 0 );
+        /* Allocate host and temp buffers of appropriate size */
+        PA_ENSURE_( PaAsiHpi_SetupBuffers( stream->input, stream->pollingInterval,
+                                           framesPerHostBuffer, inputParameters->suggestedLatency ) );
+    }
+    if( outputParameters )
+    {
+        /* Create blank stream component structure */
+        PA_UNLESS_( stream->output = (PaAsiHpiStreamComponent *)PaUtil_AllocateMemory( sizeof(PaAsiHpiStreamComponent) ),
+                    paInsufficientMemory );
+        memset( stream->output, 0, sizeof(PaAsiHpiStreamComponent) );
+        /* Create/validate format */
+        PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, outputParameters, sampleRate,
+                                           &stream->output->hpiDevice, &stream->output->hpiFormat ) );
+        /* Open stream and check format */
+        PA_ENSURE_( PaAsiHpi_OpenOutput( hostApi, stream->output->hpiDevice,
+                                         &stream->output->hpiFormat,
+                                         &stream->output->hpiStream ) );
+        outputChannelCount = outputParameters->channelCount;
+        outputSampleFormat = outputParameters->sampleFormat;
+        hostOutputSampleFormat = PaAsiHpi_HpiToPaFormat( stream->output->hpiFormat.wFormat );
+        stream->output->bytesPerFrame = outputChannelCount * Pa_GetSampleSize( hostOutputSampleFormat );
+        /* Allocate host and temp buffers of appropriate size */
+        PA_ENSURE_( PaAsiHpi_SetupBuffers( stream->output, stream->pollingInterval,
+                                           framesPerHostBuffer, outputParameters->suggestedLatency ) );
+    }
+
+    /* Determine maximum frames per host buffer (least common denominator of input/output) */
+    if( inputParameters && outputParameters )
+    {
+        stream->maxFramesPerHostBuffer = PA_MIN( stream->input->tempBufferSize / stream->input->bytesPerFrame,
+                                         stream->output->tempBufferSize / stream->output->bytesPerFrame );
+    }
+    else
+    {
+        stream->maxFramesPerHostBuffer = inputParameters ? stream->input->tempBufferSize / stream->input->bytesPerFrame
+                                         : stream->output->tempBufferSize / stream->output->bytesPerFrame;
+    }
+    assert( stream->maxFramesPerHostBuffer > 0 );
+    /* Initialize various other stream parameters */
+    stream->neverDropInput = streamFlags & paNeverDropInput;
+    stream->state = paAsiHpiStoppedState;
+
+    /* Initialize either callback or blocking interface */
+    if( streamCallback )
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->baseStreamRep,
+                                               &hpiHostApi->callbackStreamInterface,
+                                               streamCallback, userData );
+        stream->callbackMode = 1;
+    }
+    else
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->baseStreamRep,
+                                               &hpiHostApi->blockingStreamInterface,
+                                               streamCallback, userData );
+        /* Pre-allocate non-interleaved user buffer pointers for blocking interface */
+        PA_UNLESS_( stream->blockingUserBufferCopy =
+                        PaUtil_AllocateMemory( sizeof(void *) * PA_MAX( inputChannelCount, outputChannelCount ) ),
+                    paInsufficientMemory );
+        stream->callbackMode = 0;
+    }
+    PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+
+    /* Following pa_linux_alsa's lead, we operate with fixed host buffer size by default, */
+    /* since other modes will invariably lead to block adaption (maybe Bounded better?) */
+    PA_ENSURE_( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+                inputChannelCount, inputSampleFormat, hostInputSampleFormat,
+                outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
+                sampleRate, streamFlags,
+                framesPerBuffer, framesPerHostBuffer, paUtilFixedHostBufferSize,
+                streamCallback, userData ) );
+
+    stream->baseStreamRep.streamInfo.structVersion = 1;
+    stream->baseStreamRep.streamInfo.sampleRate = sampleRate;
+    /* Determine input latency from buffer processor and buffer sizes */
+    if( stream->input )
+    {
+        PaTime bufferDuration = ( stream->input->hostBufferSize + stream->input->hardwareBufferSize )
+                                / sampleRate / stream->input->bytesPerFrame;
+        stream->baseStreamRep.streamInfo.inputLatency =
+            bufferDuration +
+            ((PaTime)PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor ) -
+                stream->maxFramesPerHostBuffer) / sampleRate;
+        assert( stream->baseStreamRep.streamInfo.inputLatency > 0.0 );
+    }
+    /* Determine output latency from buffer processor and buffer sizes */
+    if( stream->output )
+    {
+        PaTime bufferDuration = ( stream->output->hostBufferSize + stream->output->hardwareBufferSize )
+                                / sampleRate / stream->output->bytesPerFrame;
+        /* Take buffer size cap into account (see PaAsiHpi_WaitForFrames) */
+        if( !stream->input && (stream->output->outputBufferCap > 0) )
+        {
+            bufferDuration = PA_MIN( bufferDuration,
+                                     stream->output->outputBufferCap / sampleRate / stream->output->bytesPerFrame );
+        }
+        stream->baseStreamRep.streamInfo.outputLatency =
+            bufferDuration +
+            ((PaTime)PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor ) -
+                stream->maxFramesPerHostBuffer) / sampleRate;
+        assert( stream->baseStreamRep.streamInfo.outputLatency > 0.0 );
+    }
+
+    /* Report stream info, for debugging purposes */
+    PaAsiHpi_StreamDump( stream );
+
+    /* Save initialized stream to PA stream list */
+    *s = (PaStream*)stream;
+    return result;
+
+error:
+    CloseStream( (PaStream*)stream );
+    return result;
+}
+
+
+/** Close PortAudio stream.
+ When CloseStream() is called, the multi-api layer ensures that the stream has already
+ been stopped or aborted. This closes the underlying HPI streams and deallocates stream
+ buffers and structs.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError CloseStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+    /* If stream is already gone, all is well */
+    if( stream == NULL )
+        return paNoError;
+
+    /* Generic stream cleanup */
+    PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+    PaUtil_TerminateStreamRepresentation( &stream->baseStreamRep );
+
+    /* Implementation-specific details - close internal streams */
+    if( stream->input )
+    {
+        /* Close HPI stream (freeing BBM host buffer in the process, if used) */
+        if( stream->input->hpiStream )
+        {
+            PA_ASIHPI_UNLESS_( HPI_InStreamClose( NULL,
+                                                  stream->input->hpiStream ), paUnanticipatedHostError );
+        }
+        /* Free temp buffer and stream component */
+        PaUtil_FreeMemory( stream->input->tempBuffer );
+        PaUtil_FreeMemory( stream->input );
+    }
+    if( stream->output )
+    {
+        /* Close HPI stream (freeing BBM host buffer in the process, if used) */
+        if( stream->output->hpiStream )
+        {
+            PA_ASIHPI_UNLESS_( HPI_OutStreamClose( NULL,
+                                                   stream->output->hpiStream ), paUnanticipatedHostError );
+        }
+        /* Free temp buffer and stream component */
+        PaUtil_FreeMemory( stream->output->tempBuffer );
+        PaUtil_FreeMemory( stream->output );
+    }
+
+    PaUtil_FreeMemory( stream->blockingUserBufferCopy );
+    PaUtil_FreeMemory( stream );
+
+error:
+    return result;
+}
+
+
+/** Prime HPI output stream with silence.
+ This resets the output stream and uses PortAudio helper routines to fill the
+ temp buffer with silence. It then writes two host buffers to the stream. This is supposed
+ to be called before the stream is started. It has no effect on input-only streams.
+
+ @param stream Pointer to stream struct
+
+ @return PortAudio error code
+ */
+static PaError PaAsiHpi_PrimeOutputWithSilence( PaAsiHpiStream *stream )
+{
+    PaError result = paNoError;
+    PaAsiHpiStreamComponent *out;
+    PaUtilZeroer *zeroer;
+    PaSampleFormat outputFormat;
+    assert( stream );
+    out = stream->output;
+    /* Only continue if stream has output channels */
+    if( !out )
+        return result;
+    assert( out->tempBuffer );
+
+    /* Clear all existing data in hardware playback buffer */
+    PA_ASIHPI_UNLESS_( HPI_OutStreamReset( NULL,
+                                           out->hpiStream ), paUnanticipatedHostError );
+    /* Fill temp buffer with silence */
+    outputFormat = PaAsiHpi_HpiToPaFormat( out->hpiFormat.wFormat );
+    zeroer = PaUtil_SelectZeroer( outputFormat );
+    zeroer(out->tempBuffer, 1, out->tempBufferSize / Pa_GetSampleSize(outputFormat) );
+    /* Write temp buffer to hardware fifo twice, to get started */
+    PA_ASIHPI_UNLESS_( HPI_OutStreamWriteBuf( NULL, out->hpiStream,
+                                              out->tempBuffer, out->tempBufferSize, &out->hpiFormat),
+                                              paUnanticipatedHostError );
+    PA_ASIHPI_UNLESS_( HPI_OutStreamWriteBuf( NULL, out->hpiStream,
+                                              out->tempBuffer, out->tempBufferSize, &out->hpiFormat),
+                                              paUnanticipatedHostError );
+error:
+    return result;
+}
+
+
+/** Start HPI streams (both input + output).
+ This starts all HPI streams in the PortAudio stream. Output streams are first primed with
+ silence, if required. After this call the PA stream is in the Active state.
+
+ @todo Implement priming via the user callback
+
+ @param stream Pointer to stream struct
+
+ @param outputPrimed True if output is already primed (if false, silence will be loaded before starting)
+
+ @return PortAudio error code
+ */
+static PaError PaAsiHpi_StartStream( PaAsiHpiStream *stream, int outputPrimed )
+{
+    PaError result = paNoError;
+
+    if( stream->input )
+    {
+        PA_ASIHPI_UNLESS_( HPI_InStreamStart( NULL,
+                                              stream->input->hpiStream ), paUnanticipatedHostError );
+    }
+    if( stream->output )
+    {
+        if( !outputPrimed )
+        {
+            /* Buffer isn't primed, so load stream with silence */
+            PA_ENSURE_( PaAsiHpi_PrimeOutputWithSilence( stream ) );
+        }
+        PA_ASIHPI_UNLESS_( HPI_OutStreamStart( NULL,
+                                               stream->output->hpiStream ), paUnanticipatedHostError );
+    }
+    stream->state = paAsiHpiActiveState;
+    stream->callbackFinished = 0;
+
+    /* Report stream info for debugging purposes */
+    /*    PaAsiHpi_StreamDump( stream );   */
+
+error:
+    return result;
+}
+
+
+/** Start PortAudio stream.
+ If the stream has a callback interface, this starts a helper thread to feed the user callback.
+ The thread will then take care of starting the HPI streams, and this function will block
+ until the streams actually start. In the case of a blocking interface, the HPI streams
+ are simply started.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError StartStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+    assert( stream );
+
+    /* Ready the processor */
+    PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
+
+    if( stream->callbackMode )
+    {
+        /* Create and start callback engine thread */
+        /* Also waits 1 second for stream to be started by engine thread (otherwise aborts) */
+        PA_ENSURE_( PaUnixThread_New( &stream->thread, &CallbackThreadFunc, stream, 1., 0 /*rtSched*/ ) );
+    }
+    else
+    {
+        PA_ENSURE_( PaAsiHpi_StartStream( stream, 0 ) );
+    }
+
+error:
+    return result;
+}
+
+
+/** Stop HPI streams (input + output), either softly or abruptly.
+ If abort is false, the function blocks until the output stream is drained, otherwise it
+ stops immediately and discards data in the stream hardware buffers.
+
+ This function is safe to call from the callback engine thread as well as the main thread.
+
+ @param stream Pointer to stream struct
+
+ @param abort True if samples in output buffer should be discarded (otherwise blocks until stream is done)
+
+ @return PortAudio error code
+
+ */
+static PaError PaAsiHpi_StopStream( PaAsiHpiStream *stream, int abort )
+{
+    PaError result = paNoError;
+
+    assert( stream );
+
+    /* Input channels */
+    if( stream->input )
+    {
+        PA_ASIHPI_UNLESS_( HPI_InStreamReset( NULL,
+                                              stream->input->hpiStream ), paUnanticipatedHostError );
+    }
+    /* Output channels */
+    if( stream->output )
+    {
+        if( !abort )
+        {
+            /* Wait until HPI output stream is drained */
+            while( 1 )
+            {
+                PaAsiHpiStreamInfo streamInfo;
+                PaTime timeLeft;
+
+                /* Obtain number of samples waiting to be played */
+                PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &streamInfo ) );
+                /* Check if stream is drained */
+                if( (streamInfo.state != HPI_STATE_PLAYING) &&
+                        (streamInfo.dataSize < stream->output->bytesPerFrame * PA_ASIHPI_MIN_FRAMES_) )
+                    break;
+                /* Sleep amount of time represented by remaining samples */
+                timeLeft = 1000.0 * streamInfo.dataSize / stream->output->bytesPerFrame
+                           / stream->baseStreamRep.streamInfo.sampleRate;
+                Pa_Sleep( (long)ceil( timeLeft ) );
+            }
+        }
+        PA_ASIHPI_UNLESS_( HPI_OutStreamReset( NULL,
+                                               stream->output->hpiStream ), paUnanticipatedHostError );
+    }
+
+    /* Report stream info for debugging purposes */
+    /*    PaAsiHpi_StreamDump( stream ); */
+
+error:
+    return result;
+}
+
+
+/** Stop or abort PortAudio stream.
+
+ This function is used to explicitly stop the PortAudio stream (via StopStream/AbortStream),
+ as opposed to the situation when the callback finishes with a result other than paContinue.
+ If a stream is in callback mode we will have to inspect whether the background thread has
+ finished, or we will have to take it out. In either case we join the thread before returning.
+ In blocking mode, we simply tell HPI to stop abruptly (abort) or finish buffers (drain).
+ The PortAudio stream will be in the Stopped state after a call to this function.
+
+ Don't call this from the callback engine thread!
+
+ @param stream Pointer to stream struct
+
+ @param abort True if samples in output buffer should be discarded (otherwise blocks until stream is done)
+
+ @return PortAudio error code
+*/
+static PaError PaAsiHpi_ExplicitStop( PaAsiHpiStream *stream, int abort )
+{
+    PaError result = paNoError;
+
+    /* First deal with the callback thread, cancelling and/or joining it if necessary */
+    if( stream->callbackMode )
+    {
+        PaError threadRes;
+        stream->callbackAbort = abort;
+        if( abort )
+        {
+            PA_DEBUG(( "Aborting callback\n" ));
+        }
+        else
+        {
+            PA_DEBUG(( "Stopping callback\n" ));
+        }
+        PA_ENSURE_( PaUnixThread_Terminate( &stream->thread, !abort, &threadRes ) );
+        if( threadRes != paNoError )
+        {
+            PA_DEBUG(( "Callback thread returned: %d\n", threadRes ));
+        }
+    }
+    else
+    {
+        PA_ENSURE_( PaAsiHpi_StopStream( stream, abort ) );
+    }
+
+    stream->state = paAsiHpiStoppedState;
+
+error:
+    return result;
+}
+
+
+/** Stop PortAudio stream.
+ This blocks until the output buffers are drained.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError StopStream( PaStream *s )
+{
+    return PaAsiHpi_ExplicitStop( (PaAsiHpiStream *) s, 0 );
+}
+
+
+/** Abort PortAudio stream.
+ This discards any existing data in output buffers and stops the stream immediately.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError AbortStream( PaStream *s )
+{
+    return PaAsiHpi_ExplicitStop( (PaAsiHpiStream * ) s, 1 );
+}
+
+
+/** Determine whether the stream is stopped.
+ A stream is considered to be stopped prior to a successful call to StartStream and after
+ a successful call to StopStream or AbortStream. If a stream callback returns a value other
+ than paContinue the stream is NOT considered to be stopped (it is in CallbackFinished state).
+
+ @param s Pointer to PortAudio stream
+
+ @return Returns one (1) when the stream is stopped, zero (0) when the stream is running, or
+         a PaErrorCode (which are always negative) if PortAudio is not initialized or an
+         error is encountered.
+*/
+static PaError IsStreamStopped( PaStream *s )
+{
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+    assert( stream );
+    return stream->state == paAsiHpiStoppedState ? 1 : 0;
+}
+
+
+/** Determine whether the stream is active.
+ A stream is active after a successful call to StartStream(), until it becomes inactive either
+ as a result of a call to StopStream() or AbortStream(), or as a result of a return value
+ other than paContinue from the stream callback. In the latter case, the stream is considered
+ inactive after the last buffer has finished playing.
+
+ @param s Pointer to PortAudio stream
+
+ @return Returns one (1) when the stream is active (i.e. playing or recording audio),
+         zero (0) when not playing, or a PaErrorCode (which are always negative)
+         if PortAudio is not initialized or an error is encountered.
+*/
+static PaError IsStreamActive( PaStream *s )
+{
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+    assert( stream );
+    return stream->state == paAsiHpiActiveState ? 1 : 0;
+}
+
+
+/** Returns current stream time.
+ This corresponds to the system clock. The clock should run continuously while the stream
+ is open, i.e. between calls to OpenStream() and CloseStream(), therefore a frame counter
+ is not good enough.
+
+ @param s Pointer to PortAudio stream
+
+ @return Stream time, in seconds
+ */
+static PaTime GetStreamTime( PaStream *s )
+{
+    return PaUtil_GetTime();
+}
+
+
+/** Returns CPU load.
+
+ @param s Pointer to PortAudio stream
+
+ @return CPU load (0.0 if blocking interface is used)
+ */
+static double GetStreamCpuLoad( PaStream *s )
+{
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+    return stream->callbackMode ? PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ) : 0.0;
+}
+
+/* --------------------------- Callback Interface --------------------------- */
+
+/** Exit routine which is called when callback thread quits.
+ This takes care of stopping the HPI streams (either waiting for output to finish, or
+ abruptly). It also calls the user-supplied StreamFinished callback, and sets the
+ stream state to CallbackFinished if it was reached via a non-paContinue return from
+ the user callback function.
+
+ @param userData A pointer to an open stream previously created with Pa_OpenStream
+ */
+static void PaAsiHpi_OnThreadExit( void *userData )
+{
+    PaAsiHpiStream *stream = (PaAsiHpiStream *) userData;
+
+    assert( stream );
+
+    PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer );
+
+    PA_DEBUG(( "%s: Stopping HPI streams\n", __FUNCTION__ ));
+    PaAsiHpi_StopStream( stream, stream->callbackAbort );
+    PA_DEBUG(( "%s: Stoppage\n", __FUNCTION__ ));
+
+    /* Eventually notify user all buffers have played */
+    if( stream->baseStreamRep.streamFinishedCallback )
+    {
+        stream->baseStreamRep.streamFinishedCallback( stream->baseStreamRep.userData );
+    }
+
+    /* Unfortunately both explicit calls to Stop/AbortStream (leading to Stopped state)
+     and implicit stops via paComplete/paAbort (leading to CallbackFinished state)
+     end up here - need another flag to remind us which is the case */
+    if( stream->callbackFinished )
+        stream->state = paAsiHpiCallbackFinishedState;
+}
+
+
+/** Wait until there is enough frames to fill a host buffer.
+ The routine attempts to sleep until at least a full host buffer can be retrieved from the
+ input HPI stream and passed to the output HPI stream. It will first sleep until enough
+ output space is available, as this is usually easily achievable. If it is an output-only
+ stream, it will also sleep if the hardware buffer is too full, thereby throttling the
+ filling of the output buffer and reducing output latency. The routine then blocks until
+ enough input samples are available, unless this will cause an output underflow. In the
+ process, input overflows and output underflows are indicated.
+
+ @param stream Pointer to stream struct
+
+ @param framesAvail Returns the number of available frames
+
+ @param cbFlags Overflows and underflows indicated in here
+
+ @return PortAudio error code (only paUnanticipatedHostError expected)
+ */
+static PaError PaAsiHpi_WaitForFrames( PaAsiHpiStream *stream, unsigned long *framesAvail,
+                                       PaStreamCallbackFlags *cbFlags )
+{
+    PaError result = paNoError;
+    double sampleRate;
+    unsigned long framesTarget;
+    uint32_t outputData = 0, outputSpace = 0, inputData = 0, framesLeft = 0;
+
+    assert( stream );
+    assert( stream->input || stream->output );
+
+    sampleRate = stream->baseStreamRep.streamInfo.sampleRate;
+    /* We have to come up with this much frames on both input and output */
+    framesTarget = stream->bufferProcessor.framesPerHostBuffer;
+    assert( framesTarget > 0 );
+
+    while( 1 )
+    {
+        PaAsiHpiStreamInfo info;
+        /* Check output first, as this takes priority in the default full-duplex mode */
+        if( stream->output )
+        {
+            PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+            /* Wait until enough space is available in output buffer to receive a full block */
+            if( info.availableFrames < framesTarget )
+            {
+                framesLeft = framesTarget - info.availableFrames;
+                Pa_Sleep( (long)ceil( 1000 * framesLeft / sampleRate ) );
+                continue;
+            }
+            /* Wait until the data in hardware buffer has dropped to a sensible level.
+             Without this, the hardware buffer quickly fills up in the absence of an input
+             stream to regulate its data rate (if data generation is fast). This leads to
+             large latencies, as the AudioScience hardware buffers are humongous.
+             This is similar to the default "Hardware Buffering=off" option in the
+             AudioScience WAV driver. */
+            if( !stream->input && (stream->output->outputBufferCap > 0) &&
+                    ( info.totalBufferedData > stream->output->outputBufferCap / stream->output->bytesPerFrame ) )
+            {
+                framesLeft = info.totalBufferedData - stream->output->outputBufferCap / stream->output->bytesPerFrame;
+                Pa_Sleep( (long)ceil( 1000 * framesLeft / sampleRate ) );
+                continue;
+            }
+            outputData = info.totalBufferedData;
+            outputSpace = info.availableFrames;
+            /* Report output underflow to callback */
+            if( info.underflow )
+            {
+                *cbFlags |= paOutputUnderflow;
+            }
+        }
+
+        /* Now check input side */
+        if( stream->input )
+        {
+            PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+            /* If a full block of samples hasn't been recorded yet, wait for it if possible */
+            if( info.availableFrames < framesTarget )
+            {
+                framesLeft = framesTarget - info.availableFrames;
+                /* As long as output is not disrupted in the process, wait for a full
+                block of input samples */
+                if( !stream->output || (outputData > framesLeft) )
+                {
+                    Pa_Sleep( (long)ceil( 1000 * framesLeft / sampleRate ) );
+                    continue;
+                }
+            }
+            inputData = info.availableFrames;
+            /** @todo The paInputOverflow flag should be set in the callback containing the
+             first input sample following the overflow. That means the block currently sitting
+             at the fore-front of recording, i.e. typically the one containing the newest (last)
+             sample in the HPI buffer system. This is most likely not the same as the current
+             block of data being passed to the callback. The current overflow should ideally
+             be noted in an overflow list of sorts, with an indication of when it should be
+             reported. The trouble starts if there are several separate overflow incidents,
+             given a big input buffer. Oh well, something to try out later... */
+            if( info.overflow )
+            {
+                *cbFlags |= paInputOverflow;
+            }
+        }
+        break;
+    }
+    /* Full-duplex stream */
+    if( stream->input && stream->output )
+    {
+        if( outputSpace >= framesTarget )
+            *framesAvail = outputSpace;
+        /* If input didn't make the target, keep the output count instead (input underflow) */
+        if( (inputData >= framesTarget) && (inputData < outputSpace) )
+            *framesAvail = inputData;
+    }
+    else
+    {
+        *framesAvail = stream->input ? inputData : outputSpace;
+    }
+
+error:
+    return result;
+}
+
+
+/** Obtain recording, current and playback timestamps of stream.
+ The current time is determined by the system clock. This "now" timestamp occurs at the
+ forefront of recording (and playback in the full-duplex case), which happens later than the
+ input timestamp by an amount equal to the total number of recorded frames in the input buffer.
+ The output timestamp indicates when the next generated sample will actually be played. This
+ happens after all the samples currently in the output buffer are played. The output timestamp
+ therefore follows the current timestamp by an amount equal to the number of frames yet to be
+ played back in the output buffer.
+
+ If the current timestamp is the present, the input timestamp is in the past and the output
+ timestamp is in the future.
+
+ @param stream Pointer to stream struct
+
+ @param timeInfo Pointer to timeInfo struct that will contain timestamps
+ */
+static void PaAsiHpi_CalculateTimeInfo( PaAsiHpiStream *stream, PaStreamCallbackTimeInfo *timeInfo )
+{
+    PaAsiHpiStreamInfo streamInfo;
+    double sampleRate;
+
+    assert( stream );
+    assert( timeInfo );
+    sampleRate = stream->baseStreamRep.streamInfo.sampleRate;
+
+    /* The current time ("now") is at the forefront of both recording and playback */
+    timeInfo->currentTime = GetStreamTime( (PaStream *)stream );
+    /* The last sample in the input buffer was recorded just now, so the first sample
+     happened (number of recorded samples)/sampleRate ago */
+    timeInfo->inputBufferAdcTime = timeInfo->currentTime;
+    if( stream->input )
+    {
+        PaAsiHpi_GetStreamInfo( stream->input, &streamInfo );
+        timeInfo->inputBufferAdcTime -= streamInfo.totalBufferedData / sampleRate;
+    }
+    /* The first of the outgoing samples will be played after all the samples in the output
+     buffer is done */
+    timeInfo->outputBufferDacTime = timeInfo->currentTime;
+    if( stream->output )
+    {
+        PaAsiHpi_GetStreamInfo( stream->output, &streamInfo );
+        timeInfo->outputBufferDacTime += streamInfo.totalBufferedData / sampleRate;
+    }
+}
+
+
+/** Read from HPI input stream and register buffers.
+ This reads data from the HPI input stream (if it exists) and registers the temp stream
+ buffers of both input and output streams with the buffer processor. In the process it also
+ handles input underflows in the full-duplex case.
+
+ @param stream Pointer to stream struct
+
+ @param numFrames On entrance the number of available frames, on exit the number of
+                  received frames
+
+ @param cbFlags Indicates overflows and underflows
+
+ @return PortAudio error code
+ */
+static PaError PaAsiHpi_BeginProcessing( PaAsiHpiStream *stream, unsigned long *numFrames,
+        PaStreamCallbackFlags *cbFlags )
+{
+    PaError result = paNoError;
+
+    assert( stream );
+    if( *numFrames > stream->maxFramesPerHostBuffer )
+        *numFrames = stream->maxFramesPerHostBuffer;
+
+    if( stream->input )
+    {
+        PaAsiHpiStreamInfo info;
+
+        uint32_t framesToGet = *numFrames;
+
+        /* Check for overflows and underflows yet again */
+        PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+        if( info.overflow )
+        {
+            *cbFlags |= paInputOverflow;
+        }
+        /* Input underflow if less than expected number of samples pitch up */
+        if( framesToGet > info.availableFrames )
+        {
+            PaUtilZeroer *zeroer;
+            PaSampleFormat inputFormat;
+
+            /* Never call an input-only stream with InputUnderflow set */
+            if( stream->output )
+                *cbFlags |= paInputUnderflow;
+            framesToGet = info.availableFrames;
+            /* Fill temp buffer with silence (to make up for missing input samples) */
+            inputFormat = PaAsiHpi_HpiToPaFormat( stream->input->hpiFormat.wFormat );
+            zeroer = PaUtil_SelectZeroer( inputFormat );
+            zeroer(stream->input->tempBuffer, 1,
+                   stream->input->tempBufferSize / Pa_GetSampleSize(inputFormat) );
+        }
+
+        /* Read block of data into temp buffer */
+        PA_ASIHPI_UNLESS_( HPI_InStreamReadBuf( NULL,
+                                             stream->input->hpiStream,
+                                             stream->input->tempBuffer,
+                                             framesToGet * stream->input->bytesPerFrame),
+                           paUnanticipatedHostError );
+        /* Register temp buffer with buffer processor (always FULL buffer) */
+        PaUtil_SetInputFrameCount( &stream->bufferProcessor, *numFrames );
+        /* HPI interface only allows interleaved channels */
+        PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor,
+                                            0, stream->input->tempBuffer,
+                                            stream->input->hpiFormat.wChannels );
+    }
+    if( stream->output )
+    {
+        /* Register temp buffer with buffer processor */
+        PaUtil_SetOutputFrameCount( &stream->bufferProcessor, *numFrames );
+        /* HPI interface only allows interleaved channels */
+        PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor,
+                                             0, stream->output->tempBuffer,
+                                             stream->output->hpiFormat.wChannels );
+    }
+
+error:
+    return result;
+}
+
+
+/** Flush output buffers to HPI output stream.
+ This completes the processing cycle by writing the temp buffer to the HPI interface.
+ Additional output underflows are caught before data is written to the stream, as this
+ action typically remedies the underflow and hides it in the process.
+
+ @param stream Pointer to stream struct
+
+ @param numFrames The number of frames to write to the output stream
+
+ @param cbFlags Indicates overflows and underflows
+ */
+static PaError PaAsiHpi_EndProcessing( PaAsiHpiStream *stream, unsigned long numFrames,
+                                       PaStreamCallbackFlags *cbFlags )
+{
+    PaError result = paNoError;
+
+    assert( stream );
+
+    if( stream->output )
+    {
+        PaAsiHpiStreamInfo info;
+        /* Check for underflows after the (potentially time-consuming) callback */
+        PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+        if( info.underflow )
+        {
+            *cbFlags |= paOutputUnderflow;
+        }
+
+        /* Write temp buffer to HPI stream */
+        PA_ASIHPI_UNLESS_( HPI_OutStreamWriteBuf( NULL,
+                                           stream->output->hpiStream,
+                                           stream->output->tempBuffer,
+                                           numFrames * stream->output->bytesPerFrame,
+                                           &stream->output->hpiFormat),
+                           paUnanticipatedHostError );
+    }
+
+error:
+    return result;
+}
+
+
+/** Main callback engine.
+ This function runs in a separate thread and does all the work of fetching audio data from
+ the AudioScience card via the HPI interface, feeding it to the user callback via the buffer
+ processor, and delivering the resulting output data back to the card via HPI calls.
+ It is started and terminated when the PortAudio stream is started and stopped, and starts
+ the HPI streams on startup.
+
+ @param userData A pointer to an open stream previously created with Pa_OpenStream.
+*/
+static void *CallbackThreadFunc( void *userData )
+{
+    PaError result = paNoError;
+    PaAsiHpiStream *stream = (PaAsiHpiStream *) userData;
+    int callbackResult = paContinue;
+
+    assert( stream );
+
+    /* Cleanup routine stops streams on thread exit */
+    pthread_cleanup_push( &PaAsiHpi_OnThreadExit, stream );
+
+    /* Start HPI streams and notify parent when we're done */
+    PA_ENSURE_( PaUnixThread_PrepareNotify( &stream->thread ) );
+    /* Buffer will be primed with silence */
+    PA_ENSURE_( PaAsiHpi_StartStream( stream, 0 ) );
+    PA_ENSURE_( PaUnixThread_NotifyParent( &stream->thread ) );
+
+    /* MAIN LOOP */
+    while( 1 )
+    {
+        PaStreamCallbackFlags cbFlags = 0;
+        unsigned long framesAvail, framesGot;
+
+        pthread_testcancel();
+
+        /** @concern StreamStop if the main thread has requested a stop and the stream has not
+        * been effectively stopped we signal this condition by modifying callbackResult
+        * (we'll want to flush buffered output). */
+        if( PaUnixThread_StopRequested( &stream->thread ) && (callbackResult == paContinue) )
+        {
+            PA_DEBUG(( "Setting callbackResult to paComplete\n" ));
+            callbackResult = paComplete;
+        }
+
+        /* Start winding down thread if requested */
+        if( callbackResult != paContinue )
+        {
+            stream->callbackAbort = (callbackResult == paAbort);
+            if( stream->callbackAbort ||
+                    /** @concern BlockAdaption: Go on if adaption buffers are empty */
+                    PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
+            {
+                goto end;
+            }
+            PA_DEBUG(( "%s: Flushing buffer processor\n", __FUNCTION__ ));
+            /* There is still buffered output that needs to be processed */
+        }
+
+        /* SLEEP */
+        /* Wait for data (or buffer space) to become available. This basically sleeps and
+        polls the HPI interface until a full block of frames can be moved. */
+        PA_ENSURE_( PaAsiHpi_WaitForFrames( stream, &framesAvail, &cbFlags ) );
+
+        /* Consume buffer space. Once we have a number of frames available for consumption we
+        must retrieve the data from the HPI interface and pass it to the PA buffer processor.
+        We should be prepared to process several chunks successively. */
+        while( framesAvail > 0 )
+        {
+            PaStreamCallbackTimeInfo timeInfo = {0, 0, 0};
+
+            pthread_testcancel();
+
+            framesGot = framesAvail;
+            if( stream->bufferProcessor.hostBufferSizeMode == paUtilFixedHostBufferSize )
+            {
+                /* We've committed to a fixed host buffer size, stick to that */
+                framesGot = framesGot >= stream->maxFramesPerHostBuffer ? stream->maxFramesPerHostBuffer : 0;
+            }
+            else
+            {
+                /* We've committed to an upper bound on the size of host buffers */
+                assert( stream->bufferProcessor.hostBufferSizeMode == paUtilBoundedHostBufferSize );
+                framesGot = PA_MIN( framesGot, stream->maxFramesPerHostBuffer );
+            }
+
+            /* Obtain buffer timestamps */
+            PaAsiHpi_CalculateTimeInfo( stream, &timeInfo );
+            PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags );
+            /* CPU load measurement should include processing activivity external to the stream callback */
+            PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
+            if( framesGot > 0 )
+            {
+                /* READ FROM HPI INPUT STREAM */
+                PA_ENSURE_( PaAsiHpi_BeginProcessing( stream, &framesGot, &cbFlags ) );
+                /* Input overflow in a full-duplex stream makes for interesting times */
+                if( stream->input && stream->output && (cbFlags & paInputOverflow) )
+                {
+                    /* Special full-duplex paNeverDropInput mode */
+                    if( stream->neverDropInput )
+                    {
+                        PaUtil_SetNoOutput( &stream->bufferProcessor );
+                        cbFlags |= paOutputOverflow;
+                    }
+                }
+                /* CALL USER CALLBACK WITH INPUT DATA, AND OBTAIN OUTPUT DATA */
+                PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult );
+                /* Clear overflow and underflow information (but PaAsiHpi_EndProcessing might
+                still show up output underflow that will carry over to next round) */
+                cbFlags = 0;
+                /*  WRITE TO HPI OUTPUT STREAM */
+                PA_ENSURE_( PaAsiHpi_EndProcessing( stream, framesGot, &cbFlags ) );
+                /* Advance frame counter */
+                framesAvail -= framesGot;
+            }
+            PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesGot );
+
+            if( framesGot == 0 )
+            {
+                /* Go back to polling for more frames */
+                break;
+
+            }
+            if( callbackResult != paContinue )
+                break;
+        }
+    }
+
+    /* This code is unreachable, but important to include regardless because it
+     * is possibly a macro with a closing brace to match the opening brace in
+     * pthread_cleanup_push() above.  The documentation states that they must
+     * always occur in pairs. */
+    pthread_cleanup_pop( 1 );
+
+end:
+    /* Indicates normal exit of callback, as opposed to the thread getting killed explicitly */
+    stream->callbackFinished = 1;
+    PA_DEBUG(( "%s: Thread %d exiting (callbackResult = %d)\n ",
+               __FUNCTION__, pthread_self(), callbackResult ));
+    /* Exit from thread and report any PortAudio error in the process */
+    PaUnixThreading_EXIT( result );
+error:
+    goto end;
+}
+
+/* --------------------------- Blocking Interface --------------------------- */
+
+/* As separate stream interfaces are used for blocking and callback streams, the following
+ functions can be guaranteed to only be called for blocking streams. */
+
+/** Read data from input stream.
+ This reads the indicated number of frames into the supplied buffer from an input stream,
+ and blocks until this is done.
+
+ @param s Pointer to PortAudio stream
+
+ @param buffer Pointer to buffer that will receive interleaved data (or an array of pointers
+               to a buffer for each non-interleaved channel)
+
+ @param frames Number of frames to read from stream
+
+ @return PortAudio error code (also indicates overflow via paInputOverflowed)
+ */
+static PaError ReadStream( PaStream *s,
+                           void *buffer,
+                           unsigned long frames )
+{
+    PaError result = paNoError;
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+    PaAsiHpiStreamInfo info;
+    void *userBuffer;
+
+    assert( stream );
+    PA_UNLESS_( stream->input, paCanNotReadFromAnOutputOnlyStream );
+
+    /* Check for input overflow since previous call to ReadStream */
+    PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+    if( info.overflow )
+    {
+        result = paInputOverflowed;
+    }
+
+    /* NB Make copy of user buffer pointers, since they are advanced by buffer processor */
+    if( stream->bufferProcessor.userInputIsInterleaved )
+    {
+        userBuffer = buffer;
+    }
+    else
+    {
+        /* Copy channels into local array */
+        userBuffer = stream->blockingUserBufferCopy;
+        memcpy( userBuffer, buffer, sizeof (void *) * stream->input->hpiFormat.wChannels );
+    }
+
+    while( frames > 0 )
+    {
+        unsigned long framesGot, framesAvail;
+        PaStreamCallbackFlags cbFlags = 0;
+
+        PA_ENSURE_( PaAsiHpi_WaitForFrames( stream, &framesAvail, &cbFlags ) );
+        framesGot = PA_MIN( framesAvail, frames );
+        PA_ENSURE_( PaAsiHpi_BeginProcessing( stream, &framesGot, &cbFlags ) );
+
+        if( framesGot > 0 )
+        {
+            framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot );
+            PA_ENSURE_( PaAsiHpi_EndProcessing( stream, framesGot, &cbFlags ) );
+            /* Advance frame counter */
+            frames -= framesGot;
+        }
+    }
+
+error:
+    return result;
+}
+
+
+/** Write data to output stream.
+ This writes the indicated number of frames from the supplied buffer to an output stream,
+ and blocks until this is done.
+
+ @param s Pointer to PortAudio stream
+
+ @param buffer Pointer to buffer that provides interleaved data (or an array of pointers
+               to a buffer for each non-interleaved channel)
+
+ @param frames Number of frames to write to stream
+
+ @return PortAudio error code (also indicates underflow via paOutputUnderflowed)
+ */
+static PaError WriteStream( PaStream *s,
+                            const void *buffer,
+                            unsigned long frames )
+{
+    PaError result = paNoError;
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+    PaAsiHpiStreamInfo info;
+    const void *userBuffer;
+
+    assert( stream );
+    PA_UNLESS_( stream->output, paCanNotWriteToAnInputOnlyStream );
+
+    /* Check for output underflow since previous call to WriteStream */
+    PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+    if( info.underflow )
+    {
+        result = paOutputUnderflowed;
+    }
+
+    /* NB Make copy of user buffer pointers, since they are advanced by buffer processor */
+    if( stream->bufferProcessor.userOutputIsInterleaved )
+    {
+        userBuffer = buffer;
+    }
+    else
+    {
+        /* Copy channels into local array */
+        userBuffer = stream->blockingUserBufferCopy;
+        memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->output->hpiFormat.wChannels );
+    }
+
+    while( frames > 0 )
+    {
+        unsigned long framesGot, framesAvail;
+        PaStreamCallbackFlags cbFlags = 0;
+
+        PA_ENSURE_( PaAsiHpi_WaitForFrames( stream, &framesAvail, &cbFlags ) );
+        framesGot = PA_MIN( framesAvail, frames );
+        PA_ENSURE_( PaAsiHpi_BeginProcessing( stream, &framesGot, &cbFlags ) );
+
+        if( framesGot > 0 )
+        {
+            framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot );
+            PA_ENSURE_( PaAsiHpi_EndProcessing( stream, framesGot, &cbFlags ) );
+            /* Advance frame counter */
+            frames -= framesGot;
+        }
+    }
+
+error:
+    return result;
+}
+
+
+/** Number of frames that can be read from input stream without blocking.
+
+ @param s Pointer to PortAudio stream
+
+ @return Number of frames, or PortAudio error code
+ */
+static signed long GetStreamReadAvailable( PaStream *s )
+{
+    PaError result = paNoError;
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+    PaAsiHpiStreamInfo info;
+
+    assert( stream );
+    PA_UNLESS_( stream->input, paCanNotReadFromAnOutputOnlyStream );
+
+    PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+    /* Round down to the nearest host buffer multiple */
+    result = (info.availableFrames / stream->maxFramesPerHostBuffer) * stream->maxFramesPerHostBuffer;
+    if( info.overflow )
+    {
+        result = paInputOverflowed;
+    }
+
+error:
+    return result;
+}
+
+
+/** Number of frames that can be written to output stream without blocking.
+
+ @param s Pointer to PortAudio stream
+
+ @return Number of frames, or PortAudio error code
+ */
+static signed long GetStreamWriteAvailable( PaStream *s )
+{
+    PaError result = paNoError;
+    PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+    PaAsiHpiStreamInfo info;
+
+    assert( stream );
+    PA_UNLESS_( stream->output, paCanNotWriteToAnInputOnlyStream );
+
+    PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+    /* Round down to the nearest host buffer multiple */
+    result = (info.availableFrames / stream->maxFramesPerHostBuffer) * stream->maxFramesPerHostBuffer;
+    if( info.underflow )
+    {
+        result = paOutputUnderflowed;
+    }
+
+error:
+    return result;
+}
diff --git a/external/portaudio/pa_mac_core.c b/external/portaudio/pa_mac_core.c
index cc273a6..14632d9 100644
--- a/external/portaudio/pa_mac_core.c
+++ b/external/portaudio/pa_mac_core.c
@@ -66,7 +66,6 @@
 
 #include <string.h> /* strlen(), memcmp() etc. */
 #include <libkern/OSAtomic.h>
-#include <CoreServices/CoreServices.h>
 
 #include "pa_mac_core.h"
 #include "pa_mac_core_utilities.h"
@@ -77,9 +76,11 @@
 extern "C"
 {
 #endif /* __cplusplus */
-
+	
+/* This is a reasonable size for a small buffer based on experience. */
+#define PA_MAC_SMALL_BUFFER_SIZE    (64)
+	
 /* prototypes for functions declared in this file */
-
 PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
 
 /*
@@ -129,6 +130,8 @@ const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input )
    OSStatus error;
    err = PaUtil_GetHostApiRepresentation( &hostApi, paCoreAudio );
    assert(err == paNoError);
+   if( err != paNoError )
+      return NULL;
    PaMacAUHAL *macCoreHostApi = (PaMacAUHAL*)hostApi;
    AudioDeviceID hostApiDevice = macCoreHostApi->devIds[device];
 
@@ -201,8 +204,40 @@ const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input )
    return channelName;
 }
 
-
-
+    
+PaError PaMacCore_GetBufferSizeRange( PaDeviceIndex device,
+                                      long *minBufferSizeFrames, long *maxBufferSizeFrames )
+{
+    PaError result;
+    PaUtilHostApiRepresentation *hostApi;
+    
+    result = PaUtil_GetHostApiRepresentation( &hostApi, paCoreAudio );
+    
+    if( result == paNoError )
+    {
+        PaDeviceIndex hostApiDeviceIndex;
+        result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDeviceIndex, device, hostApi );
+        if( result == paNoError )
+        {
+            PaMacAUHAL *macCoreHostApi = (PaMacAUHAL*)hostApi;
+            AudioDeviceID macCoreDeviceId = macCoreHostApi->devIds[hostApiDeviceIndex];
+            AudioValueRange audioRange;
+            UInt32 propSize = sizeof( audioRange );
+            
+            // return the size range for the output scope unless we only have inputs
+            Boolean isInput = 0;
+            if( macCoreHostApi->inheritedHostApiRep.deviceInfos[hostApiDeviceIndex]->maxOutputChannels == 0 )
+                isInput = 1;
+           
+            result = WARNING(AudioDeviceGetProperty( macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSizeRange, &propSize, &audioRange ) );
+
+            *minBufferSizeFrames = audioRange.mMinimum;
+            *maxBufferSizeFrames = audioRange.mMaximum;
+        }
+    }
+    
+    return result;
+}
 
 
 AudioDeviceID PaMacCore_GetStreamInputDevice( PaStream* s )
@@ -248,7 +283,6 @@ static PaError AbortStream( PaStream *stream );
 static PaError IsStreamStopped( PaStream *s );
 static PaError IsStreamActive( PaStream *stream );
 static PaTime GetStreamTime( PaStream *stream );
-static void setStreamStartTime( PaStream *stream );
 static OSStatus AudioIOProc( void *inRefCon,
                                AudioUnitRenderActionFlags *ioActionFlags,
                                const AudioTimeStamp *inTimeStamp,
@@ -281,28 +315,6 @@ static PaError OpenAndSetupOneAudioUnit(
     PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText )
 
 /*
- * Callback for setting over/underrun flags.
- *
- */
-static OSStatus xrunCallback(
-    AudioDeviceID inDevice, 
-    UInt32 inChannel, 
-    Boolean isInput, 
-    AudioDevicePropertyID inPropertyID, 
-    void* inClientData)
-{
-   PaMacCoreStream *stream = (PaMacCoreStream *) inClientData;
-   if( stream->state != ACTIVE )
-      return 0; //if the stream isn't active, we don't care if the device is dropping
-   if( isInput )
-      OSAtomicOr32( paInputUnderflow, (uint32_t *)&(stream->xrunFlags) );
-   else
-      OSAtomicOr32( paOutputOverflow, (uint32_t *)&(stream->xrunFlags) );
-
-   return 0;
-}
-
-/*
  * Callback called when starting or stopping a stream.
  */
 static void startStopCallback(
@@ -315,7 +327,11 @@ static void startStopCallback(
    PaMacCoreStream *stream = (PaMacCoreStream *) inRefCon;
    UInt32 isRunning;
    UInt32 size = sizeof( isRunning );
-   assert( !AudioUnitGetProperty( ci, kAudioOutputUnitProperty_IsRunning, inScope, inElement, &isRunning, &size ) );
+   OSStatus err;
+   err = AudioUnitGetProperty( ci, kAudioOutputUnitProperty_IsRunning, inScope, inElement, &isRunning, &size );
+   assert( !err );
+   if( err )
+      isRunning = false; //it's very unclear what to do in case of error here. There's no real way to notify the user, and crashing seems unreasonable.
    if( isRunning )
       return; //We are only interested in when we are stopping
    // -- if we are using 2 I/O units, we only need one notification!
@@ -415,6 +431,153 @@ static PaError gatherDeviceInfo(PaMacAUHAL *auhalHostApi)
     return paNoError;
 }
 
+/* =================================================================================================== */
+/**
+ * @internal
+ * @brief Clip the desired size against the allowed IO buffer size range for the device.
+ */
+static PaError ClipToDeviceBufferSize( AudioDeviceID macCoreDeviceId,
+									int isInput, UInt32 desiredSize, UInt32 *allowedSize )
+{
+	UInt32 resultSize = desiredSize;
+	AudioValueRange audioRange;
+	UInt32 propSize = sizeof( audioRange );
+	PaError err = WARNING(AudioDeviceGetProperty( macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSizeRange, &propSize, &audioRange ) );
+	resultSize = MAX( resultSize, audioRange.mMinimum );
+	resultSize = MIN( resultSize, audioRange.mMaximum );
+	*allowedSize = resultSize;
+	return err;
+}
+
+/* =================================================================================================== */
+#if 0
+static void DumpDeviceProperties( AudioDeviceID macCoreDeviceId,
+                          int isInput )
+{
+    PaError err;
+    int i;
+    UInt32 propSize;
+    UInt32 deviceLatency;
+    UInt32 streamLatency;
+    UInt32 bufferFrames;
+    UInt32 safetyOffset;
+    AudioStreamID streamIDs[128];
+    
+    printf("\n======= latency query : macCoreDeviceId = %d, isInput %d =======\n", (int)macCoreDeviceId, isInput );    
+    
+    propSize = sizeof(UInt32);
+    err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &bufferFrames));
+    printf("kAudioDevicePropertyBufferFrameSize: err = %d, propSize = %d, value = %d\n", err, propSize, bufferFrames );
+    
+    propSize = sizeof(UInt32);
+    err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertySafetyOffset, &propSize, &safetyOffset));
+    printf("kAudioDevicePropertySafetyOffset: err = %d, propSize = %d, value = %d\n", err, propSize, safetyOffset );
+    
+    propSize = sizeof(UInt32);
+    err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &deviceLatency));
+    printf("kAudioDevicePropertyLatency: err = %d, propSize = %d, value = %d\n", err, propSize, deviceLatency );
+    
+    AudioValueRange audioRange;
+    propSize = sizeof( audioRange );
+    err = WARNING(AudioDeviceGetProperty( macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSizeRange, &propSize, &audioRange ) );
+    printf("kAudioDevicePropertyBufferFrameSizeRange: err = %d, propSize = %u, minimum = %g\n", err, propSize, audioRange.mMinimum);
+    printf("kAudioDevicePropertyBufferFrameSizeRange: err = %d, propSize = %u, maximum = %g\n", err, propSize, audioRange.mMaximum );
+    
+    /* Get the streams from the device and query their latency. */
+    propSize = sizeof(streamIDs);
+    err  = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreams, &propSize, &streamIDs[0]));
+    int numStreams = propSize / sizeof(AudioStreamID);
+    for( i=0; i<numStreams; i++ )
+    {
+        printf("Stream #%d = %d---------------------- \n", i, streamIDs[i] );
+        
+        propSize = sizeof(UInt32);
+        err  = WARNING(AudioStreamGetProperty(streamIDs[i], 0, kAudioStreamPropertyLatency, &propSize, &streamLatency));
+        printf("  kAudioStreamPropertyLatency: err = %d, propSize = %d, value = %d\n", err, propSize, streamLatency );
+    }
+}
+#endif
+
+/* =================================================================================================== */
+/**
+ * @internal
+ * Calculate the fixed latency from the system and the device.
+ * Sum of kAudioStreamPropertyLatency +
+ *        kAudioDevicePropertySafetyOffset +
+ *        kAudioDevicePropertyLatency
+ *
+ * Some useful info from Jeff Moore on latency.
+ * http://osdir.com/ml/coreaudio-api/2010-01/msg00046.html
+ * http://osdir.com/ml/coreaudio-api/2009-07/msg00140.html
+ */
+static PaError CalculateFixedDeviceLatency( AudioDeviceID macCoreDeviceId, int isInput, UInt32 *fixedLatencyPtr )
+{
+    PaError err;
+    UInt32 propSize;
+    UInt32 deviceLatency;
+    UInt32 streamLatency;
+    UInt32 safetyOffset;
+    AudioStreamID streamIDs[1];
+    
+    // To get stream latency we have to get a streamID from the device.
+    // We are only going to look at the first stream so only fetch one stream.
+    propSize = sizeof(streamIDs);
+    err  = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreams, &propSize, &streamIDs[0]));
+    if( err != paNoError ) goto error;
+    if( propSize == sizeof(AudioStreamID) )
+    {        
+        propSize = sizeof(UInt32);
+        err  = WARNING(AudioStreamGetProperty(streamIDs[0], 0, kAudioStreamPropertyLatency, &propSize, &streamLatency));
+    }
+    
+    propSize = sizeof(UInt32);
+    err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertySafetyOffset, &propSize, &safetyOffset));
+    if( err != paNoError ) goto error;
+    
+    propSize = sizeof(UInt32);
+    err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &deviceLatency));
+    if( err != paNoError ) goto error;
+
+    *fixedLatencyPtr = deviceLatency + streamLatency + safetyOffset;
+    return err;
+error:
+    return err;
+}
+
+/* =================================================================================================== */
+static PaError CalculateDefaultDeviceLatencies( AudioDeviceID macCoreDeviceId,
+                                               int isInput, UInt32 *lowLatencyFramesPtr,
+                                               UInt32 *highLatencyFramesPtr )
+{
+    UInt32 propSize;
+    UInt32 bufferFrames = 0;
+    UInt32 fixedLatency = 0;
+    UInt32 clippedMinBufferSize = 0;
+    
+    //DumpDeviceProperties( macCoreDeviceId, isInput );
+    
+    PaError err = CalculateFixedDeviceLatency( macCoreDeviceId, isInput, &fixedLatency );
+    if( err != paNoError ) goto error;
+    
+    // For low latency use a small fixed size buffer clipped to the device range.
+    err = ClipToDeviceBufferSize( macCoreDeviceId, isInput, PA_MAC_SMALL_BUFFER_SIZE, &clippedMinBufferSize );
+    if( err != paNoError ) goto error;
+    
+    // For high latency use the default device buffer size.
+    propSize = sizeof(UInt32);
+    err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &bufferFrames));
+    if( err != paNoError ) goto error;
+    
+    *lowLatencyFramesPtr = fixedLatency + clippedMinBufferSize;
+    *highLatencyFramesPtr = fixedLatency + bufferFrames;
+    
+    return err;
+error:
+    return err;
+}
+
+/* =================================================================================================== */
+
 static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi,
                                PaDeviceInfo *deviceInfo,
                                AudioDeviceID macCoreDeviceId,
@@ -425,8 +588,7 @@ static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi,
     UInt32 i;
     int numChannels = 0;
     AudioBufferList *buflist = NULL;
-    UInt32 frameLatency;
-
+    
     VVDBUG(("GetChannelInfo()\n"));
 
     /* Get the number of channels from the stream configuration.
@@ -451,39 +613,33 @@ static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi,
     else
         deviceInfo->maxOutputChannels = numChannels;
       
-    if (numChannels > 0) /* do not try to retrieve the latency if there is no channels. */
+    if (numChannels > 0) /* do not try to retrieve the latency if there are no channels. */
     {
-       /* Get the latency.  Don't fail if we can't get this. */
-       /* default to something reasonable */
-       deviceInfo->defaultLowInputLatency = .01;
-       deviceInfo->defaultHighInputLatency = .10;
-       deviceInfo->defaultLowOutputLatency = .01;
-       deviceInfo->defaultHighOutputLatency = .10;
-       propSize = sizeof(UInt32);
-       err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency));
-       if (!err)
-       {
-          /** FEEDBACK:
-           * This code was arrived at by trial and error, and some extentive, but not exhaustive
-           * testing. Sebastien Beaulieu <seb at plogue.com> has suggested using
-           * kAudioDevicePropertyLatency + kAudioDevicePropertySafetyOffset + buffer size instead.
-           * At the time this code was written, many users were reporting dropouts with audio
-           * programs that probably used this formula. This was probably
-           * around 10.4.4, and the problem is probably fixed now. So perhaps
-           * his formula should be reviewed and used.
-           * */
-          double secondLatency = frameLatency / deviceInfo->defaultSampleRate;
-          if (isInput)
-          {
-             deviceInfo->defaultLowInputLatency = 3 * secondLatency;
-             deviceInfo->defaultHighInputLatency = 3 * 10 * secondLatency;
-          }
-          else
-          {
-             deviceInfo->defaultLowOutputLatency = 3 * secondLatency;
-             deviceInfo->defaultHighOutputLatency = 3 * 10 * secondLatency;
-          }
-       }
+        /* Get the latency.  Don't fail if we can't get this. */
+        /* default to something reasonable */
+        deviceInfo->defaultLowInputLatency = .01;
+        deviceInfo->defaultHighInputLatency = .10;
+        deviceInfo->defaultLowOutputLatency = .01;
+        deviceInfo->defaultHighOutputLatency = .10;        
+        UInt32 lowLatencyFrames = 0;
+        UInt32 highLatencyFrames = 0;
+        err = CalculateDefaultDeviceLatencies( macCoreDeviceId, isInput, &lowLatencyFrames, &highLatencyFrames );
+        if( err == 0 )
+        {
+            
+            double lowLatencySeconds = lowLatencyFrames / deviceInfo->defaultSampleRate;
+            double highLatencySeconds = highLatencyFrames / deviceInfo->defaultSampleRate;
+            if (isInput)
+            {
+                deviceInfo->defaultLowInputLatency = lowLatencySeconds;
+                deviceInfo->defaultHighInputLatency = highLatencySeconds;
+            }
+            else
+            {
+                deviceInfo->defaultLowOutputLatency = lowLatencySeconds;
+                deviceInfo->defaultHighOutputLatency = highLatencySeconds;
+            }
+        }
     }
     PaUtil_FreeMemory( buflist );
     return paNoError;
@@ -492,6 +648,7 @@ static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi,
     return err;
 }
 
+/* =================================================================================================== */
 static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi,
                                      PaDeviceInfo *deviceInfo,
                                      AudioDeviceID macCoreDeviceId,
@@ -504,7 +661,7 @@ static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi,
 
     VVDBUG(("InitializeDeviceInfo(): macCoreDeviceId=%ld\n", macCoreDeviceId));
 
-    memset(deviceInfo, 0, sizeof(deviceInfo));
+    memset(deviceInfo, 0, sizeof(PaDeviceInfo));
 
     deviceInfo->structVersion = 2;
     deviceInfo->hostApi = hostApiIndex;
@@ -547,10 +704,31 @@ PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIn
 {
     PaError result = paNoError;
     int i;
-    PaMacAUHAL *auhalHostApi;
+    PaMacAUHAL *auhalHostApi = NULL;
     PaDeviceInfo *deviceInfoArray;
+    int unixErr;
 
     VVDBUG(("PaMacCore_Initialize(): hostApiIndex=%d\n", hostApiIndex));
+	
+	SInt32 major;
+	SInt32 minor;
+	Gestalt(gestaltSystemVersionMajor, &major);
+	Gestalt(gestaltSystemVersionMinor, &minor);
+	
+	// Starting with 10.6 systems, the HAL notification thread is created internally
+	if (major == 10 && minor >= 6) {
+		CFRunLoopRef theRunLoop = NULL;
+		AudioObjectPropertyAddress theAddress = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
+		OSStatus osErr = AudioObjectSetPropertyData (kAudioObjectSystemObject, &theAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
+		if (osErr != noErr) {
+			goto error;
+		}
+	}
+	
+    unixErr = initializeXRunListenerList();
+    if( 0 != unixErr ) {
+       return UNIX_ERR(unixErr);
+    }
 
     auhalHostApi = (PaMacAUHAL*)PaUtil_AllocateMemory( sizeof(PaMacAUHAL) );
     if( !auhalHostApi )
@@ -670,10 +848,16 @@ error:
 
 static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
 {
+    int unixErr;
+
     PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi;
 
     VVDBUG(("Terminate()\n"));
 
+    unixErr = destroyXRunListenerList();
+    if( 0 != unixErr )
+       UNIX_ERR(unixErr);
+
     /*
         IMPLEMENT ME:
             - clean up any resources not handled by the allocation group
@@ -788,6 +972,178 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
     return paFormatIsSupported;
 }
 
+/* ================================================================================= */
+static void InitializeDeviceProperties( PaMacCoreDeviceProperties *deviceProperties )
+{
+    memset( deviceProperties, 0, sizeof(PaMacCoreDeviceProperties) );
+    deviceProperties->sampleRate = 1.0; // Better than random. Overwritten by actual values later on.
+    deviceProperties->samplePeriod = 1.0 / deviceProperties->sampleRate;
+}
+
+static Float64 CalculateSoftwareLatencyFromProperties( PaMacCoreStream *stream, PaMacCoreDeviceProperties *deviceProperties )
+{
+    UInt32 latencyFrames = deviceProperties->bufferFrameSize + deviceProperties->deviceLatency + deviceProperties->safetyOffset;
+    return latencyFrames * deviceProperties->samplePeriod; // same as dividing by sampleRate but faster
+}
+
+static Float64 CalculateHardwareLatencyFromProperties( PaMacCoreStream *stream, PaMacCoreDeviceProperties *deviceProperties )
+{
+    return deviceProperties->deviceLatency * deviceProperties->samplePeriod; // same as dividing by sampleRate but faster
+}
+
+/* Calculate values used to convert Apple timestamps into PA timestamps
+ * from the device properties. The final results of this calculation
+ * will be used in the audio callback function.
+ */
+static void UpdateTimeStampOffsets( PaMacCoreStream *stream )
+{
+    Float64 inputSoftwareLatency = 0.0;
+    Float64 inputHardwareLatency = 0.0;
+    Float64 outputSoftwareLatency = 0.0;
+    Float64 outputHardwareLatency = 0.0;
+    
+    if( stream->inputUnit != NULL )
+    {
+        inputSoftwareLatency = CalculateSoftwareLatencyFromProperties( stream, &stream->inputProperties );
+        inputHardwareLatency = CalculateHardwareLatencyFromProperties( stream, &stream->inputProperties );
+    }    
+    if( stream->outputUnit != NULL )
+    {
+        outputSoftwareLatency = CalculateSoftwareLatencyFromProperties( stream, &stream->outputProperties );
+        outputHardwareLatency = CalculateHardwareLatencyFromProperties( stream, &stream->outputProperties );
+    }    
+    
+    /* We only need a mutex around setting these variables as a group. */
+	pthread_mutex_lock( &stream->timingInformationMutex );
+    stream->timestampOffsetCombined = inputSoftwareLatency + outputSoftwareLatency;
+    stream->timestampOffsetInputDevice = inputHardwareLatency;
+    stream->timestampOffsetOutputDevice = outputHardwareLatency;
+	pthread_mutex_unlock( &stream->timingInformationMutex );
+}
+
+/* ================================================================================= */
+
+/* can be used to update from nominal or actual sample rate */
+static OSStatus UpdateSampleRateFromDeviceProperty( PaMacCoreStream *stream, AudioDeviceID deviceID, Boolean isInput, AudioDevicePropertyID sampleRatePropertyID )
+{
+    PaMacCoreDeviceProperties * deviceProperties = isInput ? &stream->inputProperties : &stream->outputProperties;
+	
+	Float64 sampleRate = 0.0;
+	UInt32 propSize = sizeof(Float64);
+    OSStatus osErr = AudioDeviceGetProperty( deviceID, 0, isInput, sampleRatePropertyID, &propSize, &sampleRate);
+	if( (osErr == noErr) && (sampleRate > 1000.0) ) /* avoid divide by zero if there's an error */
+	{
+        deviceProperties->sampleRate = sampleRate;
+        deviceProperties->samplePeriod = 1.0 / sampleRate;
+    }
+    return osErr;
+}
+
+static OSStatus AudioDevicePropertyActualSampleRateListenerProc( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void *inClientData )
+{
+	PaMacCoreStream *stream = (PaMacCoreStream*)inClientData;
+    
+    // Make sure the callback is operating on a stream that is still valid!
+    assert( stream->streamRepresentation.magic == PA_STREAM_MAGIC );
+
+	OSStatus osErr = UpdateSampleRateFromDeviceProperty( stream, inDevice, isInput, kAudioDevicePropertyActualSampleRate );
+    if( osErr == noErr )
+    {
+        UpdateTimeStampOffsets( stream );
+    }
+    return osErr;
+}
+
+/* ================================================================================= */
+static OSStatus QueryUInt32DeviceProperty( AudioDeviceID deviceID, Boolean isInput, AudioDevicePropertyID propertyID, UInt32 *outValue )
+{
+	UInt32 propertyValue = 0;
+	UInt32 propertySize = sizeof(UInt32);
+	OSStatus osErr = AudioDeviceGetProperty( deviceID, 0, isInput, propertyID, &propertySize, &propertyValue);
+	if( osErr == noErr )
+	{
+        *outValue = propertyValue;
+	}
+    return osErr;
+}
+
+static OSStatus AudioDevicePropertyGenericListenerProc( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void *inClientData )
+{
+    OSStatus osErr = noErr;
+	PaMacCoreStream *stream = (PaMacCoreStream*)inClientData;
+    
+    // Make sure the callback is operating on a stream that is still valid!
+    assert( stream->streamRepresentation.magic == PA_STREAM_MAGIC );
+    
+    PaMacCoreDeviceProperties *deviceProperties = isInput ? &stream->inputProperties : &stream->outputProperties;
+    UInt32 *valuePtr = NULL;
+    switch( inPropertyID )
+    {
+        case kAudioDevicePropertySafetyOffset:
+            valuePtr = &deviceProperties->safetyOffset;
+            break;
+                        
+        case kAudioDevicePropertyLatency:
+            valuePtr = &deviceProperties->deviceLatency;
+            break;
+            
+        case kAudioDevicePropertyBufferFrameSize:
+            valuePtr = &deviceProperties->bufferFrameSize;
+            break;            
+    }
+    if( valuePtr != NULL )
+    {
+        osErr = QueryUInt32DeviceProperty( inDevice, isInput, inPropertyID, valuePtr );
+        if( osErr == noErr )
+        {
+            UpdateTimeStampOffsets( stream );
+        }
+    }
+    return osErr;
+}
+
+/* ================================================================================= */
+/*
+ * Setup listeners in case device properties change during the run. */
+static OSStatus SetupDevicePropertyListeners( PaMacCoreStream *stream, AudioDeviceID deviceID, Boolean isInput )
+{
+    OSStatus osErr = noErr;
+    PaMacCoreDeviceProperties *deviceProperties = isInput ? &stream->inputProperties : &stream->outputProperties;
+    
+    if( (osErr = QueryUInt32DeviceProperty( deviceID, isInput,
+                                           kAudioDevicePropertyLatency, &deviceProperties->deviceLatency )) != noErr ) return osErr;
+    if( (osErr = QueryUInt32DeviceProperty( deviceID, isInput,
+                                           kAudioDevicePropertyBufferFrameSize, &deviceProperties->bufferFrameSize )) != noErr ) return osErr;
+    if( (osErr = QueryUInt32DeviceProperty( deviceID, isInput,
+                                           kAudioDevicePropertySafetyOffset, &deviceProperties->safetyOffset )) != noErr ) return osErr;
+    
+    AudioDeviceAddPropertyListener( deviceID, 0, isInput, kAudioDevicePropertyActualSampleRate, 
+                                   AudioDevicePropertyActualSampleRateListenerProc, stream );
+    
+    AudioDeviceAddPropertyListener( deviceID, 0, isInput, kAudioStreamPropertyLatency, 
+                                   AudioDevicePropertyGenericListenerProc, stream );
+    AudioDeviceAddPropertyListener( deviceID, 0, isInput, kAudioDevicePropertyBufferFrameSize, 
+                                   AudioDevicePropertyGenericListenerProc, stream );
+    AudioDeviceAddPropertyListener( deviceID, 0, isInput, kAudioDevicePropertySafetyOffset, 
+                                   AudioDevicePropertyGenericListenerProc, stream );
+    
+    return osErr;
+}
+
+static void CleanupDevicePropertyListeners( PaMacCoreStream *stream, AudioDeviceID deviceID, Boolean isInput )
+{    
+    AudioDeviceRemovePropertyListener( deviceID, 0, isInput, kAudioDevicePropertyActualSampleRate, 
+                                   AudioDevicePropertyActualSampleRateListenerProc );
+    
+    AudioDeviceRemovePropertyListener( deviceID, 0, isInput, kAudioDevicePropertyLatency, 
+                                   AudioDevicePropertyGenericListenerProc );	
+    AudioDeviceRemovePropertyListener( deviceID, 0, isInput, kAudioDevicePropertyBufferFrameSize, 
+                                   AudioDevicePropertyGenericListenerProc );
+    AudioDeviceRemovePropertyListener( deviceID, 0, isInput, kAudioDevicePropertySafetyOffset, 
+                                   AudioDevicePropertyGenericListenerProc );
+}
+
+/* ================================================================================= */
 static PaError OpenAndSetupOneAudioUnit(
                                    const PaMacCoreStream *stream,
                                    const PaStreamParameters *inStreamParams,
@@ -945,13 +1301,18 @@ static PaError OpenAndSetupOneAudioUnit(
                     sizeof(AudioDeviceID) ) );
     }
     /* -- add listener for dropouts -- */
-    ERR_WRAP( AudioDeviceAddPropertyListener( *audioDevice,
-                                              0,
-                                              outStreamParams ? false : true,
-                                              kAudioDeviceProcessorOverload,
-                                              xrunCallback,
-                                              (void *)stream) );
-
+    result = AudioDeviceAddPropertyListener( *audioDevice,
+                                             0,
+                                             outStreamParams ? false : true,
+                                             kAudioDeviceProcessorOverload,
+                                             xrunCallback,
+                                             addToXRunListenerList( (void *)stream ) ) ;
+    if( result == kAudioHardwareIllegalOperationError ) {
+       // -- already registered, we're good
+    } else {
+       // -- not already registered, just check for errors
+       ERR_WRAP( result );
+    }
     /* -- listen for stream start and stop -- */
     ERR_WRAP( AudioUnitAddPropertyListener( *audioUnit,
                                             kAudioOutputUnitProperty_IsRunning,
@@ -1216,11 +1577,17 @@ static PaError OpenAndSetupOneAudioUnit(
     ERR_WRAP( AudioUnitInitialize(*audioUnit) );
 
     if( inStreamParams && outStreamParams )
-       VDBUG( ("Opened device %ld for input and output.\n", *audioDevice ) );
+    {
+        VDBUG( ("Opened device %ld for input and output.\n", *audioDevice ) );
+    }
     else if( inStreamParams )
-       VDBUG( ("Opened device %ld for input.\n", *audioDevice ) );
+    {
+        VDBUG( ("Opened device %ld for input.\n", *audioDevice ) );
+    }
     else if( outStreamParams )
-       VDBUG( ("Opened device %ld for output.\n", *audioDevice ) );
+    {
+        VDBUG( ("Opened device %ld for output.\n", *audioDevice ) );
+    }
     return paNoError;
 #undef ERR_WRAP
 
@@ -1232,13 +1599,78 @@ static PaError OpenAndSetupOneAudioUnit(
        return paResult;
 }
 
+/* =================================================================================================== */
+
+static UInt32 CalculateOptimalBufferSize( PaMacAUHAL *auhalHostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  UInt32 fixedInputLatency,
+                                  UInt32 fixedOutputLatency,
+                                  double sampleRate,
+                                  UInt32 requestedFramesPerBuffer )
+{
+    UInt32 resultBufferSizeFrames = 0;  
+    // Use maximum of suggested input and output latencies.
+    if( inputParameters )
+    {
+        UInt32 suggestedLatencyFrames = inputParameters->suggestedLatency * sampleRate;
+        // Calculate a buffer size assuming we are double buffered.
+        SInt32 variableLatencyFrames = suggestedLatencyFrames - fixedInputLatency;
+        // Prevent negative latency.
+        variableLatencyFrames = MAX( variableLatencyFrames, 0 );       
+        resultBufferSizeFrames = MAX( resultBufferSizeFrames, (UInt32) variableLatencyFrames );
+    }
+    if( outputParameters )
+    {        
+        UInt32 suggestedLatencyFrames = outputParameters->suggestedLatency * sampleRate;
+        SInt32 variableLatencyFrames = suggestedLatencyFrames - fixedOutputLatency;
+        variableLatencyFrames = MAX( variableLatencyFrames, 0 );
+        resultBufferSizeFrames = MAX( resultBufferSizeFrames, (UInt32) variableLatencyFrames );
+    }
+    
+    // can't have zero frames. code to round up to next user buffer requires non-zero
+    resultBufferSizeFrames = MAX( resultBufferSizeFrames, 1 );
+    
+    if( requestedFramesPerBuffer != paFramesPerBufferUnspecified )
+    {
+        // make host buffer the next highest integer multiple of user frames per buffer
+        UInt32 n = (resultBufferSizeFrames + requestedFramesPerBuffer - 1) / requestedFramesPerBuffer;
+        resultBufferSizeFrames = n * requestedFramesPerBuffer;
+
+        
+        // FIXME: really we should be searching for a multiple of requestedFramesPerBuffer
+        // that is >= suggested latency and also fits within device buffer min/max
+        
+    }else{
+    	VDBUG( ("Block Size unspecified. Based on Latency, the user wants a Block Size near: %ld.\n",
+            resultBufferSizeFrames ) );
+    }
+    
+    // Clip to the capabilities of the device.
+    if( inputParameters )
+    {
+        ClipToDeviceBufferSize( auhalHostApi->devIds[inputParameters->device],
+                               true, // In the old code isInput was false!
+                               resultBufferSizeFrames, &resultBufferSizeFrames );
+    }
+    if( outputParameters )
+    {
+        ClipToDeviceBufferSize( auhalHostApi->devIds[outputParameters->device],
+                               false, resultBufferSizeFrames, &resultBufferSizeFrames );
+    }
+    VDBUG(("After querying hardware, setting block size to %ld.\n", resultBufferSizeFrames));
+
+    return resultBufferSizeFrames;
+}
+
+/* =================================================================================================== */
 /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
 static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                            PaStream** s,
                            const PaStreamParameters *inputParameters,
                            const PaStreamParameters *outputParameters,
                            double sampleRate,
-                           unsigned long framesPerBuffer,
+                           unsigned long requestedFramesPerBuffer,
                            PaStreamFlags streamFlags,
                            PaStreamCallback *streamCallback,
                            void *userData )
@@ -1249,21 +1681,34 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     int inputChannelCount, outputChannelCount;
     PaSampleFormat inputSampleFormat, outputSampleFormat;
     PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+    UInt32 fixedInputLatency = 0;
+    UInt32 fixedOutputLatency = 0;
+    // Accumulate contributions to latency in these variables.
+    UInt32 inputLatencyFrames = 0;
+    UInt32 outputLatencyFrames = 0;
+    UInt32 suggestedLatencyFramesPerBuffer = requestedFramesPerBuffer;
+    
     VVDBUG(("OpenStream(): in chan=%d, in fmt=%ld, out chan=%d, out fmt=%ld SR=%g, FPB=%ld\n",
                 inputParameters  ? inputParameters->channelCount  : -1,
                 inputParameters  ? inputParameters->sampleFormat  : -1,
                 outputParameters ? outputParameters->channelCount : -1,
                 outputParameters ? outputParameters->sampleFormat : -1,
                 (float) sampleRate,
-                framesPerBuffer ));
+                requestedFramesPerBuffer ));
     VDBUG( ("Opening Stream.\n") );
-
-    /*These first few bits of code are from paSkeleton with few modifications.*/
+	
+    /* These first few bits of code are from paSkeleton with few modifications. */
     if( inputParameters )
     {
         inputChannelCount = inputParameters->channelCount;
         inputSampleFormat = inputParameters->sampleFormat;
 
+		/* @todo Blocking read/write on Mac is not yet supported. */
+		if( !streamCallback && inputSampleFormat & paNonInterleaved )
+		{
+			return paSampleFormatNotSupported;
+		}
+		
         /* unless alternate device specification is supported, reject the use of
             paUseHostApiSpecificDeviceSpecification */
 
@@ -1288,6 +1733,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         outputChannelCount = outputParameters->channelCount;
         outputSampleFormat = outputParameters->sampleFormat;
         
+		/* @todo Blocking read/write on Mac is not yet supported. */
+		if( !streamCallback && outputSampleFormat & paNonInterleaved )
+		{
+			return paSampleFormatNotSupported;
+		}
+		
         /* unless alternate device specification is supported, reject the use of
             paUseHostApiSpecificDeviceSpecification */
 
@@ -1323,24 +1774,16 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
        do is initialize everything so that if we fail, we know what hasn't
        been touched.
      */
-
-    stream->inputAudioBufferList.mBuffers[0].mData = NULL;
-    stream->inputRingBuffer.buffer = NULL;
-    bzero( &stream->blio, sizeof( PaMacBlio ) );
-/*
+    bzero( stream, sizeof( PaMacCoreStream ) );
+    
+    /*
     stream->blio.inputRingBuffer.buffer = NULL;
     stream->blio.outputRingBuffer.buffer = NULL;
     stream->blio.inputSampleFormat = inputParameters?inputParameters->sampleFormat:0;
     stream->blio.inputSampleSize = computeSampleSizeFromFormat(stream->blio.inputSampleFormat);
     stream->blio.outputSampleFormat=outputParameters?outputParameters->sampleFormat:0;
     stream->blio.outputSampleSize = computeSampleSizeFromFormat(stream->blio.outputSampleFormat);
-*/
-    stream->inputSRConverter = NULL;
-    stream->inputUnit = NULL;
-    stream->outputUnit = NULL;
-    stream->inputFramesPerBuffer = 0;
-    stream->outputFramesPerBuffer = 0;
-    stream->bufferProcessorIsInitialized = FALSE;
+    */
 
     /* assert( streamCallback ) ; */ /* only callback mode is implemented */
     if( streamCallback )
@@ -1358,70 +1801,25 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 
     PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
 
-    /* -- handle paFramesPerBufferUnspecified -- */
-    if( framesPerBuffer == paFramesPerBufferUnspecified ) {
-       long requested = 64;
-       if( inputParameters )
-          requested = MAX( requested, inputParameters->suggestedLatency * sampleRate / 2 );
-       if( outputParameters )
-          requested = MAX( requested, outputParameters->suggestedLatency *sampleRate / 2 );
-       VDBUG( ("Block Size unspecified. Based on Latency, the user wants a Block Size near: %ld.\n",
-              requested ) );
-       if( requested <= 64 ) {
-          /*requested a realtively low latency. make sure this is in range of devices */
-          /*try to get the device's min natural buffer size and use that (but no smaller than 64).*/
-          AudioValueRange audioRange;
-          UInt32 size = sizeof( audioRange );
-          if( inputParameters ) {
-             WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device],
-                                          0,
-                                          false,
-                                          kAudioDevicePropertyBufferFrameSizeRange,
-                                          &size, &audioRange ) );
-             if( result )
-                requested = MAX( requested, audioRange.mMinimum );
-          }
-          size = sizeof( audioRange );
-          if( outputParameters ) {
-             WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device],
-                                          0,
-                                          false,
-                                          kAudioDevicePropertyBufferFrameSizeRange,
-                                          &size, &audioRange ) );
-             if( result )
-                requested = MAX( requested, audioRange.mMinimum );
-          }
-       } else {
-          /* requested a realtively high latency. make sure this is in range of devices */
-          /*try to get the device's max natural buffer size and use that (but no larger than 1024).*/
-          AudioValueRange audioRange;
-          UInt32 size = sizeof( audioRange );
-          requested = MIN( requested, 1024 );
-          if( inputParameters ) {
-             WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device],
-                                          0,
-                                          false,
-                                          kAudioDevicePropertyBufferFrameSizeRange,
-                                          &size, &audioRange ) );
-             if( result )
-                requested = MIN( requested, audioRange.mMaximum );
-          }
-          size = sizeof( audioRange );
-          if( outputParameters ) {
-             WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device],
-                                          0,
-                                          false,
-                                          kAudioDevicePropertyBufferFrameSizeRange,
-                                          &size, &audioRange ) );
-             if( result )
-                requested = MIN( requested, audioRange.mMaximum );
-          }
-       }
-       /* -- double check ranges -- */
-       if( requested > 1024 ) requested = 1024;
-       if( requested < 64 ) requested = 64;
-       VDBUG(("After querying hardware, setting block size to %ld.\n", requested));
-       framesPerBuffer = requested;
+    
+    if( inputParameters )
+    {
+        CalculateFixedDeviceLatency( auhalHostApi->devIds[inputParameters->device], true, &fixedInputLatency );
+        inputLatencyFrames += fixedInputLatency;
+    }
+    if( outputParameters )
+    {        
+        CalculateFixedDeviceLatency( auhalHostApi->devIds[outputParameters->device], false, &fixedOutputLatency );
+        outputLatencyFrames += fixedOutputLatency;
+
+    }
+    
+    suggestedLatencyFramesPerBuffer = CalculateOptimalBufferSize( auhalHostApi, inputParameters, outputParameters,
+                                                                 fixedInputLatency, fixedOutputLatency,
+                                                                 sampleRate, requestedFramesPerBuffer );
+    if( requestedFramesPerBuffer == paFramesPerBufferUnspecified )
+	{
+        requestedFramesPerBuffer = suggestedLatencyFramesPerBuffer;
     }
 
     /* -- Now we actually open and setup streams. -- */
@@ -1432,7 +1830,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
        result = OpenAndSetupOneAudioUnit( stream,
                                           inputParameters,
                                           outputParameters,
-                                          framesPerBuffer,
+                                          suggestedLatencyFramesPerBuffer,
                                           &inputFramesPerBuffer,
                                           &outputFramesPerBuffer,
                                           auhalHostApi,
@@ -1455,7 +1853,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
        result = OpenAndSetupOneAudioUnit( stream,
                                           NULL,
                                           outputParameters,
-                                          framesPerBuffer,
+                                          suggestedLatencyFramesPerBuffer,
                                           NULL,
                                           &outputFramesPerBuffer,
                                           auhalHostApi,
@@ -1469,7 +1867,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
        result = OpenAndSetupOneAudioUnit( stream,
                                           inputParameters,
                                           NULL,
-                                          framesPerBuffer,
+                                          suggestedLatencyFramesPerBuffer,
                                           &inputFramesPerBuffer,
                                           NULL,
                                           auhalHostApi,
@@ -1483,7 +1881,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
        stream->inputFramesPerBuffer = inputFramesPerBuffer;
        stream->outputFramesPerBuffer = outputFramesPerBuffer;
     }
-
+    
+    inputLatencyFrames += stream->inputFramesPerBuffer;
+    outputLatencyFrames += stream->outputFramesPerBuffer;
+    
     if( stream->inputUnit ) {
        const size_t szfl = sizeof(float);
        /* setup the AudioBufferList used for input */
@@ -1509,7 +1910,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         * ring buffer to store inpt data while waiting for output
         * data.
         */
-       if( (stream->outputUnit && stream->inputUnit != stream->outputUnit)
+       if( (stream->outputUnit && (stream->inputUnit != stream->outputUnit))
            || stream->inputSRConverter )
        {
           /* May want the ringSize ot initial position in
@@ -1527,7 +1928,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 
 
           /*now, we need to allocate memory for the ring buffer*/
-          data = calloc( ringSize, szfl );
+          data = calloc( ringSize, szfl*inputParameters->channelCount );
           if( !data )
           {
              result = paInsufficientMemory;
@@ -1535,12 +1936,14 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
           }
 
           /* now we can initialize the ring buffer */
-          PaUtil_InitializeRingBuffer( &stream->inputRingBuffer,
-                                   ringSize*szfl, data ) ;
+          PaUtil_InitializeRingBuffer( &stream->inputRingBuffer, szfl*inputParameters->channelCount, ringSize, data ) ;
           /* advance the read point a little, so we are reading from the
              middle of the buffer */
           if( stream->outputUnit )
-             PaUtil_AdvanceRingBufferWriteIndex( &stream->inputRingBuffer, ringSize*szfl / RING_BUFFER_ADVANCE_DENOMINATOR );
+             PaUtil_AdvanceRingBufferWriteIndex( &stream->inputRingBuffer, ringSize / RING_BUFFER_ADVANCE_DENOMINATOR );
+           
+           // Just adds to input latency between input device and PA full duplex callback.
+           inputLatencyFrames += ringSize;
        }
     }
 
@@ -1563,6 +1966,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
               outputParameters?outputChannelCount:0 ) ;
        if( result != paNoError )
           goto error;
+        
+        inputLatencyFrames += ringSize;
+        outputLatencyFrames += ringSize;
+        
     }
 
     /* -- initialize Buffer Processor -- */
@@ -1577,7 +1984,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                  hostOutputSampleFormat,
                  sampleRate,
                  streamFlags,
-                 framesPerBuffer,
+                 requestedFramesPerBuffer,
                  /* If sample rate conversion takes place, the buffer size
                     will not be known. */
                  maxHostFrames,
@@ -1591,49 +1998,75 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     }
     stream->bufferProcessorIsInitialized = TRUE;
 
-    /*
-        IMPLEMENT ME: initialise the following fields with estimated or actual
-        values.
-        I think this is okay the way it is br 12/1/05
-        maybe need to change input latency estimate if IO devs differ
-    */
-    stream->streamRepresentation.streamInfo.inputLatency =
-            PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)/sampleRate;
-    stream->streamRepresentation.streamInfo.outputLatency =
-            PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)/sampleRate;
-    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
-
-    stream->sampleRate  = sampleRate;
-    stream->outDeviceSampleRate = 0;
-    if( stream->outputUnit ) {
-       Float64 rate;
-       UInt32 size = sizeof( rate );
-       result = ERR( AudioDeviceGetProperty( stream->outputDevice,
-                                    0,
-                                    FALSE,
-                                    kAudioDevicePropertyNominalSampleRate,
-                                    &size, &rate ) );
-       if( result )
-          goto error;
-       stream->outDeviceSampleRate = rate;
+    // Calculate actual latency from the sum of individual latencies.
+    if( inputParameters ) 
+    {
+        inputLatencyFrames += PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor);
+        stream->streamRepresentation.streamInfo.inputLatency = inputLatencyFrames / sampleRate;
     }
-    stream->inDeviceSampleRate = 0;
-    if( stream->inputUnit ) {
-       Float64 rate;
-       UInt32 size = sizeof( rate );
-       result = ERR( AudioDeviceGetProperty( stream->inputDevice,
-                                    0,
-                                    TRUE,
-                                    kAudioDevicePropertyNominalSampleRate,
-                                    &size, &rate ) );
-       if( result )
-          goto error;
-       stream->inDeviceSampleRate = rate;
+    else
+    {
+        stream->streamRepresentation.streamInfo.inputLatency = 0.0;
+    }
+    
+    if( outputParameters ) 
+    {
+        outputLatencyFrames += PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor);
+        stream->streamRepresentation.streamInfo.outputLatency = outputLatencyFrames / sampleRate;
     }
+    else
+    {
+        stream->streamRepresentation.streamInfo.outputLatency = 0.0;
+    }
+    
+    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+    stream->sampleRate = sampleRate;
+    
     stream->userInChan  = inputChannelCount;
     stream->userOutChan = outputChannelCount;
 
-    stream->isTimeSet   = FALSE;
+    // Setup property listeners for timestamp and latency calculations.
+	pthread_mutex_init( &stream->timingInformationMutex, NULL );
+	stream->timingInformationMutexIsInitialized = 1;
+    InitializeDeviceProperties( &stream->inputProperties );     // zeros the struct. doesn't actually init it to useful values
+    InitializeDeviceProperties( &stream->outputProperties );    // zeros the struct. doesn't actually init it to useful values
+	if( stream->outputUnit )
+    {
+        Boolean isInput = FALSE;
+        
+        // Start with the current values for the device properties.
+        // Init with nominal sample rate. Use actual sample rate where available
+        
+        result = ERR( UpdateSampleRateFromDeviceProperty( 
+                stream, stream->outputDevice, isInput, kAudioDevicePropertyNominalSampleRate )  );
+        if( result )
+            goto error; /* fail if we can't even get a nominal device sample rate */
+        
+        UpdateSampleRateFromDeviceProperty( stream, stream->outputDevice, isInput, kAudioDevicePropertyActualSampleRate );
+        
+        SetupDevicePropertyListeners( stream, stream->outputDevice, isInput );
+    }
+	if( stream->inputUnit )
+    {
+        Boolean isInput = TRUE;
+       
+        // as above
+        result = ERR( UpdateSampleRateFromDeviceProperty( 
+                stream, stream->inputDevice, isInput, kAudioDevicePropertyNominalSampleRate )  );
+        if( result )
+            goto error;
+        
+        UpdateSampleRateFromDeviceProperty( stream, stream->inputDevice, isInput, kAudioDevicePropertyActualSampleRate );
+        
+        SetupDevicePropertyListeners( stream, stream->inputDevice, isInput );
+	}
+    UpdateTimeStampOffsets( stream );
+    // Setup timestamp copies to be used by audio callback.
+    stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined;
+    stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice;
+    stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice;
+
     stream->state = STOPPED;
     stream->xrunFlags = 0;
 
@@ -1646,56 +2079,12 @@ error:
     return result;
 }
 
-PaTime GetStreamTime( PaStream *s )
-{
-   /* FIXME: I am not at all sure this timing info stuff is right.
-             patest_sine_time reports negative latencies, which is wierd.*/
-    PaMacCoreStream *stream = (PaMacCoreStream*)s;
-    AudioTimeStamp timeStamp;
-
-    VVDBUG(("GetStreamTime()\n"));
-
-    if ( !stream->isTimeSet )
-        return (PaTime)0;
-
-    if ( stream->outputDevice ) {
-        AudioDeviceGetCurrentTime( stream->outputDevice, &timeStamp);
-        return (PaTime)(timeStamp.mSampleTime - stream->startTime.mSampleTime)/stream->outDeviceSampleRate;
-    } else if ( stream->inputDevice ) {
-        AudioDeviceGetCurrentTime( stream->inputDevice, &timeStamp);
-    return (PaTime)(timeStamp.mSampleTime - stream->startTime.mSampleTime)/stream->inDeviceSampleRate;
-    } else {
-        return (PaTime)0;
-    }
-}
-
-static void setStreamStartTime( PaStream *stream )
-{
-   /* FIXME: I am not at all sure this timing info stuff is right.
-             patest_sine_time reports negative latencies, which is wierd.*/
-   PaMacCoreStream *s = (PaMacCoreStream *) stream;
-   VVDBUG(("setStreamStartTime()\n"));
-   if( s->outputDevice )
-      AudioDeviceGetCurrentTime( s->outputDevice, &s->startTime);
-   else if( s->inputDevice )
-      AudioDeviceGetCurrentTime( s->inputDevice, &s->startTime);
-   else
-      bzero( &s->startTime, sizeof( s->startTime ) );
-
-   //FIXME: we need a memory barier here
-
-   s->isTimeSet = TRUE;
-}
 
+#define HOST_TIME_TO_PA_TIME( x ) ( AudioConvertHostTimeToNanos( (x) ) * 1.0E-09) /* convert to nanoseconds and then to seconds */
 
-static PaTime TimeStampToSecs(PaMacCoreStream *stream, const AudioTimeStamp* timeStamp)
+PaTime GetStreamTime( PaStream *s )
 {
-    VVDBUG(("TimeStampToSecs()\n"));
-    //printf( "ATS: %lu, %g, %g\n", timeStamp->mFlags, timeStamp->mSampleTime, timeStamp->mRateScalar );
-    if (timeStamp->mFlags & kAudioTimeStampSampleTimeValid)
-        return (timeStamp->mSampleTime / stream->sampleRate);
-    else
-        return 0;
+	return HOST_TIME_TO_PA_TIME( AudioGetCurrentHostTime() ); 
 }
 
 #define RING_BUFFER_EMPTY (1000)
@@ -1706,23 +2095,25 @@ static OSStatus ringBufferIOProc( AudioConverterRef inAudioConverter,
                              void*inUserData )
 {
    void *dummyData;
-   long dummySize;
+   ring_buffer_size_t dummySize;
    PaUtilRingBuffer *rb = (PaUtilRingBuffer *) inUserData;
 
    VVDBUG(("ringBufferIOProc()\n"));
 
-   assert( sizeof( UInt32 ) == sizeof( long ) );
    if( PaUtil_GetRingBufferReadAvailable( rb ) == 0 ) {
       *outData = NULL;
       *ioDataSize = 0;
       return RING_BUFFER_EMPTY;
    }
+   assert(sizeof(UInt32) == sizeof(ring_buffer_size_t));
+   assert( ( (*ioDataSize) / rb->elementSizeBytes ) * rb->elementSizeBytes == (*ioDataSize) ) ;
+   (*ioDataSize) /= rb->elementSizeBytes ;
    PaUtil_GetRingBufferReadRegions( rb, *ioDataSize,
-                                    outData, (long *)ioDataSize, 
+                                    outData, (ring_buffer_size_t *)ioDataSize, 
                                     &dummyData, &dummySize );
-      
    assert( *ioDataSize );
    PaUtil_AdvanceRingBufferReadIndex( rb, *ioDataSize );
+   (*ioDataSize) *= rb->elementSizeBytes ;
 
    return noErr;
 }
@@ -1744,11 +2135,12 @@ static OSStatus AudioIOProc( void *inRefCon,
    PaMacCoreStream *stream           = (PaMacCoreStream*)inRefCon;
    const bool isRender               = inBusNumber == OUTPUT_ELEMENT;
    int callbackResult                = paContinue ;
-
+   double hostTimeStampInPaTime      = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime);
+    
    VVDBUG(("AudioIOProc()\n"));
 
    PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
-
+    
    /* -----------------------------------------------------------------*\
       This output may be useful for debugging,
       But printing durring the callback is a bad enough idea that
@@ -1776,24 +2168,66 @@ static OSStatus AudioIOProc( void *inRefCon,
    }
       ----------------------------------------------------------------- */
 
-   if( !stream->isTimeSet )
-      setStreamStartTime( stream );
-
-   if( isRender ) {
-      AudioTimeStamp currentTime;
-      timeInfo.outputBufferDacTime = TimeStampToSecs(stream, inTimeStamp);
-      AudioDeviceGetCurrentTime(stream->outputDevice, &currentTime);
-      timeInfo.currentTime = TimeStampToSecs(stream, &currentTime);
-   }
-   if( isRender && stream->inputUnit == stream->outputUnit )
-      timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp);
-   if( !isRender ) {
-      AudioTimeStamp currentTime;
-      timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp);
-      AudioDeviceGetCurrentTime(stream->inputDevice, &currentTime);
-      timeInfo.currentTime = TimeStampToSecs(stream, &currentTime);
-   }
-
+	/* compute PaStreamCallbackTimeInfo */
+	
+	if( pthread_mutex_trylock( &stream->timingInformationMutex ) == 0 ){
+		/* snapshot the ioproc copy of timing information */
+		stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined;
+		stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice;
+		stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice;
+		pthread_mutex_unlock( &stream->timingInformationMutex );
+	}
+	
+	/* For timeInfo.currentTime we could calculate current time backwards from the HAL audio 
+	 output time to give a more accurate impression of the current timeslice but it doesn't 
+	 seem worth it at the moment since other PA host APIs don't do any better.
+	 */
+	timeInfo.currentTime = HOST_TIME_TO_PA_TIME( AudioGetCurrentHostTime() );
+	
+	/*
+	 For an input HAL AU, inTimeStamp is the time the samples are received from the hardware,
+	 for an output HAL AU inTimeStamp is the time the samples are sent to the hardware. 
+	 PA expresses timestamps in terms of when the samples enter the ADC or leave the DAC
+	 so we add or subtract kAudioDevicePropertyLatency below.
+	 */
+	
+	/* FIXME: not sure what to do below if the host timestamps aren't valid (kAudioTimeStampHostTimeValid isn't set)
+	 Could ask on CA mailing list if it is possible for it not to be set. If so, could probably grab a now timestamp
+	 at the top and compute from there (modulo scheduling jitter) or ask on mailing list for other options. */
+	
+	if( isRender )
+	{
+		if( stream->inputUnit ) /* full duplex */
+		{
+			if( stream->inputUnit == stream->outputUnit ) /* full duplex AUHAL IOProc */
+			{
+                // Ross and Phil agreed that the following calculation is correct based on an email from Jeff Moore:
+                // http://osdir.com/ml/coreaudio-api/2009-07/msg00140.html
+                // Basically the difference between the Apple output timestamp and the PA timestamp is kAudioDevicePropertyLatency.
+				timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - 
+                    (stream->timestampOffsetCombined_ioProcCopy + stream->timestampOffsetInputDevice_ioProcCopy);
+ 				timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy;
+			}
+			else /* full duplex with ring-buffer from a separate input AUHAL ioproc */
+			{
+				/* FIXME: take the ring buffer latency into account */
+				timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - 
+                    (stream->timestampOffsetCombined_ioProcCopy + stream->timestampOffsetInputDevice_ioProcCopy);
+				timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy;
+			}
+		}
+		else /* output only */
+		{
+			timeInfo.inputBufferAdcTime = 0;
+			timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy;
+		}
+	}
+	else /* input only */
+	{
+		timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - stream->timestampOffsetInputDevice_ioProcCopy; 
+		timeInfo.outputBufferDacTime = 0;
+	}
+	
    //printf( "---%g, %g, %g\n", timeInfo.inputBufferAdcTime, timeInfo.currentTime, timeInfo.outputBufferDacTime );
 
    if( isRender && stream->inputUnit == stream->outputUnit
@@ -1807,7 +2241,8 @@ static OSStatus AudioIOProc( void *inRefCon,
        *
        */
       OSStatus err = 0;
-      unsigned long frames;
+       unsigned long frames;
+       long bytesPerFrame = sizeof( float ) * ioData->mBuffers[0].mNumberChannels;
 
       /* -- start processing -- */
       PaUtil_BeginBufferProcessing( &(stream->bufferProcessor),
@@ -1818,8 +2253,8 @@ static OSStatus AudioIOProc( void *inRefCon,
       /* -- compute frames. do some checks -- */
       assert( ioData->mNumberBuffers == 1 );
       assert( ioData->mBuffers[0].mNumberChannels == stream->userOutChan );
-      frames = ioData->mBuffers[0].mDataByteSize;
-      frames /= sizeof( float ) * ioData->mBuffers[0].mNumberChannels;
+
+      frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame;
       /* -- copy and process input data -- */
       err= AudioUnitRender(stream->inputUnit,
                     ioActionFlags,
@@ -1827,7 +2262,8 @@ static OSStatus AudioIOProc( void *inRefCon,
                     INPUT_ELEMENT,
                     inNumberFrames,
                     &stream->inputAudioBufferList );
-      /* FEEDBACK: I'm not sure what to do when this call fails */
+      /* FEEDBACK: I'm not sure what to do when this call fails. There's nothing in the PA API to
+       * do about failures in the callback system. */
       assert( !err );
 
       PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames );
@@ -1856,7 +2292,8 @@ static OSStatus AudioIOProc( void *inRefCon,
        * and into the PA buffer processor. If sample rate conversion
        * is required on input, that is done here as well.
        */
-      unsigned long frames;
+       unsigned long frames;
+       long bytesPerFrame = sizeof( float ) * ioData->mBuffers[0].mNumberChannels;
 
       /* Sometimes, when stopping a duplex stream we get erroneous
          xrun flags, so if this is our last run, clear the flags. */
@@ -1878,8 +2315,7 @@ static OSStatus AudioIOProc( void *inRefCon,
 
       /* -- Copy and process output data -- */
       assert( ioData->mNumberBuffers == 1 );
-      frames = ioData->mBuffers[0].mDataByteSize;
-      frames /= sizeof( float ) * ioData->mBuffers[0].mNumberChannels;
+      frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame;
       assert( ioData->mBuffers[0].mNumberChannels == stream->userOutChan );
       PaUtil_SetOutputFrameCount( &(stream->bufferProcessor), frames );
       PaUtil_SetInterleavedOutputChannels( &(stream->bufferProcessor),
@@ -1893,6 +2329,8 @@ static OSStatus AudioIOProc( void *inRefCon,
          /* Here, we read the data out of the ring buffer, through the
             audio converter. */
          int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels;
+         long bytesPerFrame = flsz * inChan;
+          
          if( stream->inputSRConverter )
          {
                OSStatus err;
@@ -1909,7 +2347,12 @@ static OSStatus AudioIOProc( void *inRefCon,
                { /*the ring buffer callback underflowed */
                   err = 0;
                   bzero( ((char *)data) + size, sizeof(data)-size );
-                  stream->xrunFlags |= paInputUnderflow;
+                  /* The ring buffer can underflow normally when the stream is stopping.
+                   * So only report an error if the stream is active. */
+                  if( stream->state == ACTIVE )
+                  {
+                      stream->xrunFlags |= paInputUnderflow;
+                  }
                }
                ERR( err );
                assert( !err );
@@ -1929,12 +2372,12 @@ static OSStatus AudioIOProc( void *inRefCon,
                because we have to do a little buffer processing that the
                AudioConverter would otherwise handle for us. */
             void *data1, *data2;
-            long size1, size2;
-            PaUtil_GetRingBufferReadRegions( &stream->inputRingBuffer,
-                                             inChan*frames*flsz,
+            ring_buffer_size_t size1, size2;
+            ring_buffer_size_t framesReadable = PaUtil_GetRingBufferReadRegions( &stream->inputRingBuffer,
+                                             frames,
                                              &data1, &size1,
                                              &data2, &size2 );
-            if( size1 / ( flsz * inChan ) == frames ) {
+            if( size1 == frames ) {
                /* simplest case: all in first buffer */
                PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames );
                PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor),
@@ -1945,14 +2388,21 @@ static OSStatus AudioIOProc( void *inRefCon,
                     PaUtil_EndBufferProcessing( &(stream->bufferProcessor),
                                                 &callbackResult );
                PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, size1 );
-            } else if( ( size1 + size2 ) / ( flsz * inChan ) < frames ) {
+            } else if( framesReadable < frames ) {
+                
+                long sizeBytes1 = size1 * bytesPerFrame;
+                long sizeBytes2 = size2 * bytesPerFrame;
                /*we underflowed. take what data we can, zero the rest.*/
-               unsigned char data[frames*inChan*flsz];
-               if( size1 )
-                  memcpy( data, data1, size1 );
-               if( size2 )
-                  memcpy( data+size1, data2, size2 );
-               bzero( data+size1+size2, frames*flsz*inChan - size1 - size2 );
+               unsigned char data[ frames * bytesPerFrame ];
+               if( size1 > 0 )
+               {   
+                   memcpy( data, data1, sizeBytes1 );
+               }
+               if( size2 > 0 )
+               {
+                   memcpy( data+sizeBytes1, data2, sizeBytes2 );
+               }
+               bzero( data+sizeBytes1+sizeBytes2, (frames*bytesPerFrame) - sizeBytes1 - sizeBytes2 );
 
                PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames );
                PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor),
@@ -1963,19 +2413,17 @@ static OSStatus AudioIOProc( void *inRefCon,
                     PaUtil_EndBufferProcessing( &(stream->bufferProcessor),
                                                 &callbackResult );
                PaUtil_AdvanceRingBufferReadIndex( &stream->inputRingBuffer,
-                                                  size1+size2 );
+                                                  framesReadable );
                /* flag underflow */
                stream->xrunFlags |= paInputUnderflow;
             } else {
                /*we got all the data, but split between buffers*/
-               PaUtil_SetInputFrameCount( &(stream->bufferProcessor),
-                                          size1 / ( flsz * inChan ) );
+               PaUtil_SetInputFrameCount( &(stream->bufferProcessor), size1 );
                PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor),
                                    0,
                                    data1,
                                    inChan );
-               PaUtil_Set2ndInputFrameCount( &(stream->bufferProcessor),
-                                             size2 / ( flsz * inChan ) );
+               PaUtil_Set2ndInputFrameCount( &(stream->bufferProcessor), size2 );
                PaUtil_Set2ndInterleavedInputChannels( &(stream->bufferProcessor),
                                    0,
                                    data2,
@@ -1983,7 +2431,7 @@ static OSStatus AudioIOProc( void *inRefCon,
                framesProcessed =
                     PaUtil_EndBufferProcessing( &(stream->bufferProcessor),
                                                 &callbackResult );
-               PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, size1+size2 );
+               PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, framesReadable );
             }
          }
       } else {
@@ -2021,13 +2469,13 @@ static OSStatus AudioIOProc( void *inRefCon,
       {
          /* If this is duplex or we use a converter, put the data
             into the ring buffer. */
-         long bytesIn, bytesOut;
-         bytesIn = sizeof( float ) * inNumberFrames * chan;
-         bytesOut = PaUtil_WriteRingBuffer( &stream->inputRingBuffer,
+          ring_buffer_size_t framesWritten = PaUtil_WriteRingBuffer( &stream->inputRingBuffer,
                                             stream->inputAudioBufferList.mBuffers[0].mData,
-                                            bytesIn );
-         if( bytesIn != bytesOut )
-            stream->xrunFlags |= paInputOverflow ;
+                                            inNumberFrames );
+         if( framesWritten != inNumberFrames )
+         {
+             stream->xrunFlags |= paInputOverflow ;
+         }
       }
       else
       {
@@ -2104,7 +2552,6 @@ static OSStatus AudioIOProc( void *inRefCon,
    case paContinue: break;
    case paComplete:
    case paAbort:
-      stream->isTimeSet = FALSE;
       stream->state = CALLBACK_STOPPED ;
       if( stream->outputUnit )
          AudioOutputUnitStop(stream->outputUnit);
@@ -2133,18 +2580,37 @@ static PaError CloseStream( PaStream* s )
     VDBUG( ( "Closing stream.\n" ) );
 
     if( stream ) {
-       if( stream->outputUnit )
-          AudioDeviceRemovePropertyListener( stream->outputDevice,
-                                             0,
-                                             false,
-                                             kAudioDeviceProcessorOverload,
-                                             xrunCallback );
-       if( stream->inputUnit && stream->outputUnit != stream->inputUnit )
-          AudioDeviceRemovePropertyListener( stream->inputDevice,
-                                             0,
-                                             true,
-                                             kAudioDeviceProcessorOverload,
-                                             xrunCallback );
+		
+		if( stream->outputUnit )
+        {
+            Boolean isInput = FALSE;
+            CleanupDevicePropertyListeners( stream, stream->outputDevice, isInput );
+		}
+		
+		if( stream->inputUnit )
+        {
+            Boolean isInput = TRUE;
+            CleanupDevicePropertyListeners( stream, stream->inputDevice, isInput );
+		}
+		
+       if( stream->outputUnit ) {
+          int count = removeFromXRunListenerList( stream );
+          if( count == 0 )
+             AudioDeviceRemovePropertyListener( stream->outputDevice,
+                                                0,
+                                                false,
+                                                kAudioDeviceProcessorOverload,
+                                                xrunCallback );
+       }
+       if( stream->inputUnit && stream->outputUnit != stream->inputUnit ) {
+          int count = removeFromXRunListenerList( stream );
+          if( count == 0 )
+             AudioDeviceRemovePropertyListener( stream->inputDevice,
+                                                0,
+                                                true,
+                                                kAudioDeviceProcessorOverload,
+                                                xrunCallback );
+       }
        if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) {
           AudioUnitUninitialize( stream->outputUnit );
           CloseComponent( stream->outputUnit );
@@ -2173,6 +2639,10 @@ static PaError CloseStream( PaStream* s )
           return result;
        if( stream->bufferProcessorIsInitialized )
           PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+		
+       if( stream->timingInformationMutexIsInitialized )
+          pthread_mutex_destroy( &stream->timingInformationMutex );
+
        PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
        PaUtil_FreeMemory( stream );
     }
@@ -2202,10 +2672,7 @@ static PaError StartStream( PaStream *s )
     if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) {
        ERR_WRAP( AudioOutputUnitStart(stream->outputUnit) );
     }
-
-    //setStreamStartTime( stream );
-    //stream->isTimeSet = TRUE;
-
+	
     return paNoError;
 #undef ERR_WRAP
 }
@@ -2236,7 +2703,6 @@ static PaError StopStream( PaStream *s )
     waitUntilBlioWriteBufferIsFlushed( &stream->blio );
     VDBUG( ( "Stopping stream.\n" ) );
 
-    stream->isTimeSet = FALSE;
     stream->state = STOPPING;
 
 #define ERR_WRAP(mac_err) do { result = mac_err ; if ( result != noErr ) return ERR(result) ; } while(0)
@@ -2284,10 +2750,6 @@ static PaError StopStream( PaStream *s )
     if( paErr )
        return paErr;
 
-/*
-    //stream->isTimeSet = FALSE;
-*/
-
     VDBUG( ( "Stream Stopped.\n" ) );
     return paNoError;
 #undef ERR_WRAP
diff --git a/external/portaudio/pa_mac_core.h b/external/portaudio/pa_mac_core.h
index d7ff4a2..83e40a6 100644
--- a/external/portaudio/pa_mac_core.h
+++ b/external/portaudio/pa_mac_core.h
@@ -38,6 +38,13 @@
  * license above.
  */
 
+/** @file
+ *  @ingroup public_header
+ *  @brief CoreAudio-specific PortAudio API extension header file.
+ */
+
+#include "portaudio.h"
+
 #include <AudioUnit/AudioUnit.h>
 #include <AudioToolbox/AudioToolbox.h>
 
@@ -46,7 +53,7 @@ extern "C" {
 #endif
 
 
-/*
+/**
  * A pointer to a paMacCoreStreamInfo may be passed as
  * the hostApiSpecificStreamInfo in the PaStreamParameters struct
  * when opening a stream or querying the format. Use NULL, for the
@@ -58,17 +65,17 @@ typedef struct
     unsigned long size;           /**size of whole structure including this header */
     PaHostApiTypeId hostApiType;  /**host API for which this data is intended */
     unsigned long version;        /**structure version */
-    unsigned long flags;          /* flags to modify behaviour */
-    SInt32 const * channelMap;    /* Channel map for HAL channel mapping , if not needed, use NULL;*/ 
-    unsigned long channelMapSize; /* Channel map size for HAL channel mapping , if not needed, use 0;*/ 
+    unsigned long flags;          /** flags to modify behaviour */
+    SInt32 const * channelMap;    /** Channel map for HAL channel mapping , if not needed, use NULL;*/ 
+    unsigned long channelMapSize; /** Channel map size for HAL channel mapping , if not needed, use 0;*/ 
 } PaMacCoreStreamInfo;
 
-/*
+/**
  * Functions
  */
 
 
-/* Use this function to initialize a paMacCoreStreamInfo struct
+/** Use this function to initialize a paMacCoreStreamInfo struct
  * using the requested flags. Note that channel mapping is turned
  * off after a call to this function.
  * @param data The datastructure to initialize
@@ -76,14 +83,14 @@ typedef struct
 */
 void PaMacCore_SetupStreamInfo( PaMacCoreStreamInfo *data, unsigned long flags );
 
-/* call this after pa_SetupMacCoreStreamInfo to use channel mapping as described in notes.txt.
+/** call this after pa_SetupMacCoreStreamInfo to use channel mapping as described in notes.txt.
  * @param data The stream info structure to assign a channel mapping to
  * @param channelMap The channel map array, as described in notes.txt. This array pointer will be used directly (ie the underlying data will not be copied), so the caller should not free the array until after the stream has been opened.
  * @param channelMapSize The size of the channel map array.
  */
 void PaMacCore_SetupChannelMap( PaMacCoreStreamInfo *data, const SInt32 * const channelMap, unsigned long channelMapSize );
 
-/*
+/**
  * Retrieve the AudioDeviceID of the input device assigned to an open stream
  *
  * @param s The stream to query.
@@ -92,7 +99,7 @@ void PaMacCore_SetupChannelMap( PaMacCoreStreamInfo *data, const SInt32 * const
  */
 AudioDeviceID PaMacCore_GetStreamInputDevice( PaStream* s );
  
-/*
+/**
  * Retrieve the AudioDeviceID of the output device assigned to an open stream
  *
  * @param s The stream to query.
@@ -101,7 +108,7 @@ AudioDeviceID PaMacCore_GetStreamInputDevice( PaStream* s );
  */
 AudioDeviceID PaMacCore_GetStreamOutputDevice( PaStream* s );
 
-/*
+/**
  * Returns a statically allocated string with the device's name
  * for the given channel. NULL will be returned on failure.
  *
@@ -117,28 +124,41 @@ AudioDeviceID PaMacCore_GetStreamOutputDevice( PaStream* s );
  */
 const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input );
 
-/*
+    
+/** Retrieve the range of legal native buffer sizes for the specificed device, in sample frames.
+ 
+ @param device The global index of the PortAudio device about which the query is being made.
+ @param minBufferSizeFrames A pointer to the location which will receive the minimum buffer size value.
+ @param maxBufferSizeFrames A pointer to the location which will receive the maximum buffer size value.
+ 
+ @see kAudioDevicePropertyBufferFrameSizeRange in the CoreAudio SDK.
+ */
+PaError PaMacCore_GetBufferSizeRange( PaDeviceIndex device,
+                                       long *minBufferSizeFrames, long *maxBufferSizeFrames );
+
+
+/**
  * Flags
  */
 
-/*
+/**
  * The following flags alter the behaviour of PA on the mac platform.
  * they can be ORed together. These should work both for opening and
  * checking a device.
  */
 
-/* Allows PortAudio to change things like the device's frame size,
+/** Allows PortAudio to change things like the device's frame size,
  * which allows for much lower latency, but might disrupt the device
  * if other programs are using it, even when you are just Querying
  * the device. */
 #define paMacCoreChangeDeviceParameters (0x01)
 
-/* In combination with the above flag,
+/** In combination with the above flag,
  * causes the stream opening to fail, unless the exact sample rates
  * are supported by the device. */
 #define paMacCoreFailIfConversionRequired (0x02)
 
-/* These flags set the SR conversion quality, if required. The wierd ordering
+/** These flags set the SR conversion quality, if required. The wierd ordering
  * allows Maximum Quality to be the default.*/
 #define paMacCoreConversionQualityMin    (0x0100)
 #define paMacCoreConversionQualityMedium (0x0200)
@@ -146,26 +166,26 @@ const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input )
 #define paMacCoreConversionQualityHigh   (0x0400)
 #define paMacCoreConversionQualityMax    (0x0000)
 
-/*
+/**
  * Here are some "preset" combinations of flags (above) to get to some
  * common configurations. THIS IS OVERKILL, but if more flags are added
  * it won't be.
  */
 
-/*This is the default setting: do as much sample rate conversion as possible
+/**This is the default setting: do as much sample rate conversion as possible
  * and as little mucking with the device as possible. */
 #define paMacCorePlayNice                    (0x00)
-/*This setting is tuned for pro audio apps. It allows SR conversion on input
+/**This setting is tuned for pro audio apps. It allows SR conversion on input
   and output, but it tries to set the appropriate SR on the device.*/
 #define paMacCorePro                         (0x01)
-/*This is a setting to minimize CPU usage and still play nice.*/
+/**This is a setting to minimize CPU usage and still play nice.*/
 #define paMacCoreMinimizeCPUButPlayNice      (0x0100)
-/*This is a setting to minimize CPU usage, even if that means interrupting the device. */
+/**This is a setting to minimize CPU usage, even if that means interrupting the device. */
 #define paMacCoreMinimizeCPU                 (0x0101)
 
 
 #ifdef __cplusplus
 }
-#endif /* __cplusplus */
+#endif /** __cplusplus */
 
-#endif /* PA_MAC_CORE_H */
+#endif /** PA_MAC_CORE_H */
diff --git a/external/portaudio/pa_mac_core_blocking.c b/external/portaudio/pa_mac_core_blocking.c
index 51478fa..606a569 100644
--- a/external/portaudio/pa_mac_core_blocking.c
+++ b/external/portaudio/pa_mac_core_blocking.c
@@ -53,7 +53,7 @@
 
 /**
  @file
- @ingroup hostaip_src
+ @ingroup hostapi_src
 
  This file contains the implementation
  required for blocking I/O. It is separated from pa_mac_core.c simply to ease
@@ -71,12 +71,12 @@
 #endif
 
 /*
- * This fnuction determines the size of a particular sample format.
+ * This function determines the size of a particular sample format.
  * if the format is not recognized, this returns zero.
  */
 static size_t computeSampleSizeFromFormat( PaSampleFormat format )
 {
-   switch( format ) {
+   switch( format & (~paNonInterleaved) ) {
    case paFloat32: return 4;
    case paInt32: return 4;
    case paInt24: return 3;
@@ -91,7 +91,7 @@ static size_t computeSampleSizeFromFormat( PaSampleFormat format )
  */
 static size_t computeSampleSizeFromFormatPow2( PaSampleFormat format )
 {
-   switch( format ) {
+   switch( format & (~paNonInterleaved) ) {
    case paFloat32: return 4;
    case paInt32: return 4;
    case paInt24: return 4;
@@ -121,6 +121,7 @@ PaError initializeBlioRingBuffers(
 {
    void *data;
    int result;
+   OSStatus err;
 
    /* zeroify things */
    bzero( blio, sizeof( PaMacBlio ) );
@@ -169,10 +170,11 @@ PaError initializeBlioRingBuffers(
          goto error;
       }
 
-      assert( 0 == PaUtil_InitializeRingBuffer(
+      err = PaUtil_InitializeRingBuffer(
             &blio->inputRingBuffer,
-            ringBufferSize*blio->inputSampleSizePow2*inChan,
-            data ) );
+            1, ringBufferSize*blio->inputSampleSizePow2*inChan,
+            data );
+      assert( !err );
    }
    if( outChan ) {
       data = calloc( ringBufferSize, blio->outputSampleSizePow2*outChan );
@@ -182,10 +184,11 @@ PaError initializeBlioRingBuffers(
          goto error;
       }
 
-      assert( 0 == PaUtil_InitializeRingBuffer(
+      err = PaUtil_InitializeRingBuffer(
             &blio->outputRingBuffer,
-            ringBufferSize*blio->outputSampleSizePow2*outChan,
-            data ) );
+            1, ringBufferSize*blio->outputSampleSizePow2*outChan,
+            data );
+      assert( !err );
    }
 
    result = resetBlioRingBuffers( blio );
@@ -344,6 +347,8 @@ int BlioCallback( const void *input, void *output, unsigned long frameCount,
    long avail;
    long toRead;
    long toWrite;
+   long read;
+   long written;
 
    /* set flags returned by OS: */
    OSAtomicOr32( statusFlags, &blio->statusFlags ) ;
@@ -354,13 +359,15 @@ int BlioCallback( const void *input, void *output, unsigned long frameCount,
 
       /* check for underflow */
       if( avail < frameCount * blio->inputSampleSizeActual * blio->inChan )
+      {
          OSAtomicOr32( paInputOverflow, &blio->statusFlags );
-
+      }
       toRead = MIN( avail, frameCount * blio->inputSampleSizeActual * blio->inChan );
 
       /* copy the data */
       /*printf( "reading %d\n", toRead );*/
-      assert( toRead == PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, toRead ) );
+      read = PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, toRead );
+      assert( toRead == read );
 #ifdef PA_MAC__BLIO_MUTEX
       /* Priority inversion. See notes below. */
       blioSetIsInputEmpty( blio, false );
@@ -383,7 +390,8 @@ int BlioCallback( const void *input, void *output, unsigned long frameCount,
                 frameCount * blio->outputSampleSizeActual * blio->outChan - toWrite );
       /* copy the data */
       /*printf( "writing %d\n", toWrite );*/
-      assert( toWrite == PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, toWrite ) );
+      written = PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, toWrite );
+      assert( toWrite == written );
 #ifdef PA_MAC__BLIO_MUTEX
       /* We have a priority inversion here. However, we will only have to
          wait if this was true and is now false, which means we've got
@@ -570,7 +578,7 @@ signed long GetStreamReadAvailable( PaStream* stream )
     VVDBUG(("GetStreamReadAvailable()\n"));
 
     return PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer )
-                         / ( blio->outputSampleSizeActual * blio->outChan );
+                         / ( blio->inputSampleSizeActual * blio->inChan );
 }
 
 
diff --git a/external/portaudio/pa_mac_core_blocking.h b/external/portaudio/pa_mac_core_blocking.h
index c24531d..971223b 100644
--- a/external/portaudio/pa_mac_core_blocking.h
+++ b/external/portaudio/pa_mac_core_blocking.h
@@ -53,7 +53,7 @@
 
 /**
  @file
- @ingroup hostaip_src
+ @ingroup hostapi_src
 */
 
 #ifndef PA_MAC_CORE_BLOCKING_H_
diff --git a/external/portaudio/pa_mac_core_internal.h b/external/portaudio/pa_mac_core_internal.h
index 291304c..14e3d07 100644
--- a/external/portaudio/pa_mac_core_internal.h
+++ b/external/portaudio/pa_mac_core_internal.h
@@ -61,10 +61,11 @@
 #ifndef PA_MAC_CORE_INTERNAL_H__
 #define PA_MAC_CORE_INTERNAL_H__
 
+#include <CoreAudio/CoreAudio.h>
+#include <CoreServices/CoreServices.h>
 #include <AudioUnit/AudioUnit.h>
 #include <AudioToolbox/AudioToolbox.h>
 
-
 #include "portaudio.h"
 #include "pa_util.h"
 #include "pa_hostapi.h"
@@ -112,7 +113,22 @@ typedef struct
 }
 PaMacAUHAL;
 
-
+typedef struct PaMacCoreDeviceProperties
+{
+    /* Values in Frames from property queries. */
+    UInt32 safetyOffset;
+    UInt32 bufferFrameSize;
+    // UInt32 streamLatency; // Seems to be the same as deviceLatency!?
+    UInt32 deviceLatency;
+    /* Current device sample rate. May change! 
+       These are initialized to the nominal device sample rate, 
+       and updated with the actual sample rate, when/where available. 
+       Note that these are the *device* sample rates, prior to any required 
+       SR conversion. */
+    Float64 sampleRate;
+    Float64 samplePeriod; // reciprocal
+}
+PaMacCoreDeviceProperties;
 
 /* stream data structure specifically for this implementation */
 typedef struct PaMacCoreStream
@@ -140,8 +156,7 @@ typedef struct PaMacCoreStream
     AudioBufferList inputAudioBufferList;
     AudioTimeStamp startTime;
     /* FIXME: instead of volatile, these should be properly memory barriered */
-    volatile PaStreamCallbackFlags xrunFlags;
-    volatile bool isTimeSet;
+    volatile uint32_t xrunFlags; /*PaStreamCallbackFlags*/
     volatile enum {
        STOPPED          = 0, /* playback is completely stopped,
                                 and the user has called StopStream(). */
@@ -155,9 +170,24 @@ typedef struct PaMacCoreStream
        ACTIVE           = 3  /* The stream is active and running. */
     } state;
     double sampleRate;
-    //these may be different from the stream sample rate due to SR conversion:
-    double outDeviceSampleRate;
-    double inDeviceSampleRate;
+    PaMacCoreDeviceProperties  inputProperties;
+    PaMacCoreDeviceProperties  outputProperties;
+    
+	/* data updated by main thread and notifications, protected by timingInformationMutex */
+	int timingInformationMutexIsInitialized;
+	pthread_mutex_t timingInformationMutex;
+
+    /* These are written by the PA thread or from CoreAudio callbacks. Protected by the mutex. */
+    Float64 timestampOffsetCombined;
+    Float64 timestampOffsetInputDevice;
+    Float64 timestampOffsetOutputDevice;
+	
+	/* Offsets in seconds to be applied to Apple timestamps to convert them to PA timestamps.
+     * While the io proc is active, the following values are only accessed and manipulated by the ioproc */
+    Float64 timestampOffsetCombined_ioProcCopy;
+    Float64 timestampOffsetInputDevice_ioProcCopy;
+    Float64 timestampOffsetOutputDevice_ioProcCopy;
+    
 }
 PaMacCoreStream;
 
diff --git a/external/portaudio/pa_mac_core_utilities.c b/external/portaudio/pa_mac_core_utilities.c
index e96b78b..63e616f 100644
--- a/external/portaudio/pa_mac_core_utilities.c
+++ b/external/portaudio/pa_mac_core_utilities.c
@@ -57,6 +57,11 @@
 */
 
 #include "pa_mac_core_utilities.h"
+#include "pa_mac_core_internal.h"
+#include <libkern/OSAtomic.h>
+#include <strings.h>
+#include <pthread.h>
+#include <sys/time.h>
 
 PaError PaMacCore_SetUnixError( int err, int line )
 {
@@ -241,8 +246,8 @@ long computeRingBufferSize( const PaStreamParameters *inputParameters,
    long ringSize;
    int index;
    int i;
-   double latencyTimesChannelCount ;
-   long framesPerBufferTimesChannelCount ;
+   double latency ;
+   long framesPerBuffer ;
 
    VVDBUG(( "computeRingBufferSize()\n" ));
 
@@ -250,33 +255,25 @@ long computeRingBufferSize( const PaStreamParameters *inputParameters,
 
    if( outputParameters && inputParameters )
    {
-      latencyTimesChannelCount = MAX(
-           inputParameters->suggestedLatency * inputParameters->channelCount,
-           outputParameters->suggestedLatency * outputParameters->channelCount );
-      framesPerBufferTimesChannelCount = MAX(
-           inputFramesPerBuffer * inputParameters->channelCount,
-           outputFramesPerBuffer * outputParameters->channelCount );
+      latency = MAX( inputParameters->suggestedLatency, outputParameters->suggestedLatency );
+      framesPerBuffer = MAX( inputFramesPerBuffer, outputFramesPerBuffer );
    } 
    else if( outputParameters )
    {
-      latencyTimesChannelCount
-                = outputParameters->suggestedLatency * outputParameters->channelCount;
-      framesPerBufferTimesChannelCount
-                = outputFramesPerBuffer * outputParameters->channelCount;
+      latency = outputParameters->suggestedLatency;
+      framesPerBuffer = outputFramesPerBuffer ;
    }
    else /* we have inputParameters  */
    {
-      latencyTimesChannelCount
-                = inputParameters->suggestedLatency * inputParameters->channelCount;
-      framesPerBufferTimesChannelCount
-                = inputFramesPerBuffer * inputParameters->channelCount;
+      latency = inputParameters->suggestedLatency;
+      framesPerBuffer = inputFramesPerBuffer ;
    }
 
-   ringSize = (long) ( latencyTimesChannelCount * sampleRate * 2 + .5);
-   VDBUG( ( "suggested latency * channelCount: %d\n", (int) (latencyTimesChannelCount*sampleRate) ) );
-   if( ringSize < framesPerBufferTimesChannelCount * 3 )
-      ringSize = framesPerBufferTimesChannelCount * 3 ;
-   VDBUG(("framesPerBuffer*channelCount:%d\n",(int)framesPerBufferTimesChannelCount));
+   ringSize = (long) ( latency * sampleRate * 2 + .5);
+   VDBUG( ( "suggested latency : %d\n", (int) (latency*sampleRate) ) );
+   if( ringSize < framesPerBuffer * 3 )
+      ringSize = framesPerBuffer * 3 ;
+   VDBUG(("framesPerBuffer:%d\n",(int)framesPerBuffer));
    VDBUG(("Ringbuffer size (1): %d\n", (int)ringSize ));
 
    /* make sure it's at least 4 */
@@ -301,8 +298,13 @@ long computeRingBufferSize( const PaStreamParameters *inputParameters,
 /*
  * Durring testing of core audio, I found that serious crashes could occur
  * if properties such as sample rate were changed multiple times in rapid
- * succession. The function below has some fancy logic to make sure that changes
- * are acknowledged before another is requested. That seems to help a lot.
+ * succession. The function below could be used to with a condition variable.
+ * to prevent propertychanges from happening until the last property
+ * change is acknowledged. Instead, I implemented a busy-wait, which is simpler
+ * to implement b/c in second round of testing (nov '09) property changes occured
+ * quickly and so there was no real way to test the condition variable implementation.
+ * therefore, this function is not used, but it is aluded to in commented code below,
+ * since it represents a theoretically better implementation.
  */
 
 OSStatus propertyProc(
@@ -312,9 +314,7 @@ OSStatus propertyProc(
     AudioDevicePropertyID inPropertyID, 
     void* inClientData )
 {
-   MutexAndBool *mab = (MutexAndBool *) inClientData;
-   mab->once = TRUE;
-   pthread_mutex_unlock( &(mab->mutex) );
+   // this is where we would set the condition variable
    return noErr;
 }
 
@@ -322,7 +322,11 @@ OSStatus propertyProc(
    be acknowledged, and returns the final value, which is not guaranteed
    by this function to be the same as the desired value. Obviously, this
    function can only be used for data whose input and output are the
-   same size and format, and their size and format are known in advance.*/
+   same size and format, and their size and format are known in advance.
+   whether or not the call succeeds, if the data is successfully read,
+   it is returned in outPropertyData. If it is not read successfully,
+   outPropertyData is zeroed, which may or may not be useful in
+   determining if the property was read. */
 PaError AudioDeviceSetPropertyNowAndWaitForChange(
     AudioDeviceID inDevice,
     UInt32 inChannel, 
@@ -333,83 +337,72 @@ PaError AudioDeviceSetPropertyNowAndWaitForChange(
     void *outPropertyData )
 {
    OSStatus macErr;
-   int unixErr;
-   MutexAndBool mab;
    UInt32 outPropertyDataSize = inPropertyDataSize;
 
    /* First, see if it already has that value. If so, return. */
    macErr = AudioDeviceGetProperty( inDevice, inChannel,
                                  isInput, inPropertyID, 
                                  &outPropertyDataSize, outPropertyData );
-   if( macErr )
-      goto failMac2;
+   if( macErr ) {
+      memset( outPropertyData, 0, inPropertyDataSize );
+      goto failMac;
+   }
    if( inPropertyDataSize!=outPropertyDataSize )
       return paInternalError;
    if( 0==memcmp( outPropertyData, inPropertyData, outPropertyDataSize ) )
       return paNoError;
 
-   /* setup and lock mutex */
-   mab.once = FALSE;
-   unixErr = pthread_mutex_init( &mab.mutex, NULL );
-   if( unixErr )
-      goto failUnix2;
-   unixErr = pthread_mutex_lock( &mab.mutex );
-   if( unixErr )
-      goto failUnix;
+   /* Ideally, we'd use a condition variable to determine changes.
+      we could set that up here. */
 
-   /* add property listener */
+   /* If we were using a cond variable, we'd do something useful here,
+      but for now, this is just to make 10.6 happy. */
    macErr = AudioDeviceAddPropertyListener( inDevice, inChannel, isInput,
                                    inPropertyID, propertyProc,
-                                   &mab ); 
+                                   NULL ); 
    if( macErr )
+      /* we couldn't add a listener. */
       goto failMac;
+
    /* set property */
    macErr  = AudioDeviceSetProperty( inDevice, NULL, inChannel,
                                  isInput, inPropertyID,
                                  inPropertyDataSize, inPropertyData );
-   if( macErr ) {
-      /* we couldn't set the property, so we'll just unlock the mutex
-         and move on. */
-      pthread_mutex_unlock( &mab.mutex );
-   }
-
-   /* wait for property to change */                      
-   unixErr = pthread_mutex_lock( &mab.mutex );
-   if( unixErr )
-      goto failUnix;
-
-   /* now read the property back out */
-   macErr = AudioDeviceGetProperty( inDevice, inChannel,
-                                 isInput, inPropertyID, 
-                                 &outPropertyDataSize, outPropertyData );
    if( macErr )
       goto failMac;
-   /* cleanup */
-   AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput,
-                                      inPropertyID, propertyProc );
-   unixErr = pthread_mutex_unlock( &mab.mutex );
-   if( unixErr )
-      goto failUnix2;
-   unixErr = pthread_mutex_destroy( &mab.mutex );
-   if( unixErr )
-      goto failUnix2;
 
+   /* busy-wait up to 30 seconds for the property to change */
+   /* busy-wait is justified here only because the correct alternative (condition variable)
+      was hard to test, since most of the waiting ended up being for setting rather than
+      getting in OS X 10.5. This was not the case in earlier OS versions. */
+   struct timeval tv1, tv2;
+   gettimeofday( &tv1, NULL );
+   memcpy( &tv2, &tv1, sizeof( struct timeval ) );
+   while( tv2.tv_sec - tv1.tv_sec < 30 ) {
+      /* now read the property back out */
+      macErr = AudioDeviceGetProperty( inDevice, inChannel,
+                                    isInput, inPropertyID, 
+                                    &outPropertyDataSize, outPropertyData );
+      if( macErr ) {
+         memset( outPropertyData, 0, inPropertyDataSize );
+         goto failMac;
+      }
+      /* and compare... */
+      if( 0==memcmp( outPropertyData, inPropertyData, outPropertyDataSize ) ) {
+         AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, inPropertyID, propertyProc );
+         return paNoError;
+      }
+      /* No match yet, so let's sleep and try again. */
+      Pa_Sleep( 100 );
+      gettimeofday( &tv2, NULL );
+   }
+   DBUG( ("Timeout waiting for device setting.\n" ) );
+   
+   AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, inPropertyID, propertyProc );
    return paNoError;
 
- failUnix:
-   pthread_mutex_destroy( &mab.mutex );
-   AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput,
-                                      inPropertyID, propertyProc );
-
- failUnix2:
-   DBUG( ("Error #%d while setting a device property: %s\n", unixErr, strerror( unixErr ) ) );
-   return paUnanticipatedHostError;
-
  failMac:
-   pthread_mutex_destroy( &mab.mutex );
-   AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput,
-                                      inPropertyID, propertyProc );
- failMac2:
+   AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, inPropertyID, propertyProc );
    return ERR( macErr );
 }
 
@@ -427,10 +420,6 @@ PaError setBestSampleRateForDevice( const AudioDeviceID device,
                                     const bool requireExact,
                                     const Float64 desiredSrate )
 {
-   /*FIXME: changing the sample rate is disruptive to other programs using the
-            device, so it might be good to offer a custom flag to not change the
-            sample rate and just do conversion. (in my casual tests, there is
-            no disruption unless the sample rate really does need to change) */
    const bool isInput = isOutput ? 0 : 1;
    Float64 srate;
    UInt32 propsize = sizeof( Float64 );
@@ -442,13 +431,15 @@ PaError setBestSampleRateForDevice( const AudioDeviceID device,
    VDBUG(("Setting sample rate for device %ld to %g.\n",device,(float)desiredSrate));
 
    /* -- try setting the sample rate -- */
+   srate = 0;
    err = AudioDeviceSetPropertyNowAndWaitForChange(
                                  device, 0, isInput,
                                  kAudioDevicePropertyNominalSampleRate,
                                  propsize, &desiredSrate, &srate );
-   if( err )
-      return err;
 
+   /* -- if the rate agrees, and was changed, we are done -- */
+   if( srate != 0 && srate == desiredSrate )
+      return paNoError;
    /* -- if the rate agrees, and we got no errors, we are done -- */
    if( !err && srate == desiredSrate )
       return paNoError;
@@ -501,19 +492,19 @@ PaError setBestSampleRateForDevice( const AudioDeviceID device,
 
    /* -- set the sample rate -- */
    propsize = sizeof( best );
+   srate = 0;
    err = AudioDeviceSetPropertyNowAndWaitForChange(
                                  device, 0, isInput,
                                  kAudioDevicePropertyNominalSampleRate,
                                  propsize, &best, &srate );
-   if( err )
-      return err;
 
-   if( err )
-      return ERR( err );
    /* -- if the set rate matches, we are done -- */
-   if( srate == best )
+   if( srate != 0 && srate == best )
       return paNoError;
 
+   if( err )
+      return ERR( err );
+
    /* -- otherwise, something wierd happened: we didn't set the rate, and we got no errors. Just bail. */
    return paInternalError;
 }
@@ -533,84 +524,60 @@ PaError setBestFramesPerBuffer( const AudioDeviceID device,
                                 UInt32 requestedFramesPerBuffer, 
                                 UInt32 *actualFramesPerBuffer )
 {
-   UInt32 afpb;
-   const bool isInput = !isOutput;
-   UInt32 propsize = sizeof(UInt32);
-   OSErr err;
-   Float64 min  = -1; /*the min blocksize*/
-   Float64 best = -1; /*the best blocksize*/
-   int i=0;
-   AudioValueRange *ranges;
-
-   if( actualFramesPerBuffer == NULL )
-      actualFramesPerBuffer = &afpb;
+    UInt32 afpb;
+    const bool isInput = !isOutput;
+    UInt32 propsize = sizeof(UInt32);
+    OSErr err;
+    AudioValueRange range;
 
+    if( actualFramesPerBuffer == NULL )
+    {
+        actualFramesPerBuffer = &afpb;
+    }
 
-   /* -- try and set exact FPB -- */
-   err = AudioDeviceSetProperty( device, NULL, 0, isInput,
+    /* -- try and set exact FPB -- */
+    err = AudioDeviceSetProperty( device, NULL, 0, isInput,
                                  kAudioDevicePropertyBufferFrameSize,
                                  propsize, &requestedFramesPerBuffer);
-   err = AudioDeviceGetProperty( device, 0, isInput,
+    err = AudioDeviceGetProperty( device, 0, isInput,
                            kAudioDevicePropertyBufferFrameSize,
                            &propsize, actualFramesPerBuffer);
-   if( err )
-      return ERR( err );
-   if( *actualFramesPerBuffer == requestedFramesPerBuffer )
-      return paNoError; /* we are done */
-
-   /* -- fetch available block sizes -- */
-   err = AudioDeviceGetPropertyInfo( device, 0, isInput,
-                           kAudioDevicePropertyBufferSizeRange,
-                           &propsize, NULL );
-   if( err )
-      return ERR( err );
-   ranges = (AudioValueRange *)calloc( 1, propsize );
-   if( !ranges )
-      return paInsufficientMemory;
-   err = AudioDeviceGetProperty( device, 0, isInput,
-                                kAudioDevicePropertyBufferSizeRange,
-                                &propsize, ranges );
-   if( err )
-   {
-      free( ranges );
+    if( err )
+    {
+        return ERR( err );
+    }
+    // Did we get the size we asked for?
+    if( *actualFramesPerBuffer == requestedFramesPerBuffer )
+    {
+        return paNoError; /* we are done */
+    }
+    
+    // Clip requested value against legal range for the device.
+    propsize = sizeof(AudioValueRange);
+    err = AudioDeviceGetProperty( device, 0, isInput,
+                                kAudioDevicePropertyBufferFrameSizeRange,
+                                &propsize, &range );
+    if( err )
+    {
       return ERR( err );
-   }
-   VDBUG(("Requested block size of %lu was not available.\n",
-          requestedFramesPerBuffer ));
-   VDBUG(("%lu Available block sizes are:\n",propsize/sizeof(AudioValueRange)));
-#ifdef MAC_CORE_VERBOSE_DEBUG
-   for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
-      VDBUG( ("\t%g-%g\n",
-              (float) ranges[i].mMinimum,
-              (float) ranges[i].mMaximum ) );
-#endif
-   VDBUG(("-----\n"));
-   
-   /* --- now pick the best available framesPerBuffer -- */
-   for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
-   {
-      if( min == -1 || ranges[i].mMinimum < min ) min = ranges[i].mMinimum;
-      if( ranges[i].mMaximum < requestedFramesPerBuffer ) {
-         if( best < 0 )
-            best = ranges[i].mMaximum;
-         else if( ranges[i].mMaximum > best )
-            best = ranges[i].mMaximum;
-      }
-   }
-   if( best == -1 )
-      best = min;
-   VDBUG( ("Minimum FPB  %g. best is %g.\n", min, best ) );
-   free( ranges );
-
+    }
+    if( requestedFramesPerBuffer < range.mMinimum )
+    {
+        requestedFramesPerBuffer = range.mMinimum;
+    }
+    else if( requestedFramesPerBuffer > range.mMaximum )
+    {
+        requestedFramesPerBuffer = range.mMaximum;
+    }
+    
    /* --- set the buffer size (ignore errors) -- */
-   requestedFramesPerBuffer = (UInt32) best ;
-   propsize = sizeof( UInt32 );
+    propsize = sizeof( UInt32 );
    err = AudioDeviceSetProperty( device, NULL, 0, isInput,
-                                 kAudioDevicePropertyBufferSize,
+                                 kAudioDevicePropertyBufferFrameSize,
                                  propsize, &requestedFramesPerBuffer );
    /* --- read the property to check that it was set -- */
    err = AudioDeviceGetProperty( device, 0, isInput,
-                                 kAudioDevicePropertyBufferSize,
+                                 kAudioDevicePropertyBufferFrameSize,
                                  &propsize, actualFramesPerBuffer );
 
    if( err )
@@ -618,3 +585,117 @@ PaError setBestFramesPerBuffer( const AudioDeviceID device,
 
    return paNoError;
 }
+
+/**********************
+ *
+ * XRun stuff
+ *
+ **********************/
+
+struct PaMacXRunListNode_s {
+   PaMacCoreStream *stream;
+   struct PaMacXRunListNode_s *next;
+} ;
+
+typedef struct PaMacXRunListNode_s PaMacXRunListNode;
+
+/** Always empty, so that it can always be the one returned by
+    addToXRunListenerList. note that it's not a pointer. */
+static PaMacXRunListNode firstXRunListNode;
+static int xRunListSize;
+static pthread_mutex_t xrunMutex;
+
+OSStatus xrunCallback(
+    AudioDeviceID inDevice, 
+    UInt32 inChannel, 
+    Boolean isInput, 
+    AudioDevicePropertyID inPropertyID, 
+    void* inClientData)
+{
+   PaMacXRunListNode *node = (PaMacXRunListNode *) inClientData;
+
+   int ret = pthread_mutex_trylock( &xrunMutex ) ;
+
+   if( ret == 0 ) {
+
+      node = node->next ; //skip the first node
+
+      for( ; node; node=node->next ) {
+         PaMacCoreStream *stream = node->stream;
+
+         if( stream->state != ACTIVE )
+            continue; //if the stream isn't active, we don't care if the device is dropping
+
+         if( isInput ) {
+            if( stream->inputDevice == inDevice )
+               OSAtomicOr32( paInputOverflow, &stream->xrunFlags );
+         } else {
+            if( stream->outputDevice == inDevice )
+               OSAtomicOr32( paOutputUnderflow, &stream->xrunFlags );
+         }
+      }
+
+      pthread_mutex_unlock( &xrunMutex );
+   }
+
+   return 0;
+}
+
+int initializeXRunListenerList()
+{
+   xRunListSize = 0;
+   bzero( (void *) &firstXRunListNode, sizeof(firstXRunListNode) );
+   return pthread_mutex_init( &xrunMutex, NULL );
+}
+int destroyXRunListenerList()
+{
+   PaMacXRunListNode *node;
+   node = firstXRunListNode.next;
+   while( node ) {
+      PaMacXRunListNode *tmp = node;
+      node = node->next;
+      free( tmp );
+   }
+   xRunListSize = 0;
+   return pthread_mutex_destroy( &xrunMutex );
+}
+
+void *addToXRunListenerList( void *stream )
+{
+   pthread_mutex_lock( &xrunMutex );
+   PaMacXRunListNode *newNode;
+   // setup new node:
+   newNode = (PaMacXRunListNode *) malloc( sizeof( PaMacXRunListNode ) );
+   newNode->stream = (PaMacCoreStream *) stream;
+   newNode->next = firstXRunListNode.next;
+   // insert:
+   firstXRunListNode.next = newNode;
+   pthread_mutex_unlock( &xrunMutex );
+
+   return &firstXRunListNode;
+}
+
+int removeFromXRunListenerList( void *stream )
+{
+   pthread_mutex_lock( &xrunMutex );
+   PaMacXRunListNode *node, *prev;
+   prev = &firstXRunListNode;
+   node = firstXRunListNode.next;
+   while( node ) {
+      if( node->stream == stream ) {
+         //found it:
+         --xRunListSize;
+         prev->next = node->next;
+         free( node );
+         pthread_mutex_unlock( &xrunMutex );
+         return xRunListSize;
+      }
+      prev = prev->next;
+      node = node->next;
+   }
+
+   pthread_mutex_unlock( &xrunMutex );
+   // failure
+   return xRunListSize;
+}
+
diff --git a/external/portaudio/pa_mac_core_utilities.h b/external/portaudio/pa_mac_core_utilities.h
index 0b72b26..7c4afe5 100644
--- a/external/portaudio/pa_mac_core_utilities.h
+++ b/external/portaudio/pa_mac_core_utilities.h
@@ -143,18 +143,6 @@ long computeRingBufferSize( const PaStreamParameters *inputParameters,
                                    long outputFramesPerBuffer,
                                    double sampleRate );
 
-/*
- * Durring testing of core audio, I found that serious crashes could occur
- * if properties such as sample rate were changed multiple times in rapid
- * succession. The function below has some fancy logic to make sure that changes
- * are acknowledged before another is requested. That seems to help a lot.
- */
-
-typedef struct {
-   bool once; /* I didn't end up using this. bdr */
-   pthread_mutex_t mutex;
-} MutexAndBool ;
-
 OSStatus propertyProc(
     AudioDeviceID inDevice, 
     UInt32 inChannel, 
@@ -202,4 +190,29 @@ PaError setBestFramesPerBuffer( const AudioDeviceID device,
                                 const bool isOutput,
                                 UInt32 requestedFramesPerBuffer, 
                                 UInt32 *actualFramesPerBuffer );
+
+
+/*********************
+ *
+ *  xrun handling
+ *
+ *********************/
+
+OSStatus xrunCallback(
+    AudioDeviceID inDevice, 
+    UInt32 inChannel, 
+    Boolean isInput, 
+    AudioDevicePropertyID inPropertyID, 
+    void* inClientData ) ;
+
+/** returns zero on success or a unix style error code. */
+int initializeXRunListenerList();
+/** returns zero on success or a unix style error code. */
+int destroyXRunListenerList();
+
+/**Returns the list, so that it can be passed to CorAudio.*/
+void *addToXRunListenerList( void *stream );
+/**Returns the number of Listeners in the list remaining.*/
+int removeFromXRunListenerList( void *stream );
+
 #endif /* PA_MAC_CORE_UTILITIES_H__*/
diff --git a/external/portaudio/pa_memorybarrier.h b/external/portaudio/pa_memorybarrier.h
new file mode 100644
index 0000000..2879ce3
--- /dev/null
+++ b/external/portaudio/pa_memorybarrier.h
@@ -0,0 +1,128 @@
+/*
+ * $Id: pa_memorybarrier.h 1240 2007-07-17 13:05:07Z bjornroche $
+ * Portable Audio I/O Library
+ * Memory barrier utilities
+ *
+ * Author: Bjorn Roche, XO Audio, LLC
+ *
+ * This program uses the PortAudio Portable Audio Library.
+ * For more information see: http://www.portaudio.com
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+/**
+ @file pa_memorybarrier.h
+ @ingroup common_src
+*/
+
+/****************
+ * Some memory barrier primitives based on the system.
+ * right now only OS X, FreeBSD, and Linux are supported. In addition to providing
+ * memory barriers, these functions should ensure that data cached in registers
+ * is written out to cache where it can be snooped by other CPUs. (ie, the volatile
+ * keyword should not be required)
+ *
+ * the primitives that must be defined are:
+ *
+ * PaUtil_FullMemoryBarrier()
+ * PaUtil_ReadMemoryBarrier()
+ * PaUtil_WriteMemoryBarrier()
+ *
+ ****************/
+
+#if defined(__APPLE__)
+#   include <libkern/OSAtomic.h>
+    /* Here are the memory barrier functions. Mac OS X only provides
+       full memory barriers, so the three types of barriers are the same,
+       however, these barriers are superior to compiler-based ones. */
+#   define PaUtil_FullMemoryBarrier()  OSMemoryBarrier()
+#   define PaUtil_ReadMemoryBarrier()  OSMemoryBarrier()
+#   define PaUtil_WriteMemoryBarrier() OSMemoryBarrier()
+#elif defined(__GNUC__)
+    /* GCC >= 4.1 has built-in intrinsics. We'll use those */
+#   if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
+#      define PaUtil_FullMemoryBarrier()  __sync_synchronize()
+#      define PaUtil_ReadMemoryBarrier()  __sync_synchronize()
+#      define PaUtil_WriteMemoryBarrier() __sync_synchronize()
+    /* as a fallback, GCC understands volatile asm and "memory" to mean it
+     * should not reorder memory read/writes */
+    /* Note that it is not clear that any compiler actually defines __PPC__,
+     * it can probably removed safely. */
+#   elif defined( __ppc__ ) || defined( __powerpc__) || defined( __PPC__ )
+#      define PaUtil_FullMemoryBarrier()  asm volatile("sync":::"memory")
+#      define PaUtil_ReadMemoryBarrier()  asm volatile("sync":::"memory")
+#      define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory")
+#   elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || \
+         defined( __i686__ ) || defined( __x86_64__ )
+#      define PaUtil_FullMemoryBarrier()  asm volatile("mfence":::"memory")
+#      define PaUtil_ReadMemoryBarrier()  asm volatile("lfence":::"memory")
+#      define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory")
+#   else
+#      ifdef ALLOW_SMP_DANGERS
+#         warning Memory barriers not defined on this system or system unknown
+#         warning For SMP safety, you should fix this.
+#         define PaUtil_FullMemoryBarrier()
+#         define PaUtil_ReadMemoryBarrier()
+#         define PaUtil_WriteMemoryBarrier()
+#      else
+#         error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed.
+#      endif
+#   endif
+#elif (_MSC_VER >= 1400) && !defined(_WIN32_WCE)
+#   include <intrin.h>
+#   pragma intrinsic(_ReadWriteBarrier)
+#   pragma intrinsic(_ReadBarrier)
+#   pragma intrinsic(_WriteBarrier)
+/* note that MSVC intrinsics _ReadWriteBarrier(), _ReadBarrier(), _WriteBarrier() are just compiler barriers *not* memory barriers */
+#   define PaUtil_FullMemoryBarrier()  _ReadWriteBarrier()
+#   define PaUtil_ReadMemoryBarrier()  _ReadBarrier()
+#   define PaUtil_WriteMemoryBarrier() _WriteBarrier()
+#elif defined(_WIN32_WCE)
+#   define PaUtil_FullMemoryBarrier()
+#   define PaUtil_ReadMemoryBarrier()
+#   define PaUtil_WriteMemoryBarrier()
+#elif defined(_MSC_VER) || defined(__BORLANDC__)
+#   define PaUtil_FullMemoryBarrier()  _asm { lock add    [esp], 0 }
+#   define PaUtil_ReadMemoryBarrier()  _asm { lock add    [esp], 0 }
+#   define PaUtil_WriteMemoryBarrier() _asm { lock add    [esp], 0 }
+#else
+#   ifdef ALLOW_SMP_DANGERS
+#      warning Memory barriers not defined on this system or system unknown
+#      warning For SMP safety, you should fix this.
+#      define PaUtil_FullMemoryBarrier()
+#      define PaUtil_ReadMemoryBarrier()
+#      define PaUtil_WriteMemoryBarrier()
+#   else
+#      error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed.
+#   endif
+#endif
diff --git a/external/portaudio/pa_process.c b/external/portaudio/pa_process.c
index 59bf434..f6052d1 100644
--- a/external/portaudio/pa_process.c
+++ b/external/portaudio/pa_process.c
@@ -1,5 +1,5 @@
 /*
- * $Id: pa_process.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_process.c 1913 2013-11-18 11:42:27Z gineera $
  * Portable Audio I/O Library
  * streamCallback <-> host buffer processing adapter
  *
@@ -41,43 +41,6 @@
  @ingroup common_src
 
  @brief Buffer Processor implementation.
-    
- The code in this file is not optimised yet - although it's not clear that
- it needs to be. there may appear to be redundancies
- that could be factored into common functions, but the redundanceis are left
- intentionally as each appearance may have different optimisation possibilities.
-
- The optimisations which are planned involve only converting data in-place
- where possible, rather than copying to the temp buffer(s).
-
- Note that in the extreme case of being able to convert in-place, and there
- being no conversion necessary there should be some code which short-circuits
- the operation.
-
-    @todo Consider cache tilings for intereave<->deinterleave.
-
-    @todo implement timeInfo->currentTime int PaUtil_BeginBufferProcessing()
-
-    @todo specify and implement some kind of logical policy for handling the
-        underflow and overflow stream flags when the underflow/overflow overlaps
-        multiple user buffers/callbacks.
-
-	@todo provide support for priming the buffers with data from the callback.
-        The client interface is now implemented through PaUtil_SetNoInput()
-        which sets bp->hostInputChannels[0][0].data to zero. However this is
-        currently only implemented in NonAdaptingProcess(). It shouldn't be
-        needed for AdaptingInputOnlyProcess() (no priming should ever be
-        requested for AdaptingInputOnlyProcess()).
-        Not sure if additional work should be required to make it work with
-        AdaptingOutputOnlyProcess, but it definitely is required for
-        AdaptingProcess.
-
-    @todo implement PaUtil_SetNoOutput for AdaptingProcess
-
-    @todo don't allocate temp buffers for blocking streams unless they are
-        needed. At the moment they are needed, but perhaps for host APIs
-        where the implementation passes a buffer to the host they could be
-        used.
 */
 
 
@@ -139,6 +102,7 @@ PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bp,
     PaError result = paNoError;
     PaError bytesPerSample;
     unsigned long tempInputBufferSize, tempOutputBufferSize;
+    PaStreamFlags tempInputStreamFlags;
 
     if( streamFlags & paNeverDropInput )
     {
@@ -259,13 +223,28 @@ PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bp,
             goto error;
         }
 
+        /* Under the assumption that no ADC in existence delivers better than 24bits resolution,
+            we disable dithering when host input format is paInt32 and user format is paInt24, 
+            since the host samples will just be padded with zeros anyway. */
+
+        tempInputStreamFlags = streamFlags;
+        if( !(tempInputStreamFlags & paDitherOff) /* dither is on */
+                && (hostInputSampleFormat & paInt32) /* host input format is int32 */
+                && (userInputSampleFormat & paInt24) /* user requested format is int24 */ ){
+
+            tempInputStreamFlags = tempInputStreamFlags | paDitherOff;
+        }
+
         bp->inputConverter =
-            PaUtil_SelectConverter( hostInputSampleFormat, userInputSampleFormat, streamFlags );
+            PaUtil_SelectConverter( hostInputSampleFormat, userInputSampleFormat, tempInputStreamFlags );
 
-        bp->inputZeroer = PaUtil_SelectZeroer( hostInputSampleFormat );
+        bp->inputZeroer = PaUtil_SelectZeroer( userInputSampleFormat );
             
         bp->userInputIsInterleaved = (userInputSampleFormat & paNonInterleaved)?0:1;
+		
+        bp->hostInputIsInterleaved = (hostInputSampleFormat & paNonInterleaved)?0:1;
 
+        bp->userInputSampleFormatIsEqualToHost = ((userInputSampleFormat & ~paNonInterleaved) == (hostInputSampleFormat & ~paNonInterleaved));
 
         tempInputBufferSize =
             bp->framesPerTempBuffer * bp->bytesPerUserInputSample * inputChannelCount;
@@ -333,6 +312,10 @@ PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bp,
 
         bp->userOutputIsInterleaved = (userOutputSampleFormat & paNonInterleaved)?0:1;
 
+        bp->hostOutputIsInterleaved = (hostOutputSampleFormat & paNonInterleaved)?0:1;
+
+        bp->userOutputSampleFormatIsEqualToHost = ((userOutputSampleFormat & ~paNonInterleaved) == (hostOutputSampleFormat & ~paNonInterleaved));
+
         tempOutputBufferSize =
                 bp->framesPerTempBuffer * bp->bytesPerUserOutputSample * outputChannelCount;
 
@@ -445,13 +428,13 @@ void PaUtil_ResetBufferProcessor( PaUtilBufferProcessor* bp )
 }
 
 
-unsigned long PaUtil_GetBufferProcessorInputLatency( PaUtilBufferProcessor* bp )
+unsigned long PaUtil_GetBufferProcessorInputLatencyFrames( PaUtilBufferProcessor* bp )
 {
     return bp->initialFramesInTempInputBuffer;
 }
 
 
-unsigned long PaUtil_GetBufferProcessorOutputLatency( PaUtilBufferProcessor* bp )
+unsigned long PaUtil_GetBufferProcessorOutputLatencyFrames( PaUtilBufferProcessor* bp )
 {
     return bp->initialFramesInTempOutputBuffer;
 }
@@ -497,6 +480,7 @@ void PaUtil_SetInterleavedInputChannels( PaUtilBufferProcessor* bp,
 
     assert( firstChannel < bp->inputChannelCount );
     assert( firstChannel + channelCount <= bp->inputChannelCount );
+    assert( bp->hostInputIsInterleaved );
 
     for( i=0; i< channelCount; ++i )
     {
@@ -511,6 +495,7 @@ void PaUtil_SetNonInterleavedInputChannel( PaUtilBufferProcessor* bp,
         unsigned int channel, void *data )
 {
     assert( channel < bp->inputChannelCount );
+    assert( !bp->hostInputIsInterleaved );
     
     bp->hostInputChannels[0][channel].data = data;
     bp->hostInputChannels[0][channel].stride = 1;
@@ -546,6 +531,7 @@ void PaUtil_Set2ndInterleavedInputChannels( PaUtilBufferProcessor* bp,
 
     assert( firstChannel < bp->inputChannelCount );
     assert( firstChannel + channelCount <= bp->inputChannelCount );
+    assert( bp->hostInputIsInterleaved );
     
     for( i=0; i< channelCount; ++i )
     {
@@ -560,6 +546,7 @@ void PaUtil_Set2ndNonInterleavedInputChannel( PaUtilBufferProcessor* bp,
         unsigned int channel, void *data )
 {
     assert( channel < bp->inputChannelCount );
+    assert( !bp->hostInputIsInterleaved );
     
     bp->hostInputChannels[1][channel].data = data;
     bp->hostInputChannels[1][channel].stride = 1;
@@ -581,6 +568,8 @@ void PaUtil_SetNoOutput( PaUtilBufferProcessor* bp )
     assert( bp->outputChannelCount > 0 );
 
     bp->hostOutputChannels[0][0].data = 0;
+
+    /* note that only NonAdaptingProcess is able to deal with no output at this stage. not implemented for AdaptingProcess */
 }
 
 
@@ -607,6 +596,7 @@ void PaUtil_SetInterleavedOutputChannels( PaUtilBufferProcessor* bp,
 
     assert( firstChannel < bp->outputChannelCount );
     assert( firstChannel + channelCount <= bp->outputChannelCount );
+    assert( bp->hostOutputIsInterleaved );
     
     for( i=0; i< channelCount; ++i )
     {
@@ -620,6 +610,7 @@ void PaUtil_SetNonInterleavedOutputChannel( PaUtilBufferProcessor* bp,
         unsigned int channel, void *data )
 {
     assert( channel < bp->outputChannelCount );
+    assert( !bp->hostOutputIsInterleaved );
 
     PaUtil_SetOutputChannel( bp, channel, data, 1 );
 }
@@ -655,6 +646,7 @@ void PaUtil_Set2ndInterleavedOutputChannels( PaUtilBufferProcessor* bp,
 
     assert( firstChannel < bp->outputChannelCount );
     assert( firstChannel + channelCount <= bp->outputChannelCount );
+    assert( bp->hostOutputIsInterleaved );
     
     for( i=0; i< channelCount; ++i )
     {
@@ -668,6 +660,7 @@ void PaUtil_Set2ndNonInterleavedOutputChannel( PaUtilBufferProcessor* bp,
         unsigned int channel, void *data )
 {
     assert( channel < bp->outputChannelCount );
+    assert( !bp->hostOutputIsInterleaved );
     
     PaUtil_Set2ndOutputChannel( bp, channel, data, 1 );
 }
@@ -683,7 +676,9 @@ void PaUtil_BeginBufferProcessing( PaUtilBufferProcessor* bp,
         
     bp->timeInfo->inputBufferAdcTime -= bp->framesInTempInputBuffer * bp->samplePeriod;
     
-    bp->timeInfo->currentTime = 0; /** FIXME: @todo time info currentTime not implemented */
+    /* We just pass through timeInfo->currentTime provided by the caller. This is
+        not strictly conformant to the word of the spec, since the buffer processor 
+        might call the callback multiple times, and we never refresh currentTime. */
 
     /* the first streamCallback will be called to generate samples which will be
         outputted after the frames currently in the output buffer have been
@@ -722,6 +717,8 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp,
     unsigned long frameCount;
     unsigned long framesToGo = framesToProcess;
     unsigned long framesProcessed = 0;
+    int skipOutputConvert = 0;
+    int skipInputConvert = 0;
 
 
     if( *streamCallbackResult == paContinue )
@@ -738,18 +735,27 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp,
             }
             else /* there are input channels */
             {
-                /*
-                    could use more elaborate logic here and sometimes process
-                    buffers in-place.
-                */
-            
+                
                 destBytePtr = (unsigned char *)bp->tempInputBuffer;
 
                 if( bp->userInputIsInterleaved )
                 {
                     destSampleStrideSamples = bp->inputChannelCount;
                     destChannelStrideBytes = bp->bytesPerUserInputSample;
-                    userInput = bp->tempInputBuffer;
+
+                    /* process host buffer directly, or use temp buffer if formats differ or host buffer non-interleaved,
+                     * or if the number of channels differs between the host (set in stride) and the user */
+                    if( bp->userInputSampleFormatIsEqualToHost && bp->hostInputIsInterleaved
+                        && bp->hostInputChannels[0][0].data && bp->inputChannelCount == hostInputChannels[0].stride )
+                    {
+                        userInput = hostInputChannels[0].data;
+                        destBytePtr = (unsigned char *)hostInputChannels[0].data;
+                        skipInputConvert = 1;
+                    }
+                    else
+                    {
+                        userInput = bp->tempInputBuffer;
+                    }
                 }
                 else /* user input is not interleaved */
                 {
@@ -757,10 +763,21 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp,
                     destChannelStrideBytes = frameCount * bp->bytesPerUserInputSample;
 
                     /* setup non-interleaved ptrs */
-                    for( i=0; i<bp->inputChannelCount; ++i )
+                    if( bp->userInputSampleFormatIsEqualToHost && !bp->hostInputIsInterleaved && bp->hostInputChannels[0][0].data )
                     {
-                        bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) +
-                            i * bp->bytesPerUserInputSample * frameCount;
+                        for( i=0; i<bp->inputChannelCount; ++i )
+                        {
+                            bp->tempInputBufferPtrs[i] = hostInputChannels[i].data;
+                        }
+                        skipInputConvert = 1;
+                    }
+                    else
+                    {
+                        for( i=0; i<bp->inputChannelCount; ++i )
+                        {
+                            bp->tempInputBufferPtrs[i] = ((unsigned char*)bp->tempInputBuffer) +
+                                i * bp->bytesPerUserInputSample * frameCount;
+                        }
                     }
                 
                     userInput = bp->tempInputBufferPtrs;
@@ -778,19 +795,31 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp,
                     }
                 }
                 else
-                {
-                    for( i=0; i<bp->inputChannelCount; ++i )
+	            {
+                    if( skipInputConvert )
                     {
-                        bp->inputConverter( destBytePtr, destSampleStrideSamples,
-                                                hostInputChannels[i].data,
-                                                hostInputChannels[i].stride,
-                                                frameCount, &bp->ditherGenerator );
-
-                        destBytePtr += destChannelStrideBytes;  /* skip to next destination channel */
-
-                        /* advance src ptr for next iteration */
-                        hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) +
-                                frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample;
+                        for( i=0; i<bp->inputChannelCount; ++i )
+                        {
+                            /* advance src ptr for next iteration */
+                            hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) +
+                                    frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample;
+                        }
+                    }
+                    else
+                    {
+                        for( i=0; i<bp->inputChannelCount; ++i )
+                        {
+                            bp->inputConverter( destBytePtr, destSampleStrideSamples,
+                                                    hostInputChannels[i].data,
+                                                    hostInputChannels[i].stride,
+                                                    frameCount, &bp->ditherGenerator );
+
+                            destBytePtr += destChannelStrideBytes;  /* skip to next destination channel */
+
+                            /* advance src ptr for next iteration */
+                            hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) +
+                                    frameCount * hostInputChannels[i].stride * bp->bytesPerHostInputSample;
+                        }
                     }
                 }
             }
@@ -805,21 +834,41 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp,
             {
                 if( bp->userOutputIsInterleaved )
                 {
-                    userOutput = bp->tempOutputBuffer;
+                    /* process host buffer directly, or use temp buffer if formats differ or host buffer non-interleaved */
+                    if( bp->userOutputSampleFormatIsEqualToHost && bp->hostOutputIsInterleaved )
+                    {
+                        userOutput = hostOutputChannels[0].data;
+                        skipOutputConvert = 1;
+                    }
+                    else
+                    {
+                        userOutput = bp->tempOutputBuffer;
+                    }
                 }
                 else /* user output is not interleaved */
                 {
-                    for( i = 0; i < bp->outputChannelCount; ++i )
+                    if( bp->userOutputSampleFormatIsEqualToHost && !bp->hostOutputIsInterleaved )
                     {
-                        bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) +
-                            i * bp->bytesPerUserOutputSample * frameCount;
+                        for( i=0; i<bp->outputChannelCount; ++i )
+                        {
+                            bp->tempOutputBufferPtrs[i] = hostOutputChannels[i].data;
+                        }
+                        skipOutputConvert = 1;
+                    }
+                    else
+                    {
+                        for( i=0; i<bp->outputChannelCount; ++i )
+                        {
+                            bp->tempOutputBufferPtrs[i] = ((unsigned char*)bp->tempOutputBuffer) +
+                                i * bp->bytesPerUserOutputSample * frameCount;
+                        }
                     }
 
                     userOutput = bp->tempOutputBufferPtrs;
                 }
             }
- 
-			           *streamCallbackResult = bp->streamCallback( userInput, userOutput,
+        
+            *streamCallbackResult = bp->streamCallback( userInput, userOutput,
                     frameCount, bp->timeInfo, bp->callbackStatusFlags, bp->userData );
 
             if( *streamCallbackResult == paAbort )
@@ -836,37 +885,45 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp,
                 
                 if( bp->outputChannelCount != 0 && bp->hostOutputChannels[0][0].data )
                 {
-                    /*
-                        could use more elaborate logic here and sometimes process
-                        buffers in-place.
-                    */
-            
-                    srcBytePtr = (unsigned char *)bp->tempOutputBuffer;
-
-                    if( bp->userOutputIsInterleaved )
-                    {
-                        srcSampleStrideSamples = bp->outputChannelCount;
-                        srcChannelStrideBytes = bp->bytesPerUserOutputSample;
-                    }
-                    else /* user output is not interleaved */
-                    {
-                        srcSampleStrideSamples = 1;
-                        srcChannelStrideBytes = frameCount * bp->bytesPerUserOutputSample;
-                    }
-
-                    for( i=0; i<bp->outputChannelCount; ++i )
-                    {
-                        bp->outputConverter(    hostOutputChannels[i].data,
-                                                hostOutputChannels[i].stride,
-                                                srcBytePtr, srcSampleStrideSamples,
-                                                frameCount, &bp->ditherGenerator );
-
-                        srcBytePtr += srcChannelStrideBytes;  /* skip to next source channel */
-
-                        /* advance dest ptr for next iteration */
-                        hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) +
-                                frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample;
-                    }
+                    if( skipOutputConvert )
+					{
+						for( i=0; i<bp->outputChannelCount; ++i )
+                    	{
+                        	/* advance dest ptr for next iteration */
+                        	hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) +
+                            	    frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample;
+                    	}
+					}
+					else
+					{
+
+                    	srcBytePtr = (unsigned char *)bp->tempOutputBuffer;
+
+                    	if( bp->userOutputIsInterleaved )
+                    	{
+                        	srcSampleStrideSamples = bp->outputChannelCount;
+                        	srcChannelStrideBytes = bp->bytesPerUserOutputSample;
+                    	}
+                    	else /* user output is not interleaved */
+                    	{
+                        	srcSampleStrideSamples = 1;
+                        	srcChannelStrideBytes = frameCount * bp->bytesPerUserOutputSample;
+                    	}
+
+                    	for( i=0; i<bp->outputChannelCount; ++i )
+                    	{
+                        	bp->outputConverter(    hostOutputChannels[i].data,
+                                                	hostOutputChannels[i].stride,
+                                                	srcBytePtr, srcSampleStrideSamples,
+                                                	frameCount, &bp->ditherGenerator );
+
+                        	srcBytePtr += srcChannelStrideBytes;  /* skip to next source channel */
+
+                        	/* advance dest ptr for next iteration */
+                        	hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) +
+                                		frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample;
+                    	}
+					}
                 }
              
                 framesProcessed += frameCount;
@@ -997,7 +1054,7 @@ static unsigned long AdaptingInputOnlyProcess( PaUtilBufferProcessor *bp,
                         bp->framesPerUserBuffer, bp->timeInfo,
                         bp->callbackStatusFlags, bp->userData );
 
-                bp->timeInfo->inputBufferAdcTime += frameCount * bp->samplePeriod;
+                bp->timeInfo->inputBufferAdcTime += bp->framesPerUserBuffer * bp->samplePeriod;
             }
             
             bp->framesInTempInputBuffer = 0;
@@ -1628,9 +1685,9 @@ unsigned long PaUtil_CopyInput( PaUtilBufferProcessor* bp,
                                 hostInputChannels[i].stride,
                                 framesToCopy, &bp->ditherGenerator );
 
-            destBytePtr += destChannelStrideBytes;  /* skip to next source channel */
+            destBytePtr += destChannelStrideBytes;  /* skip to next dest channel */
 
-            /* advance dest ptr for next iteration */
+            /* advance source ptr for next iteration */
             hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) +
                     framesToCopy * hostInputChannels[i].stride * bp->bytesPerHostInputSample;
         }
@@ -1660,7 +1717,7 @@ unsigned long PaUtil_CopyInput( PaUtilBufferProcessor* bp,
             destBytePtr += bp->bytesPerUserInputSample * framesToCopy;
             nonInterleavedDestPtrs[i] = destBytePtr;
             
-            /* advance dest ptr for next iteration */
+            /* advance source ptr for next iteration */
             hostInputChannels[i].data = ((unsigned char*)hostInputChannels[i].data) +
                     framesToCopy * hostInputChannels[i].stride * bp->bytesPerHostInputSample;
         }
diff --git a/external/portaudio/pa_process.h b/external/portaudio/pa_process.h
index 89584bc..4d5f56a 100644
--- a/external/portaudio/pa_process.h
+++ b/external/portaudio/pa_process.h
@@ -1,7 +1,7 @@
 #ifndef PA_PROCESS_H
 #define PA_PROCESS_H
 /*
- * $Id: pa_process.h 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_process.h 1668 2011-05-02 17:07:11Z rossb $
  * Portable Audio I/O Library callback buffer processing adapters
  *
  * Based on the Open Source API proposed by Ross Bencina
@@ -256,6 +256,8 @@ typedef struct {
 
     PaUtilHostBufferSizeMode hostBufferSizeMode;
     int useNonAdaptingProcess;
+    int userOutputSampleFormatIsEqualToHost;
+    int userInputSampleFormatIsEqualToHost;
     unsigned long framesPerTempBuffer;
 
     unsigned int inputChannelCount;
@@ -287,12 +289,14 @@ typedef struct {
 
     PaStreamCallbackFlags callbackStatusFlags;
 
+    int hostInputIsInterleaved;
     unsigned long hostInputFrameCount[2];
     PaUtilChannelDescriptor *hostInputChannels[2]; /**< pointers to arrays of channel descriptors.
                                                         pointers are NULL for half-duplex output processing.
                                                         hostInputChannels[i].data is NULL when the caller
                                                         calls PaUtil_SetNoInput()
                                                         */
+    int hostOutputIsInterleaved;
     unsigned long hostOutputFrameCount[2];
     PaUtilChannelDescriptor *hostOutputChannels[2]; /**< pointers to arrays of channel descriptors.
                                                          pointers are NULL for half-duplex input processing.
@@ -402,25 +406,25 @@ void PaUtil_TerminateBufferProcessor( PaUtilBufferProcessor* bufferProcessor );
 void PaUtil_ResetBufferProcessor( PaUtilBufferProcessor* bufferProcessor );
 
 
-/** Retrieve the input latency of a buffer processor.
+/** Retrieve the input latency of a buffer processor, in frames.
 
  @param bufferProcessor The buffer processor examine.
 
  @return The input latency introduced by the buffer processor, in frames.
 
- @see PaUtil_GetBufferProcessorOutputLatency
+ @see PaUtil_GetBufferProcessorOutputLatencyFrames
 */
-unsigned long PaUtil_GetBufferProcessorInputLatency( PaUtilBufferProcessor* bufferProcessor );
+unsigned long PaUtil_GetBufferProcessorInputLatencyFrames( PaUtilBufferProcessor* bufferProcessor );
 
-/** Retrieve the output latency of a buffer processor.
+/** Retrieve the output latency of a buffer processor, in frames.
 
  @param bufferProcessor The buffer processor examine.
 
  @return The output latency introduced by the buffer processor, in frames.
 
- @see PaUtil_GetBufferProcessorInputLatency
+ @see PaUtil_GetBufferProcessorInputLatencyFrames
 */
-unsigned long PaUtil_GetBufferProcessorOutputLatency( PaUtilBufferProcessor* bufferProcessor );
+unsigned long PaUtil_GetBufferProcessorOutputLatencyFrames( PaUtilBufferProcessor* bufferProcessor );
 
 /*@}*/
 
diff --git a/external/portaudio/pa_ringbuffer.c b/external/portaudio/pa_ringbuffer.c
index f4e1201..19c9149 100644
--- a/external/portaudio/pa_ringbuffer.c
+++ b/external/portaudio/pa_ringbuffer.c
@@ -1,5 +1,5 @@
 /*
- * $Id: pa_ringbuffer.c 1240 2007-07-17 13:05:07Z bjornroche $
+ * $Id: pa_ringbuffer.c 1738 2011-08-18 11:47:28Z rossb $
  * Portable Audio I/O Library
  * Ring Buffer utility.
  *
@@ -7,6 +7,8 @@
  * modified for SMP safety on Mac OS X by Bjorn Roche
  * modified for SMP safety on Linux by Leland Lucius
  * also, allowed for const where possible
+ * modified for multiple-byte-sized data elements by Sven Fischer 
+ *
  * Note that this is safe only for a single-thread reader and a
  * single-thread writer.
  *
@@ -55,101 +57,39 @@
 #include <math.h>
 #include "pa_ringbuffer.h"
 #include <string.h>
-
-/****************
- * First, we'll define some memory barrier primitives based on the system.
- * right now only OS X, FreeBSD, and Linux are supported. In addition to providing
- * memory barriers, these functions should ensure that data cached in registers
- * is written out to cache where it can be snooped by other CPUs. (ie, the volatile
- * keyword should not be required)
- *
- * the primitives that must be defined are:
- *
- * PaUtil_FullMemoryBarrier()
- * PaUtil_ReadMemoryBarrier()
- * PaUtil_WriteMemoryBarrier()
- *
- ****************/
-
-#if defined(__APPLE__)
-#   include <libkern/OSAtomic.h>
-    /* Here are the memory barrier functions. Mac OS X only provides
-       full memory barriers, so the three types of barriers are the same,
-       however, these barriers are superior to compiler-based ones. */
-#   define PaUtil_FullMemoryBarrier()  OSMemoryBarrier()
-#   define PaUtil_ReadMemoryBarrier()  OSMemoryBarrier()
-#   define PaUtil_WriteMemoryBarrier() OSMemoryBarrier()
-#elif defined(__GNUC__)
-    /* GCC >= 4.1 has built-in intrinsics. We'll use those */
-#   if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
-#      define PaUtil_FullMemoryBarrier()  __sync_synchronize()
-#      define PaUtil_ReadMemoryBarrier()  __sync_synchronize()
-#      define PaUtil_WriteMemoryBarrier() __sync_synchronize()
-    /* as a fallback, GCC understands volatile asm and "memory" to mean it
-     * should not reorder memory read/writes */
-#   elif defined( __PPC__ )
-#      define PaUtil_FullMemoryBarrier()  asm volatile("sync":::"memory")
-#      define PaUtil_ReadMemoryBarrier()  asm volatile("sync":::"memory")
-#      define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory")
-#   elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || defined( __i686__ ) || defined( __x86_64__ )
-#      define PaUtil_FullMemoryBarrier()  asm volatile("mfence":::"memory")
-#      define PaUtil_ReadMemoryBarrier()  asm volatile("lfence":::"memory")
-#      define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory")
-#   else
-#      ifdef ALLOW_SMP_DANGERS
-#         warning Memory barriers not defined on this system or system unknown
-#         warning For SMP safety, you should fix this.
-#         define PaUtil_FullMemoryBarrier()
-#         define PaUtil_ReadMemoryBarrier()
-#         define PaUtil_WriteMemoryBarrier()
-#      else
-#         error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed.
-#      endif
-#   endif
-#else
-#   ifdef ALLOW_SMP_DANGERS
-#      warning Memory barriers not defined on this system or system unknown
-#      warning For SMP safety, you should fix this.
-#      define PaUtil_FullMemoryBarrier()
-#      define PaUtil_ReadMemoryBarrier()
-#      define PaUtil_WriteMemoryBarrier()
-#   else
-#      error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed.
-#   endif
-#endif
+#include "pa_memorybarrier.h"
 
 /***************************************************************************
  * Initialize FIFO.
- * numBytes must be power of 2, returns -1 if not.
+ * elementCount must be power of 2, returns -1 if not.
  */
-long PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, long numBytes, void *dataPtr )
+ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr )
 {
-    if( ((numBytes-1) & numBytes) != 0) return -1; /* Not Power of two. */
-    rbuf->bufferSize = numBytes;
+    if( ((elementCount-1) & elementCount) != 0) return -1; /* Not Power of two. */
+    rbuf->bufferSize = elementCount;
     rbuf->buffer = (char *)dataPtr;
     PaUtil_FlushRingBuffer( rbuf );
-    rbuf->bigMask = (numBytes*2)-1;
-    rbuf->smallMask = (numBytes)-1;
+    rbuf->bigMask = (elementCount*2)-1;
+    rbuf->smallMask = (elementCount)-1;
+    rbuf->elementSizeBytes = elementSizeBytes;
     return 0;
 }
 
 /***************************************************************************
-** Return number of bytes available for reading. */
-long PaUtil_GetRingBufferReadAvailable( PaUtilRingBuffer *rbuf )
+** Return number of elements available for reading. */
+ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf )
 {
-    PaUtil_ReadMemoryBarrier();
     return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask );
 }
 /***************************************************************************
-** Return number of bytes available for writing. */
-long PaUtil_GetRingBufferWriteAvailable( PaUtilRingBuffer *rbuf )
+** Return number of elements available for writing. */
+ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf )
 {
-    /* Since we are calling PaUtil_GetRingBufferReadAvailable, we don't need an aditional MB */
     return ( rbuf->bufferSize - PaUtil_GetRingBufferReadAvailable(rbuf));
 }
 
 /***************************************************************************
-** Clear buffer. Should only be called when buffer is NOT being read. */
+** Clear buffer. Should only be called when buffer is NOT being read or written. */
 void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf )
 {
     rbuf->writeIndex = rbuf->readIndex = 0;
@@ -159,126 +99,138 @@ void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf )
 ** Get address of region(s) to which we can write data.
 ** If the region is contiguous, size2 will be zero.
 ** If non-contiguous, size2 will be the size of second region.
-** Returns room available to be written or numBytes, whichever is smaller.
+** Returns room available to be written or elementCount, whichever is smaller.
 */
-long PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, long numBytes,
-                                       void **dataPtr1, long *sizePtr1,
-                                       void **dataPtr2, long *sizePtr2 )
+ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
+                                       void **dataPtr1, ring_buffer_size_t *sizePtr1,
+                                       void **dataPtr2, ring_buffer_size_t *sizePtr2 )
 {
-    long   index;
-    long   available = PaUtil_GetRingBufferWriteAvailable( rbuf );
-    if( numBytes > available ) numBytes = available;
+    ring_buffer_size_t   index;
+    ring_buffer_size_t   available = PaUtil_GetRingBufferWriteAvailable( rbuf );
+    if( elementCount > available ) elementCount = available;
     /* Check to see if write is not contiguous. */
     index = rbuf->writeIndex & rbuf->smallMask;
-    if( (index + numBytes) > rbuf->bufferSize )
+    if( (index + elementCount) > rbuf->bufferSize )
     {
         /* Write data in two blocks that wrap the buffer. */
-        long   firstHalf = rbuf->bufferSize - index;
-        *dataPtr1 = &rbuf->buffer[index];
+        ring_buffer_size_t   firstHalf = rbuf->bufferSize - index;
+        *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
         *sizePtr1 = firstHalf;
         *dataPtr2 = &rbuf->buffer[0];
-        *sizePtr2 = numBytes - firstHalf;
+        *sizePtr2 = elementCount - firstHalf;
     }
     else
     {
-        *dataPtr1 = &rbuf->buffer[index];
-        *sizePtr1 = numBytes;
+        *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
+        *sizePtr1 = elementCount;
         *dataPtr2 = NULL;
         *sizePtr2 = 0;
     }
-    return numBytes;
+
+    if( available )
+        PaUtil_FullMemoryBarrier(); /* (write-after-read) => full barrier */
+
+    return elementCount;
 }
 
 
 /***************************************************************************
 */
-long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long numBytes )
+ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount )
 {
-    /* we need to ensure that previous writes are seen before we update the write index */
+    /* ensure that previous writes are seen before we update the write index 
+       (write after write)
+    */
     PaUtil_WriteMemoryBarrier();
-    return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask;
+    return rbuf->writeIndex = (rbuf->writeIndex + elementCount) & rbuf->bigMask;
 }
 
 /***************************************************************************
 ** Get address of region(s) from which we can read data.
 ** If the region is contiguous, size2 will be zero.
 ** If non-contiguous, size2 will be the size of second region.
-** Returns room available to be written or numBytes, whichever is smaller.
+** Returns room available to be read or elementCount, whichever is smaller.
 */
-long PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, long numBytes,
-                                void **dataPtr1, long *sizePtr1,
-                                void **dataPtr2, long *sizePtr2 )
+ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
+                                void **dataPtr1, ring_buffer_size_t *sizePtr1,
+                                void **dataPtr2, ring_buffer_size_t *sizePtr2 )
 {
-    long   index;
-    long   available = PaUtil_GetRingBufferReadAvailable( rbuf );
-    if( numBytes > available ) numBytes = available;
+    ring_buffer_size_t   index;
+    ring_buffer_size_t   available = PaUtil_GetRingBufferReadAvailable( rbuf ); /* doesn't use memory barrier */
+    if( elementCount > available ) elementCount = available;
     /* Check to see if read is not contiguous. */
     index = rbuf->readIndex & rbuf->smallMask;
-    if( (index + numBytes) > rbuf->bufferSize )
+    if( (index + elementCount) > rbuf->bufferSize )
     {
         /* Write data in two blocks that wrap the buffer. */
-        long firstHalf = rbuf->bufferSize - index;
-        *dataPtr1 = &rbuf->buffer[index];
+        ring_buffer_size_t firstHalf = rbuf->bufferSize - index;
+        *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
         *sizePtr1 = firstHalf;
         *dataPtr2 = &rbuf->buffer[0];
-        *sizePtr2 = numBytes - firstHalf;
+        *sizePtr2 = elementCount - firstHalf;
     }
     else
     {
-        *dataPtr1 = &rbuf->buffer[index];
-        *sizePtr1 = numBytes;
+        *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
+        *sizePtr1 = elementCount;
         *dataPtr2 = NULL;
         *sizePtr2 = 0;
     }
-    return numBytes;
+    
+    if( available )
+        PaUtil_ReadMemoryBarrier(); /* (read-after-read) => read barrier */
+
+    return elementCount;
 }
 /***************************************************************************
 */
-long PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, long numBytes )
+ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount )
 {
-    /* we need to ensure that previous writes are always seen before updating the index. */
-    PaUtil_WriteMemoryBarrier();
-    return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask;
+    /* ensure that previous reads (copies out of the ring buffer) are always completed before updating (writing) the read index. 
+       (write-after-read) => full barrier
+    */
+    PaUtil_FullMemoryBarrier();
+    return rbuf->readIndex = (rbuf->readIndex + elementCount) & rbuf->bigMask;
 }
 
 /***************************************************************************
-** Return bytes written. */
-long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long numBytes )
+** Return elements written. */
+ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount )
 {
-    long size1, size2, numWritten;
+    ring_buffer_size_t size1, size2, numWritten;
     void *data1, *data2;
-    numWritten = PaUtil_GetRingBufferWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 );
+    numWritten = PaUtil_GetRingBufferWriteRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 );
     if( size2 > 0 )
     {
 
-        memcpy( data1, data, size1 );
-        data = ((char *)data) + size1;
-        memcpy( data2, data, size2 );
+        memcpy( data1, data, size1*rbuf->elementSizeBytes );
+        data = ((char *)data) + size1*rbuf->elementSizeBytes;
+        memcpy( data2, data, size2*rbuf->elementSizeBytes );
     }
     else
     {
-        memcpy( data1, data, size1 );
+        memcpy( data1, data, size1*rbuf->elementSizeBytes );
     }
     PaUtil_AdvanceRingBufferWriteIndex( rbuf, numWritten );
     return numWritten;
 }
 
 /***************************************************************************
-** Return bytes read. */
-long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long numBytes )
+** Return elements read. */
+ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount )
 {
-    long size1, size2, numRead;
+    ring_buffer_size_t size1, size2, numRead;
     void *data1, *data2;
-    numRead = PaUtil_GetRingBufferReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 );
+    numRead = PaUtil_GetRingBufferReadRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 );
     if( size2 > 0 )
     {
-        memcpy( data, data1, size1 );
-        data = ((char *)data) + size1;
-        memcpy( data, data2, size2 );
+        memcpy( data, data1, size1*rbuf->elementSizeBytes );
+        data = ((char *)data) + size1*rbuf->elementSizeBytes;
+        memcpy( data, data2, size2*rbuf->elementSizeBytes );
     }
     else
     {
-        memcpy( data, data1, size1 );
+        memcpy( data, data1, size1*rbuf->elementSizeBytes );
     }
     PaUtil_AdvanceRingBufferReadIndex( rbuf, numRead );
     return numRead;
diff --git a/external/portaudio/pa_ringbuffer.h b/external/portaudio/pa_ringbuffer.h
index b380889..0cab3a5 100644
--- a/external/portaudio/pa_ringbuffer.h
+++ b/external/portaudio/pa_ringbuffer.h
@@ -1,13 +1,15 @@
 #ifndef PA_RINGBUFFER_H
 #define PA_RINGBUFFER_H
 /*
- * $Id: pa_ringbuffer.h 1151 2006-11-29 02:11:16Z leland_lucius $
+ * $Id: pa_ringbuffer.h 1873 2012-10-07 19:00:11Z philburk $
  * Portable Audio I/O Library
  * Ring Buffer utility.
  *
  * Author: Phil Burk, http://www.softsynth.com
  * modified for SMP safety on OS X by Bjorn Roche.
  * also allowed for const where possible.
+ * modified for multiple-byte-sized data elements by Sven Fischer 
+ *
  * Note that this is safe only for a single-thread reader
  * and a single-thread writer.
  *
@@ -48,8 +50,41 @@
 
 /** @file
  @ingroup common_src
+ @brief Single-reader single-writer lock-free ring buffer
+
+ PaUtilRingBuffer is a ring buffer used to transport samples between
+ different execution contexts (threads, OS callbacks, interrupt handlers)
+ without requiring the use of any locks. This only works when there is
+ a single reader and a single writer (ie. one thread or callback writes
+ to the ring buffer, another thread or callback reads from it).
+
+ The PaUtilRingBuffer structure manages a ring buffer containing N 
+ elements, where N must be a power of two. An element may be any size 
+ (specified in bytes).
+
+ The memory area used to store the buffer elements must be allocated by 
+ the client prior to calling PaUtil_InitializeRingBuffer() and must outlive
+ the use of the ring buffer.
+ 
+ @note The ring buffer functions are not normally exposed in the PortAudio libraries. 
+ If you want to call them then you will need to add pa_ringbuffer.c to your application source code.
 */
 
+#if defined(__APPLE__)
+#include <sys/types.h>
+typedef int32_t ring_buffer_size_t;
+#elif defined( __GNUC__ )
+typedef long ring_buffer_size_t;
+#elif (_MSC_VER >= 1400)
+typedef long ring_buffer_size_t;
+#elif defined(_MSC_VER) || defined(__BORLANDC__)
+typedef long ring_buffer_size_t;
+#else
+typedef long ring_buffer_size_t;
+#endif
+
+
+
 #ifdef __cplusplus
 extern "C"
 {
@@ -57,48 +92,51 @@ extern "C"
 
 typedef struct PaUtilRingBuffer
 {
-    long   bufferSize; /* Number of bytes in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */
-    long   writeIndex; /* Index of next writable byte. Set by PaUtil_AdvanceRingBufferWriteIndex. */
-    long   readIndex;  /* Index of next readable byte. Set by PaUtil_AdvanceRingBufferReadIndex. */
-    long   bigMask;    /* Used for wrapping indices with extra bit to distinguish full/empty. */
-    long   smallMask;  /* Used for fitting indices to buffer. */
-    char  *buffer;
+    ring_buffer_size_t  bufferSize; /**< Number of elements in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */
+    volatile ring_buffer_size_t  writeIndex; /**< Index of next writable element. Set by PaUtil_AdvanceRingBufferWriteIndex. */
+    volatile ring_buffer_size_t  readIndex;  /**< Index of next readable element. Set by PaUtil_AdvanceRingBufferReadIndex. */
+    ring_buffer_size_t  bigMask;    /**< Used for wrapping indices with extra bit to distinguish full/empty. */
+    ring_buffer_size_t  smallMask;  /**< Used for fitting indices to buffer. */
+    ring_buffer_size_t  elementSizeBytes; /**< Number of bytes per element. */
+    char  *buffer;    /**< Pointer to the buffer containing the actual data. */
 }PaUtilRingBuffer;
 
-/** Initialize Ring Buffer.
+/** Initialize Ring Buffer to empty state ready to have elements written to it.
 
  @param rbuf The ring buffer.
 
- @param numBytes The number of bytes in the buffer and must be power of 2.
+ @param elementSizeBytes The size of a single data element in bytes.
+
+ @param elementCount The number of elements in the buffer (must be a power of 2).
 
  @param dataPtr A pointer to a previously allocated area where the data
- will be maintained.  It must be numBytes long.
+ will be maintained.  It must be elementCount*elementSizeBytes long.
 
- @return -1 if numBytes is not a power of 2, otherwise 0.
+ @return -1 if elementCount is not a power of 2, otherwise 0.
 */
-long PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, long numBytes, void *dataPtr );
+ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr );
 
-/** Clear buffer. Should only be called when buffer is NOT being read.
+/** Reset buffer to empty. Should only be called when buffer is NOT being read or written.
 
  @param rbuf The ring buffer.
 */
 void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf );
 
-/** Retrieve the number of bytes available in the ring buffer for writing.
+/** Retrieve the number of elements available in the ring buffer for writing.
 
  @param rbuf The ring buffer.
 
- @return The number of bytes available for writing.
+ @return The number of elements available for writing.
 */
-long PaUtil_GetRingBufferWriteAvailable( PaUtilRingBuffer *rbuf );
+ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf );
 
-/** Retrieve the number of bytes available in the ring buffer for reading.
+/** Retrieve the number of elements available in the ring buffer for reading.
 
  @param rbuf The ring buffer.
 
- @return The number of bytes available for reading.
+ @return The number of elements available for reading.
 */
-long PaUtil_GetRingBufferReadAvailable( PaUtilRingBuffer *rbuf );
+ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf );
 
 /** Write data to the ring buffer.
 
@@ -106,11 +144,11 @@ long PaUtil_GetRingBufferReadAvailable( PaUtilRingBuffer *rbuf );
 
  @param data The address of new data to write to the buffer.
 
- @param numBytes The number of bytes to be written.
+ @param elementCount The number of elements to be written.
 
- @return The number of bytes written.
+ @return The number of elements written.
 */
-long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long numBytes );
+ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount );
 
 /** Read data from the ring buffer.
 
@@ -118,17 +156,17 @@ long PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, long numB
 
  @param data The address where the data should be stored.
 
- @param numBytes The number of bytes to be read.
+ @param elementCount The number of elements to be read.
 
- @return The number of bytes read.
+ @return The number of elements read.
 */
-long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long numBytes );
+ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount );
 
 /** Get address of region(s) to which we can write data.
 
  @param rbuf The ring buffer.
 
- @param numBytes The number of bytes desired.
+ @param elementCount The number of elements desired.
 
  @param dataPtr1 The address where the first (or only) region pointer will be
  stored.
@@ -137,32 +175,32 @@ long PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, long numBytes );
  stored.
 
  @param dataPtr2 The address where the second region pointer will be stored if
- the first region is too small to satisfy numBytes.
+ the first region is too small to satisfy elementCount.
 
  @param sizePtr2 The address where the second region length will be stored if
- the first region is too small to satisfy numBytes.
+ the first region is too small to satisfy elementCount.
 
- @return The room available to be written or numBytes, whichever is smaller.
+ @return The room available to be written or elementCount, whichever is smaller.
 */
-long PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, long numBytes,
-                                       void **dataPtr1, long *sizePtr1,
-                                       void **dataPtr2, long *sizePtr2 );
+ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
+                                       void **dataPtr1, ring_buffer_size_t *sizePtr1,
+                                       void **dataPtr2, ring_buffer_size_t *sizePtr2 );
 
 /** Advance the write index to the next location to be written.
 
  @param rbuf The ring buffer.
 
- @param numBytes The number of bytes to advance.
+ @param elementCount The number of elements to advance.
 
  @return The new position.
 */
-long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long numBytes );
+ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount );
 
-/** Get address of region(s) from which we can write data.
+/** Get address of region(s) from which we can read data.
 
  @param rbuf The ring buffer.
 
- @param numBytes The number of bytes desired.
+ @param elementCount The number of elements desired.
 
  @param dataPtr1 The address where the first (or only) region pointer will be
  stored.
@@ -171,26 +209,26 @@ long PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, long numBytes )
  stored.
 
  @param dataPtr2 The address where the second region pointer will be stored if
- the first region is too small to satisfy numBytes.
+ the first region is too small to satisfy elementCount.
 
  @param sizePtr2 The address where the second region length will be stored if
- the first region is too small to satisfy numBytes.
+ the first region is too small to satisfy elementCount.
 
- @return The number of bytes available for reading.
+ @return The number of elements available for reading.
 */
-long PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, long numBytes,
-                                      void **dataPtr1, long *sizePtr1,
-                                      void **dataPtr2, long *sizePtr2 );
+ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
+                                      void **dataPtr1, ring_buffer_size_t *sizePtr1,
+                                      void **dataPtr2, ring_buffer_size_t *sizePtr2 );
 
 /** Advance the read index to the next location to be read.
 
  @param rbuf The ring buffer.
 
- @param numBytes The number of bytes to advance.
+ @param elementCount The number of elements to advance.
 
  @return The new position.
 */
-long PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, long numBytes );
+ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount );
 
 #ifdef __cplusplus
 }
diff --git a/external/portaudio/pa_stream.c b/external/portaudio/pa_stream.c
index 172e7d2..ea91821 100644
--- a/external/portaudio/pa_stream.c
+++ b/external/portaudio/pa_stream.c
@@ -1,10 +1,10 @@
 /*
- * $Id: pa_stream.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_stream.c 1339 2008-02-15 07:50:33Z rossb $
  * Portable Audio I/O Library
- * 
+ * stream interface
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 2002 Ross Bencina
+ * Copyright (c) 2008 Ross Bencina
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -40,8 +40,8 @@
 /** @file
  @ingroup common_src
 
- @brief Interface used by pa_front to virtualize functions which operate on
- streams.
+ @brief Stream interfaces, representation structures and helper functions
+ used to interface between pa_front.c host API implementations.
 */
 
 
diff --git a/external/portaudio/pa_stream.h b/external/portaudio/pa_stream.h
index f5363b3..8d707b7 100644
--- a/external/portaudio/pa_stream.h
+++ b/external/portaudio/pa_stream.h
@@ -1,12 +1,12 @@
 #ifndef PA_STREAM_H
 #define PA_STREAM_H
 /*
- * $Id: pa_stream.h 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_stream.h 1339 2008-02-15 07:50:33Z rossb $
  * Portable Audio I/O Library
  * stream interface
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ * Copyright (c) 1999-2008 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -42,8 +42,8 @@
 /** @file
  @ingroup common_src
 
- @brief Interface used by pa_front to virtualize functions which operate on
- streams.
+ @brief Stream interfaces, representation structures and helper functions
+ used to interface between pa_front.c host API implementations.
 */
 
 
diff --git a/external/portaudio/pa_trace.c b/external/portaudio/pa_trace.c
index 583d3ae..6a41cbb 100644
--- a/external/portaudio/pa_trace.c
+++ b/external/portaudio/pa_trace.c
@@ -1,5 +1,5 @@
 /*
- * $Id: pa_trace.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_trace.c 1916 2014-01-17 03:45:15Z philburk $
  * Portable Audio I/O Library Trace Facility
  * Store trace information in real-time for later printing.
  *
@@ -40,18 +40,22 @@
 /** @file
  @ingroup common_src
 
- @brief Event trace mechanism for debugging.
+ @brief Real-time safe event trace logging facility for debugging.
 */
 
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdarg.h>
 #include <string.h>
+#include <assert.h>
 #include "pa_trace.h"
+#include "pa_util.h"
+#include "pa_debugprint.h"
 
 #if PA_TRACE_REALTIME_EVENTS
 
-static char *traceTextArray[PA_MAX_TRACE_RECORDS];
+static char const *traceTextArray[PA_MAX_TRACE_RECORDS];
 static int traceIntArray[PA_MAX_TRACE_RECORDS];
 static int traceIndex = 0;
 static int traceBlock = 0;
@@ -94,4 +98,141 @@ void PaUtil_AddTraceMessage( const char *msg, int data )
     }
 }
 
+/************************************************************************/
+/* High performance log alternative                                     */
+/************************************************************************/
+
+typedef unsigned long long  PaUint64;
+
+typedef struct __PaHighPerformanceLog
+{
+    unsigned    magik;
+    int         writePtr;
+    int         readPtr;
+    int         size;
+    double      refTime;
+    char*       data;
+} PaHighPerformanceLog;
+
+static const unsigned kMagik = 0xcafebabe;
+
+#define USEC_PER_SEC    (1000000ULL)
+
+int PaUtil_InitializeHighSpeedLog( LogHandle* phLog, unsigned maxSizeInBytes )
+{
+    PaHighPerformanceLog* pLog = (PaHighPerformanceLog*)PaUtil_AllocateMemory(sizeof(PaHighPerformanceLog));
+    if (pLog == 0)
+    {
+        return paInsufficientMemory;
+    }
+    assert(phLog != 0);
+    *phLog = pLog;
+
+    pLog->data = (char*)PaUtil_AllocateMemory(maxSizeInBytes);
+    if (pLog->data == 0)
+    {
+        PaUtil_FreeMemory(pLog);
+        return paInsufficientMemory;
+    }
+    pLog->magik = kMagik;
+    pLog->size = maxSizeInBytes;
+    pLog->refTime = PaUtil_GetTime();
+    return paNoError;
+}
+
+void PaUtil_ResetHighSpeedLogTimeRef( LogHandle hLog )
+{
+    PaHighPerformanceLog* pLog = (PaHighPerformanceLog*)hLog;
+    assert(pLog->magik == kMagik);
+    pLog->refTime = PaUtil_GetTime();
+}
+
+typedef struct __PaLogEntryHeader
+{
+    int    size;
+    double timeStamp;
+} PaLogEntryHeader;
+
+#ifdef __APPLE__
+#define _vsnprintf vsnprintf
+#define min(a,b) ((a)<(b)?(a):(b))
+#endif
+
+
+int PaUtil_AddHighSpeedLogMessage( LogHandle hLog, const char* fmt, ... )
+{
+    va_list l;
+    int n = 0;
+    PaHighPerformanceLog* pLog = (PaHighPerformanceLog*)hLog;
+    if (pLog != 0)
+    {
+        PaLogEntryHeader* pHeader;
+        char* p;
+        int maxN;
+        assert(pLog->magik == kMagik);
+        pHeader = (PaLogEntryHeader*)( pLog->data + pLog->writePtr );
+        p = (char*)( pHeader + 1 );
+        maxN = pLog->size - pLog->writePtr - 2 * sizeof(PaLogEntryHeader);
+
+        pHeader->timeStamp = PaUtil_GetTime() - pLog->refTime;
+        if (maxN > 0)
+        {
+            if (maxN > 32)
+            {
+                va_start(l, fmt);
+                n = _vsnprintf(p, min(1024, maxN), fmt, l);
+                va_end(l);
+            }
+            else {
+                n = sprintf(p, "End of log...");
+            }
+            n = ((n + sizeof(unsigned)) & ~(sizeof(unsigned)-1)) + sizeof(PaLogEntryHeader);
+            pHeader->size = n;
+#if 0
+            PaUtil_DebugPrint("%05u.%03u: %s\n", pHeader->timeStamp/1000, pHeader->timeStamp%1000, p);
+#endif
+            pLog->writePtr += n;
+        }
+    }
+    return n;
+}
+
+void PaUtil_DumpHighSpeedLog( LogHandle hLog, const char* fileName )
+{
+    FILE* f = (fileName != NULL) ? fopen(fileName, "w") : stdout;
+    unsigned localWritePtr;
+    PaHighPerformanceLog* pLog = (PaHighPerformanceLog*)hLog;
+    assert(pLog->magik == kMagik);
+    localWritePtr = pLog->writePtr;
+    while (pLog->readPtr != localWritePtr)
+    {
+        const PaLogEntryHeader* pHeader = (const PaLogEntryHeader*)( pLog->data + pLog->readPtr );
+        const char* p = (const char*)( pHeader + 1 );
+        const PaUint64 ts = (const PaUint64)( pHeader->timeStamp * USEC_PER_SEC );
+        assert(pHeader->size < (1024+sizeof(unsigned)+sizeof(PaLogEntryHeader)));
+        fprintf(f, "%05u.%03u: %s\n", (unsigned)(ts/1000), (unsigned)(ts%1000), p);
+        pLog->readPtr += pHeader->size;
+    }
+    if (f != stdout)
+    {
+        fclose(f);
+    }
+}
+
+void PaUtil_DiscardHighSpeedLog( LogHandle hLog )
+{
+    PaHighPerformanceLog* pLog = (PaHighPerformanceLog*)hLog;
+    assert(pLog->magik == kMagik);
+    PaUtil_FreeMemory(pLog->data);
+    PaUtil_FreeMemory(pLog);
+}
+
+#else
+/* This stub was added so that this file will generate a symbol.
+ * Otherwise linker/archiver programs will complain.
+ */
+int PaUtil_TraceStubToSatisfyLinker(void)
+{
+	return 0;
+}
 #endif /* TRACE_REALTIME_EVENTS */
diff --git a/external/portaudio/pa_trace.h b/external/portaudio/pa_trace.h
index a4d2a33..612dbf3 100644
--- a/external/portaudio/pa_trace.h
+++ b/external/portaudio/pa_trace.h
@@ -1,7 +1,7 @@
 #ifndef PA_TRACE_H
 #define PA_TRACE_H
 /*
- * $Id: pa_trace.h 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_trace.h 1812 2012-02-14 09:32:57Z robiwan $
  * Portable Audio I/O Library Trace Facility
  * Store trace information in real-time for later printing.
  *
@@ -42,14 +42,36 @@
 /** @file
  @ingroup common_src
 
- @brief Event trace mechanism for debugging.
+ @brief Real-time safe event trace logging facility for debugging.
 
- Allows data to be written to the buffer at interrupt time and dumped later.
+ Allows data to be logged to a fixed size trace buffer in a real-time
+ execution context (such as at interrupt time). Each log entry consists 
+ of a message comprising a string pointer and an int.  The trace buffer 
+ may be dumped to stdout later.
+
+ This facility is only active if PA_TRACE_REALTIME_EVENTS is set to 1,
+ otherwise the trace functions expand to no-ops.
+
+ @fn PaUtil_ResetTraceMessages
+ @brief Clear the trace buffer.
+
+ @fn PaUtil_AddTraceMessage
+ @brief Add a message to the trace buffer. A message consists of string and an int.
+ @param msg The string pointer must remain valid until PaUtil_DumpTraceMessages 
+    is called. As a result, usually only string literals should be passed as 
+    the msg parameter.
+
+ @fn PaUtil_DumpTraceMessages
+ @brief Print all messages in the trace buffer to stdout and clear the trace buffer.
 */
 
+#ifndef PA_TRACE_REALTIME_EVENTS
+#define PA_TRACE_REALTIME_EVENTS     (0)   /**< Set to 1 to enable logging using the trace functions defined below */
+#endif
 
-#define PA_TRACE_REALTIME_EVENTS     (0)   /* Keep log of various real-time events. */
-#define PA_MAX_TRACE_RECORDS      (2048)
+#ifndef PA_MAX_TRACE_RECORDS
+#define PA_MAX_TRACE_RECORDS      (2048)   /**< Maximum number of records stored in trace buffer */   
+#endif
 
 #ifdef __cplusplus
 extern "C"
@@ -62,13 +84,29 @@ extern "C"
 void PaUtil_ResetTraceMessages();
 void PaUtil_AddTraceMessage( const char *msg, int data );
 void PaUtil_DumpTraceMessages();
-    
+
+/* Alternative interface */
+
+typedef void* LogHandle;
+
+int PaUtil_InitializeHighSpeedLog(LogHandle* phLog, unsigned maxSizeInBytes);
+void PaUtil_ResetHighSpeedLogTimeRef(LogHandle hLog);
+int PaUtil_AddHighSpeedLogMessage(LogHandle hLog, const char* fmt, ...);
+void PaUtil_DumpHighSpeedLog(LogHandle hLog, const char* fileName);
+void PaUtil_DiscardHighSpeedLog(LogHandle hLog);
+
 #else
 
 #define PaUtil_ResetTraceMessages() /* noop */
 #define PaUtil_AddTraceMessage(msg,data) /* noop */
 #define PaUtil_DumpTraceMessages() /* noop */
 
+#define PaUtil_InitializeHighSpeedLog(phLog, maxSizeInBytes)  (0)
+#define PaUtil_ResetHighSpeedLogTimeRef(hLog)
+#define PaUtil_AddHighSpeedLogMessage(...)   (0)
+#define PaUtil_DumpHighSpeedLog(hLog, fileName)
+#define PaUtil_DiscardHighSpeedLog(hLog)
+
 #endif
 
 
diff --git a/external/portaudio/pa_unix_hostapis.c b/external/portaudio/pa_unix_hostapis.c
index 8a32707..98fc0a1 100644
--- a/external/portaudio/pa_unix_hostapis.c
+++ b/external/portaudio/pa_unix_hostapis.c
@@ -1,6 +1,6 @@
 #ifdef UNIX
 /*
- * $Id: pa_unix_hostapis.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_unix_hostapis.c 1740 2011-08-25 07:17:48Z philburk $
  * Portable Audio I/O Library UNIX initialization table
  *
  * Based on the Open Source API proposed by Ross Bencina
@@ -50,19 +50,19 @@ PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
 PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
 /* Linux AudioScience HPI */
 PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
 
+/** Note that on Linux, ALSA is placed before OSS so that the former is preferred over the latter.
+ */
 
 PaUtilHostApiInitializer *paHostApiInitializers[] =
     {
 		#if defined (linux) && defined (ALSA)
-			PaAlsa_Initialize,
-		#elif defined (sgi)
-			PaSGI_Initialize,
-		#else
-			//PaOSS_Initialize,
+			PaAlsa_Initialize,   // ppgb
 		#endif
         0   /* NULL terminated array */
     };
 
-int paDefaultHostApiIndex = 0;
+int paDefaultHostApiIndex = 0;   // ppgb
 #endif
diff --git a/external/portaudio/pa_unix_oss.c b/external/portaudio/pa_unix_oss.c
index 942f15c..96e8a4e 100644
--- a/external/portaudio/pa_unix_oss.c
+++ b/external/portaudio/pa_unix_oss.c
@@ -1,5 +1,5 @@
 /*
- * $Id: pa_unix_oss.c 1296 2007-10-28 22:43:50Z aknudsen $
+ * $Id: pa_unix_oss.c 1894 2013-06-08 19:30:41Z gineera $
  * PortAudio Portable Real-Time Audio Library
  * Latest Version at: http://www.portaudio.com
  * OSS implementation by:
@@ -65,7 +65,11 @@
 
 #ifdef HAVE_SYS_SOUNDCARD_H
 # include <sys/soundcard.h>
-# define DEVICE_NAME_BASE            "/dev/dsp"
+# ifdef __NetBSD__
+#  define DEVICE_NAME_BASE           "/dev/audio"
+# else
+#  define DEVICE_NAME_BASE           "/dev/dsp"
+# endif
 #elif defined(HAVE_LINUX_SOUNDCARD_H)
 # include <linux/soundcard.h>
 # define DEVICE_NAME_BASE            "/dev/dsp"
@@ -97,7 +101,7 @@ static pthread_t mainThread_;
             /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
             if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \
             { \
-                PaUtil_SetLastHostErrorInfo( paALSA, sysErr_, strerror( errno ) ); \
+                PaUtil_SetLastHostErrorInfo( paOSS, sysErr_, strerror( errno ) ); \
             } \
             \
             PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
@@ -181,7 +185,7 @@ typedef struct PaOssStream
     double sampleRate;
 
     int callbackMode;
-    int callbackStop, callbackAbort;
+    volatile int callbackStop, callbackAbort;
 
     PaOssStreamComponent *capture, *playback;
     unsigned long pollTimeout;
@@ -314,6 +318,13 @@ error:
     return result;
 }
 
+static int CalcHigherLogTwo( int n )
+{
+    int log2 = 0;
+    while( (1<<log2) < n ) log2++;
+    return log2;
+}
+
 static PaError QueryDirection( const char *deviceName, StreamMode mode, double *defaultSampleRate, int *maxChannelCount,
         double *defaultLowLatency, double *defaultHighLatency )
 {
@@ -323,6 +334,8 @@ static PaError QueryDirection( const char *deviceName, StreamMode mode, double *
     int devHandle = -1;
     int sr;
     *maxChannelCount = 0;  /* Default value in case this fails */
+    int temp, frgmt;
+    unsigned long fragFrames;
 
     if ( (devHandle = open( deviceName, (mode == StreamMode_In ? O_RDONLY : O_WRONLY) | O_NONBLOCK ))  < 0 )
     {
@@ -332,7 +345,11 @@ static PaError QueryDirection( const char *deviceName, StreamMode mode, double *
         }
         else
         {
-            PA_DEBUG(( "%s: Can't access device: %s\n", __FUNCTION__, strerror( errno ) ));
+            /* Ignore ENOENT, which means we've tried a non-existent device */
+            if( errno != ENOENT )
+            {
+                PA_DEBUG(( "%s: Can't access device %s: %s\n", __FUNCTION__, deviceName, strerror( errno ) ));
+            }
         }
 
         return paDeviceUnavailable;
@@ -346,7 +363,7 @@ static PaError QueryDirection( const char *deviceName, StreamMode mode, double *
     maxNumChannels = 0;
     for( numChannels = 1; numChannels <= 16; numChannels++ )
     {
-        int temp = numChannels;
+        temp = numChannels;
         if( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ) < 0 )
         {
             busy = EAGAIN == errno || EBUSY == errno;
@@ -393,27 +410,35 @@ static PaError QueryDirection( const char *deviceName, StreamMode mode, double *
      * to a supported number of channels. SG20011005 */
     {
         /* use most reasonable default value */
-        int temp = PA_MIN( maxNumChannels, 2 );
-        ENSURE_( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ), paUnanticipatedHostError );
+        numChannels = PA_MIN( maxNumChannels, 2 );
+        ENSURE_( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &numChannels ), paUnanticipatedHostError );
     }
 
     /* Get supported sample rate closest to 44100 Hz */
     if( *defaultSampleRate < 0 )
     {
         sr = 44100;
-        if( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ) < 0 )
-        {
-            result = paUnanticipatedHostError;
-            goto error;
-        }
+        ENSURE_( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ), paUnanticipatedHostError );
 
         *defaultSampleRate = sr;
     }
 
     *maxChannelCount = maxNumChannels;
-    /* TODO */
-    *defaultLowLatency = 512. / *defaultSampleRate;
-    *defaultHighLatency = 2048. / *defaultSampleRate;
+
+    /* Attempt to set low latency with 4 frags-per-buffer, 128 frames-per-frag (total buffer 512 frames)
+     * since the ioctl sets bytes, multiply by numChannels, and base on 2 bytes-per-sample, */
+    fragFrames = 128;
+    frgmt = (4 << 16) + (CalcHigherLogTwo( fragFrames * numChannels * 2 ) & 0xffff);
+    ENSURE_( ioctl( devHandle, SNDCTL_DSP_SETFRAGMENT, &frgmt ), paUnanticipatedHostError );
+
+    /* Use the value set by the ioctl to give the latency achieved */
+    fragFrames = pow( 2, frgmt & 0xffff ) / (numChannels * 2);
+    *defaultLowLatency = ((frgmt >> 16) - 1) * fragFrames / *defaultSampleRate;
+
+    /* Cannot now try setting a high latency (device would need closing and opening again).  Make
+     * high-latency 4 times the low unless the fragFrames are significantly more than requested 128 */
+    temp = (fragFrames < 256) ? 4 : (fragFrames < 512) ? 2 : 1;
+    *defaultHighLatency = temp * *defaultLowLatency;
 
 error:
     if( devHandle >= 0 )
@@ -515,20 +540,13 @@ static PaError BuildDeviceList( PaOSSHostApiRepresentation *ossApi )
        char deviceName[32];
        PaDeviceInfo *deviceInfo;
        int testResult;
-       struct stat stbuf;
 
        if( i == 0 )
           snprintf(deviceName, sizeof (deviceName), "%s", DEVICE_NAME_BASE);
        else
           snprintf(deviceName, sizeof (deviceName), "%s%d", DEVICE_NAME_BASE, i);
 
-       /* PA_DEBUG(("PaOSS BuildDeviceList: trying device %s\n", deviceName )); */
-       if( stat( deviceName, &stbuf ) < 0 )
-       {
-           if( ENOENT != errno )
-               PA_DEBUG(( "%s: Error stat'ing %s: %s\n", __FUNCTION__, deviceName, strerror( errno ) ));
-           continue;
-       }
+       /* PA_DEBUG(("%s: trying device %s\n", __FUNCTION__, deviceName )); */
        if( (testResult = QueryDevice( deviceName, ossApi, &deviceInfo )) != paNoError )
        {
            if( testResult != paDeviceUnavailable )
@@ -785,11 +803,15 @@ error:
     return result;
 }
 
+/** Open input and output devices.
+ *
+ * @param idev: Returned input device file descriptor.
+ * @param odev: Returned output device file descriptor.
+ */
 static PaError OpenDevices( const char *idevName, const char *odevName, int *idev, int *odev )
 {
     PaError result = paNoError;
     int flags = O_NONBLOCK, duplex = 0;
-    int enableBits = 0;
     *idev = *odev = -1;
 
     if( idevName && odevName )
@@ -809,10 +831,6 @@ static PaError OpenDevices( const char *idevName, const char *odevName, int *ide
     {
         ENSURE_( *idev = open( idevName, flags ), paDeviceUnavailable );
         PA_ENSURE( ModifyBlocking( *idev, 1 ) ); /* Blocking */
-
-        /* Initially disable */
-        enableBits = ~PCM_ENABLE_INPUT;
-        ENSURE_( ioctl( *idev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError );
     }
     if( odevName )
     {
@@ -820,10 +838,6 @@ static PaError OpenDevices( const char *idevName, const char *odevName, int *ide
         {
             ENSURE_( *odev = open( odevName, flags ), paDeviceUnavailable );
             PA_ENSURE( ModifyBlocking( *odev, 1 ) ); /* Blocking */
-
-            /* Initially disable */
-            enableBits = ~PCM_ENABLE_OUTPUT;
-            ENSURE_( ioctl( *odev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError );
         }
         else
         {
@@ -969,13 +983,8 @@ static unsigned long PaOssStreamComponent_BufferSize( PaOssStreamComponent *comp
     return PaOssStreamComponent_FrameSize( component ) * component->hostFrames * component->numBufs;
 }
 
-static int CalcHigherLogTwo( int n )
-{
-    int log2 = 0;
-    while( (1<<log2) < n ) log2++;
-    return log2;
-}
-
+/** Configure stream component device parameters.
+ */
 static PaError PaOssStreamComponent_Configure( PaOssStreamComponent *component, double sampleRate, unsigned long
         framesPerBuffer, StreamMode streamMode, PaOssStreamComponent *master )
 {
@@ -1000,8 +1009,9 @@ static PaError PaOssStreamComponent_Configure( PaOssStreamComponent *component,
          */
         if( framesPerBuffer == paFramesPerBufferUnspecified )
         {
-            bufSz = (unsigned long)(component->latency * sampleRate);
-            fragSz = bufSz / 4;
+            /* Aim for 4 fragments in the complete buffer; the latency comes from 3 of these */
+            fragSz = (unsigned long)(component->latency * sampleRate / 3);
+            bufSz = fragSz * 4;
         }
         else
         {
@@ -1126,7 +1136,7 @@ static PaError PaOssStream_Configure( PaOssStream *stream, double sampleRate, un
         assert( component->hostChannelCount > 0 );
         assert( component->hostFrames > 0 );
 
-        *inputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate;
+        *inputLatency = (component->hostFrames * (component->numBufs - 1)) / sampleRate;
     }
     if( stream->playback )
     {
@@ -1137,7 +1147,7 @@ static PaError PaOssStream_Configure( PaOssStream *stream, double sampleRate, un
         assert( component->hostChannelCount > 0 );
         assert( component->hostFrames > 0 );
 
-        *outputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate;
+        *outputLatency = (component->hostFrames * (component->numBufs - 1)) / sampleRate;
     }
 
     if( duplex )
@@ -1246,13 +1256,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     {
         inputHostFormat = stream->capture->hostFormat;
         stream->streamRepresentation.streamInfo.inputLatency = inLatency +
-            PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor ) / sampleRate;
+            PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor ) / sampleRate;
     }
     if( outputParameters )
     {
         outputHostFormat = stream->playback->hostFormat;
         stream->streamRepresentation.streamInfo.outputLatency = outLatency +
-            PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate;
+            PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor ) / sampleRate;
     }
 
     /* Initialize buffer processor with fixed host buffer size.
@@ -1322,7 +1332,17 @@ static PaError PaOssStream_WaitForFrames( PaOssStream *stream, unsigned long *fr
 
     while( pollPlayback || pollCapture )
     {
+#ifdef PTHREAD_CANCELED
         pthread_testcancel();
+#else
+        /* avoid indefinite waiting on thread not supporting cancelation */
+        if( stream->callbackStop || stream->callbackAbort )
+        {
+            PA_DEBUG(( "Cancelling PaOssStream_WaitForFrames\n" ));
+            (*frames) = 0;
+            return paNoError;
+        }
+#endif
 
         /* select may modify the timeout parameter */
         selectTimeval.tv_usec = timeout;
@@ -1346,8 +1366,17 @@ static PaError PaOssStream_WaitForFrames( PaOssStream *stream, unsigned long *fr
             ENSURE_( -1, paUnanticipatedHostError );
         }
         */
+#ifdef PTHREAD_CANCELED
         pthread_testcancel();
-
+#else
+        /* avoid indefinite waiting on thread not supporting cancelation */
+        if( stream->callbackStop || stream->callbackAbort )
+        {
+            PA_DEBUG(( "Cancelling PaOssStream_WaitForFrames\n" ));
+            (*frames) = 0;
+            return paNoError;
+        }
+#endif
         if( pollCapture )
         {
             if( FD_ISSET( captureFd, &readFds ) )
@@ -1437,6 +1466,12 @@ static PaError PaOssStream_Prepare( PaOssStream *stream )
     if( stream->triggered )
         return result;
 
+    /* The OSS reference instructs us to clear direction bits before setting them.*/
+    if( stream->playback )
+        ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError );
+    if( stream->capture )
+        ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError );
+
     if( stream->playback )
     {
         size_t bufSz = PaOssStreamComponent_BufferSize( stream->playback );
@@ -1487,17 +1522,29 @@ static PaError PaOssStream_Stop( PaOssStream *stream, int abort )
     PaError result = paNoError;
 
     /* Looks like the only safe way to stop audio without reopening the device is SNDCTL_DSP_POST.
-     * Also disable capture/playback till the stream is started again */
+     * Also disable capture/playback till the stream is started again.
+     */
+    int captureErr = 0, playbackErr = 0;
     if( stream->capture )
     {
-        ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError );
+        if( (captureErr = ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 )) < 0 )
+        {
+            PA_DEBUG(( "%s: Failed to stop capture device, error: %d\n", __FUNCTION__, captureErr ));
+        }
     }
     if( stream->playback && !stream->sharedDevice )
     {
-        ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError );
+        if( (playbackErr = ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 )) < 0 )
+        {
+            PA_DEBUG(( "%s: Failed to stop playback device, error: %d\n", __FUNCTION__, playbackErr ));
+        }
+    }
+
+    if( captureErr || playbackErr )
+    {
+        result = paUnanticipatedHostError;
     }
 
-error:
     return result;
 }
 
@@ -1590,8 +1637,15 @@ static void *PaOSS_AudioThreadProc( void *userData )
 
     while( 1 )
     {
+#ifdef PTHREAD_CANCELED
         pthread_testcancel();
-
+#else
+        if( stream->callbackAbort ) /* avoid indefinite waiting on thread not supporting cancelation */
+        {
+            PA_DEBUG(( "Aborting callback thread\n" ));
+            break;
+        }
+#endif
         if( stream->callbackStop && callbackResult == paContinue )
         {
             PA_DEBUG(( "Setting callbackResult to paComplete\n" ));
@@ -1618,8 +1672,21 @@ static void *PaOSS_AudioThreadProc( void *userData )
         {
             unsigned long frames = framesAvail;
 
+#ifdef PTHREAD_CANCELED
             pthread_testcancel();
+#else
+            if( stream->callbackStop )
+            {
+                PA_DEBUG(( "Setting callbackResult to paComplete\n" ));
+                callbackResult = paComplete;
+            }
 
+            if( stream->callbackAbort ) /* avoid indefinite waiting on thread not supporting cancelation */
+            {
+                PA_DEBUG(( "Aborting callback thread\n" ));
+                break;
+            }
+#endif
             PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
 
             /* Read data */
@@ -1861,6 +1928,7 @@ static PaError ReadStream( PaStream* s,
                            void *buffer,
                            unsigned long frames )
 {
+    PaError result = paNoError;
     PaOssStream *stream = (PaOssStream*)s;
     int bytesRequested, bytesRead;
     unsigned long framesRequested;
@@ -1881,21 +1949,28 @@ static PaError ReadStream( PaStream* s,
         framesRequested = PA_MIN( frames, stream->capture->hostFrames );
 
 	bytesRequested = framesRequested * PaOssStreamComponent_FrameSize( stream->capture );
-	bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested );
+	ENSURE_( (bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested )),
+                 paUnanticipatedHostError );
 	if ( bytesRequested != bytesRead )
+	{
+	    PA_DEBUG(( "Requested %d bytes, read %d\n", bytesRequested, bytesRead ));
 	    return paUnanticipatedHostError;
+	}
 
 	PaUtil_SetInputFrameCount( &stream->bufferProcessor, stream->capture->hostFrames );
 	PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, stream->capture->hostChannelCount );
         PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesRequested );
 	frames -= framesRequested;
     }
-    return paNoError;
+
+error:
+    return result;
 }
 
 
 static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frames )
 {
+    PaError result = paNoError;
     PaOssStream *stream = (PaOssStream*)s;
     int bytesRequested, bytesWritten;
     unsigned long framesConverted;
@@ -1921,35 +1996,50 @@ static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frame
 	frames -= framesConverted;
 
 	bytesRequested = framesConverted * PaOssStreamComponent_FrameSize( stream->playback );
-	bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested );
+	ENSURE_( (bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested )),
+                 paUnanticipatedHostError );
 
 	if ( bytesRequested != bytesWritten )
+	{
+	    PA_DEBUG(( "Requested %d bytes, wrote %d\n", bytesRequested, bytesWritten ));
 	    return paUnanticipatedHostError;
+	}
     }
-    return paNoError;
+
+error:
+    return result;
 }
 
 
 static signed long GetStreamReadAvailable( PaStream* s )
 {
+    PaError result = paNoError;
     PaOssStream *stream = (PaOssStream*)s;
     audio_buf_info info;
 
-    if( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ) < 0 )
-        return paUnanticipatedHostError;
+    ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ), paUnanticipatedHostError );
     return info.fragments * stream->capture->hostFrames;
+
+error:
+    return result;
 }
 
 
 /* TODO: Compute number of allocated bytes somewhere else, can we use ODELAY with capture */
 static signed long GetStreamWriteAvailable( PaStream* s )
 {
+    PaError result = paNoError;
     PaOssStream *stream = (PaOssStream*)s;
     int delay = 0;
-
-    if( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ) < 0 )
-        return paUnanticipatedHostError;
-
+#ifdef SNDCTL_DSP_GETODELAY
+    ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ), paUnanticipatedHostError );
+#endif
     return (PaOssStreamComponent_BufferSize( stream->playback ) - delay) / PaOssStreamComponent_FrameSize( stream->playback );
+
+/* Conditionally compile this to avoid warning about unused label */
+#ifdef SNDCTL_DSP_GETODELAY
+error:
+    return result;
+#endif
 }
 
diff --git a/external/portaudio/pa_unix_util.c b/external/portaudio/pa_unix_util.c
index 54e1d7b..c78f331 100644
--- a/external/portaudio/pa_unix_util.c
+++ b/external/portaudio/pa_unix_util.c
@@ -1,6 +1,6 @@
 #ifdef UNIX
 /*
- * $Id: pa_unix_util.c 1232 2007-06-16 14:49:43Z rossb $
+ * $Id: pa_unix_util.c 1510 2010-06-10 08:05:29Z dmitrykos $
  * Portable Audio I/O Library
  * UNIX platform-specific support functions
  *
@@ -52,6 +52,13 @@
 #include <math.h>
 #include <errno.h>
 
+#if defined(__APPLE__) && !defined(HAVE_MACH_ABSOLUTE_TIME)
+#define HAVE_MACH_ABSOLUTE_TIME
+#endif
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+#include <mach/mach_time.h>
+#endif
+
 #include "pa_util.h"
 #include "pa_unix_util.h"
 #include "pa_debugprint.h"
@@ -119,27 +126,47 @@ void Pa_Sleep( long msec )
 #endif
 }
 
-/*            *** NOT USED YET: ***
-static int usePerformanceCounter_;
-static double microsecondsPerTick_;
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+/*
+    Discussion on the CoreAudio mailing list suggests that calling
+    gettimeofday (or anything else in the BSD layer) is not real-time
+    safe, so we use mach_absolute_time on OSX. This implementation is 
+    based on these two links:
+
+    Technical Q&A QA1398 - Mach Absolute Time Units
+    http://developer.apple.com/mac/library/qa/qa2004/qa1398.html
+
+    Tutorial: Performance and Time.
+    http://www.macresearch.org/tutorial_performance_and_time
 */
 
+/* Scaler to convert the result of mach_absolute_time to seconds */
+static double machSecondsConversionScaler_ = 0.0; 
+#endif
+
 void PaUtil_InitializeClock( void )
 {
-    /* TODO */
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+    mach_timebase_info_data_t info;
+    kern_return_t err = mach_timebase_info( &info );
+    if( err == 0  )
+        machSecondsConversionScaler_ = 1e-9 * (double) info.numer / (double) info.denom;
+#endif
 }
 
 
 PaTime PaUtil_GetTime( void )
 {
-#ifdef HAVE_CLOCK_GETTIME
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+    return mach_absolute_time() * machSecondsConversionScaler_;
+#elif defined(HAVE_CLOCK_GETTIME)
     struct timespec tp;
     clock_gettime(CLOCK_REALTIME, &tp);
-    return (PaTime)(tp.tv_sec + tp.tv_nsec / 1.e9);
+    return (PaTime)(tp.tv_sec + tp.tv_nsec * 1e-9);
 #else
     struct timeval tv;
     gettimeofday( &tv, NULL );
-    return (PaTime) tv.tv_usec / 1000000. + tv.tv_sec;
+    return (PaTime) tv.tv_usec * 1e-6 + tv.tv_sec;
 #endif
 }
 
@@ -167,9 +194,15 @@ PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *e
     if( exitResult )
         *exitResult = paNoError;
 
+    /* If pthread_cancel is not supported (Android platform) whole this function can lead to indefinite waiting if 
+       working thread (callbackThread) has'n received any stop signals from outside, please keep 
+       this in mind when considering using PaUtil_CancelThreading
+    */
+#ifdef PTHREAD_CANCELED
     /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */
     if( !wait )
         pthread_cancel( threading->callbackThread );   /* XXX: Safe to call this if the thread has exited on its own? */
+#endif
     pthread_join( threading->callbackThread, &pret );
 
 #ifdef PTHREAD_CANCELED
@@ -191,7 +224,7 @@ PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *e
 /* paUnixMainThread 
  * We have to be a bit careful with defining this global variable,
  * as explained below. */
-#ifdef __apple__
+#ifdef __APPLE__
 /* apple/gcc has a "problem" with global vars and dynamic libs.
    Initializing it seems to fix the problem.
    Described a bit in this thread:
@@ -204,7 +237,7 @@ pthread_t paUnixMainThread = 0;
 pthread_t paUnixMainThread = 0;
 #endif
 
-PaError PaUnixThreading_Initialize()
+PaError PaUnixThreading_Initialize( void )
 {
     paUnixMainThread = pthread_self();
     return paNoError;
@@ -401,12 +434,19 @@ PaError PaUnixThread_Terminate( PaUnixThread* self, int wait, PaError* exitResul
     {
         PA_DEBUG(( "%s: Canceling thread %d\n", __FUNCTION__, self->thread ));
         /* XXX: Safe to call this if the thread has exited on its own? */
+#ifdef PTHREAD_CANCELED
         pthread_cancel( self->thread );
+#endif
     }
     PA_DEBUG(( "%s: Joining thread %d\n", __FUNCTION__, self->thread ));
     PA_ENSURE_SYSTEM( pthread_join( self->thread, &pret ), 0 );
 
+#ifdef PTHREAD_CANCELED
     if( pret && PTHREAD_CANCELED != pret )
+#else
+    /* !wait means the thread may have been canceled */
+    if( pret && wait )
+#endif
     {
         if( exitResult )
         {
@@ -480,9 +520,11 @@ PaError PaUnixMutex_Terminate( PaUnixMutex* self )
 PaError PaUnixMutex_Lock( PaUnixMutex* self )
 {
     PaError result = paNoError;
-    int oldState;
     
+#ifdef PTHREAD_CANCEL
+	int oldState;
     PA_ENSURE_SYSTEM( pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldState ), 0 );
+#endif
     PA_ENSURE_SYSTEM( pthread_mutex_lock( &self->mtx ), 0 );
 
 error:
@@ -496,10 +538,12 @@ error:
 PaError PaUnixMutex_Unlock( PaUnixMutex* self )
 {
     PaError result = paNoError;
-    int oldState;
 
     PA_ENSURE_SYSTEM( pthread_mutex_unlock( &self->mtx ), 0 );
+#ifdef PTHREAD_CANCEL
+	int oldState;
     PA_ENSURE_SYSTEM( pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldState ), 0 );
+#endif
 
 error:
     return result;
@@ -665,4 +709,4 @@ static void *CanaryFunc( void *userData )
 }
 */
 #endif
-#endif
\ No newline at end of file
+#endif
diff --git a/external/portaudio/pa_util.h b/external/portaudio/pa_util.h
index 55eaa13..c454ea7 100644
--- a/external/portaudio/pa_util.h
+++ b/external/portaudio/pa_util.h
@@ -1,12 +1,12 @@
 #ifndef PA_UTIL_H
 #define PA_UTIL_H
 /*
- * $Id: pa_util.h 1229 2007-06-15 16:11:11Z rossb $
+ * $Id: pa_util.h 1584 2011-02-02 18:58:17Z rossb $
  * Portable Audio I/O Library implementation utilities header
  * common implementation utilities and interfaces
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ * Copyright (c) 1999-2008 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -44,8 +44,8 @@
 
     @brief Prototypes for utility functions used by PortAudio implementations.
 
-    @todo Document and adhere to the alignment guarantees provided by
-    PaUtil_AllocateMemory().
+    Some functions declared here are defined in pa_front.c while others
+    are implemented separately for each platform.
 */
 
 
diff --git a/external/portaudio/pa_win_coinitialize.c b/external/portaudio/pa_win_coinitialize.c
new file mode 100644
index 0000000..e15b145
--- /dev/null
+++ b/external/portaudio/pa_win_coinitialize.c
@@ -0,0 +1,144 @@
+/*
+ * Microsoft COM initialization routines
+ * Copyright (c) 1999-2011 Ross Bencina, Dmitry Kostjuchenko
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2011 Ross Bencina, Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/** @file
+ @ingroup win_src
+
+ @brief Microsoft COM initialization routines.
+*/
+
+#include <windows.h>
+#include <objbase.h>
+
+#include "portaudio.h"
+#include "pa_util.h"
+#include "pa_debugprint.h"
+
+#include "pa_win_coinitialize.h"
+
+
+#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) && !defined(_WIN32_WCE) /* MSC version 6 and above */
+#pragma comment( lib, "ole32.lib" )
+#endif
+
+
+/* use some special bit patterns here to try to guard against uninitialized memory errors */
+#define PAWINUTIL_COM_INITIALIZED       (0xb38f)
+#define PAWINUTIL_COM_NOT_INITIALIZED   (0xf1cd)
+
+
+PaError PaWinUtil_CoInitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult )
+{
+    HRESULT hr;
+
+    comInitializationResult->state = PAWINUTIL_COM_NOT_INITIALIZED;
+
+    /*
+        If COM is already initialized CoInitialize will either return
+        FALSE, or RPC_E_CHANGED_MODE if it was initialised in a different
+        threading mode. In either case we shouldn't consider it an error
+        but we need to be careful to not call CoUninitialize() if 
+        RPC_E_CHANGED_MODE was returned.
+    */
+
+    hr = CoInitialize(0); /* use legacy-safe equivalent to CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) */
+    if( FAILED(hr) && hr != RPC_E_CHANGED_MODE )
+    {
+        PA_DEBUG(("CoInitialize(0) failed. hr=%d\n", hr));
+
+        if( hr == E_OUTOFMEMORY )
+            return paInsufficientMemory;
+
+        {
+            char *lpMsgBuf;
+            FormatMessage(
+                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+                NULL,
+                hr,
+                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                (LPTSTR) &lpMsgBuf,
+                0,
+                NULL
+            );
+            PaUtil_SetLastHostErrorInfo( hostApiType, hr, lpMsgBuf );
+            LocalFree( lpMsgBuf );
+        }
+
+        return paUnanticipatedHostError;
+    }
+
+    if( hr != RPC_E_CHANGED_MODE )
+    {
+        comInitializationResult->state = PAWINUTIL_COM_INITIALIZED;
+
+        /*
+            Memorize calling thread id and report warning on Uninitialize if 
+            calling thread is different as CoInitialize must match CoUninitialize 
+            in the same thread.
+        */
+        comInitializationResult->initializingThreadId = GetCurrentThreadId();
+    }
+
+    return paNoError;
+}
+
+
+void PaWinUtil_CoUninitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult )
+{
+    if( comInitializationResult->state != PAWINUTIL_COM_NOT_INITIALIZED
+            && comInitializationResult->state != PAWINUTIL_COM_INITIALIZED ){
+    
+        PA_DEBUG(("ERROR: PaWinUtil_CoUninitialize called without calling PaWinUtil_CoInitialize\n"));
+    }
+
+    if( comInitializationResult->state == PAWINUTIL_COM_INITIALIZED )
+    {
+        DWORD currentThreadId = GetCurrentThreadId();
+		if( comInitializationResult->initializingThreadId != currentThreadId )
+		{
+			PA_DEBUG(("ERROR: failed PaWinUtil_CoUninitialize calling thread[%d] does not match initializing thread[%d]\n",
+				currentThreadId, comInitializationResult->initializingThreadId));
+		}
+		else
+		{
+			CoUninitialize();
+
+            comInitializationResult->state = PAWINUTIL_COM_NOT_INITIALIZED;
+		}
+    }
+}
\ No newline at end of file
diff --git a/external/portaudio/pa_win_coinitialize.h b/external/portaudio/pa_win_coinitialize.h
new file mode 100644
index 0000000..a76337c
--- /dev/null
+++ b/external/portaudio/pa_win_coinitialize.h
@@ -0,0 +1,94 @@
+/*
+ * Microsoft COM initialization routines
+ * Copyright (c) 1999-2011 Ross Bencina, Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+/** @file
+ @ingroup win_src
+
+ @brief Microsoft COM initialization routines.
+*/
+
+#ifndef PA_WIN_COINITIALIZE_H
+#define PA_WIN_COINITIALIZE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+/**
+ @brief Data type used to hold the result of an attempt to initialize COM
+    using PaWinUtil_CoInitialize. Must be retained between a call to 
+    PaWinUtil_CoInitialize and a matching call to PaWinUtil_CoUninitialize.
+*/
+typedef struct PaWinUtilComInitializationResult{
+    int state;
+    int initializingThreadId;
+} PaWinUtilComInitializationResult;
+
+
+/**
+ @brief Initialize Microsoft COM subsystem on the current thread.
+
+ @param hostApiType the host API type id of the caller. Used for error reporting.
+
+ @param comInitializationResult An output parameter. The value pointed to by 
+        this parameter stores information required by PaWinUtil_CoUninitialize 
+        to correctly uninitialize COM. The value should be retained and later 
+        passed to PaWinUtil_CoUninitialize.
+
+ If PaWinUtil_CoInitialize returns paNoError, the caller must later call
+ PaWinUtil_CoUninitialize once.
+*/
+PaError PaWinUtil_CoInitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult );
+
+
+/**
+ @brief Uninitialize the Microsoft COM subsystem on the current thread using 
+ the result of a previous call to PaWinUtil_CoInitialize. Must be called on the same
+ thread as PaWinUtil_CoInitialize.
+
+ @param hostApiType the host API type id of the caller. Used for error reporting.
+
+ @param comInitializationResult An input parameter. A pointer to a value previously
+ initialized by a call to PaWinUtil_CoInitialize.
+*/
+void PaWinUtil_CoUninitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult );
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* PA_WIN_COINITIALIZE_H */
diff --git a/external/portaudio/pa_win_ds.c b/external/portaudio/pa_win_ds.c
index 6bc6be9..8b1adb3 100644
--- a/external/portaudio/pa_win_ds.c
+++ b/external/portaudio/pa_win_ds.c
@@ -1,6 +1,6 @@
 #ifdef _WIN32
 /*
- * $Id: pa_win_ds.c 1286 2007-09-26 21:34:23Z rossb $
+ * $Id: pa_win_ds.c 1877 2012-11-10 02:55:20Z rbencina $
  * Portable Audio I/O Library DirectSound implementation
  *
  * Authors: Phil Burk, Robert Marsanyi & Ross Bencina
@@ -39,51 +39,42 @@
  */
 
 /** @file
- @ingroup hostaip_src
-
-    @todo implement paInputOverflow callback status flag
-    
-    @todo implement paNeverDropInput.
-
-    @todo implement host api specific extension to set i/o buffer sizes in frames
-
-    @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.)
-
-    @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable
-
-    @todo audit handling of DirectSound result codes - in many cases we could convert a HRESULT into
-        a native portaudio error code. Standard DirectSound result codes are documented at msdn.
-
-    @todo implement IsFormatSupported
-
-    @todo call PaUtil_SetLastHostErrorInfo with a specific error string (currently just "DSound error").
-
-    @todo make sure all buffers have been played before stopping the stream
-        when the stream callback returns paComplete
-
-    @todo retrieve default devices using the DRVM_MAPPER_PREFERRED_GET functions used in the wmme api
-        these wave device ids can be aligned with the directsound devices either by retrieving
-        the system interface device name using DRV_QUERYDEVICEINTERFACE or by using the wave device
-        id retrieved in KsPropertySetEnumerateCallback.
+ @ingroup hostapi_src
+*/
 
-    old TODOs from phil, need to work out if these have been done:
-        O- fix "patest_stop.c"
+/* Until May 2011 PA/DS has used a multimedia timer to perform the callback.
+   We're replacing this with a new implementation using a thread and a different timer mechanim.
+   Defining PA_WIN_DS_USE_WMME_TIMER uses the old (pre-May 2011) behavior.
 */
+//#define PA_WIN_DS_USE_WMME_TIMER
 
-#undef UNICODE
+#include <assert.h>
 #include <stdio.h>
 #include <string.h> /* strlen() */
+
+#define _WIN32_WINNT 0x0400 /* required to get waitable timer APIs */
+#include <initguid.h> /* make sure ds guids get defined */
 #include <windows.h>
 #include <objbase.h>
 
+
 /*
-  We are only using DX3 in here, no need to polute the namespace - davidv
+  Use the earliest version of DX required, no need to polute the namespace
 */
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+#define DIRECTSOUND_VERSION 0x0800
+#else
 #define DIRECTSOUND_VERSION 0x0300
+#endif
 #include <dsound.h>
 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
 #include <dsconf.h>
 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+#ifndef UNDER_CE
+#include <process.h>
+#endif
+#endif
 
 #include "pa_util.h"
 #include "pa_allocation.h"
@@ -97,18 +88,53 @@
 #include "pa_win_ds_dynlink.h"
 #include "pa_win_waveformat.h"
 #include "pa_win_wdmks_utils.h"
-
+#include "pa_win_coinitialize.h"
 
 #if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
 #pragma comment( lib, "dsound.lib" )
 #pragma comment( lib, "winmm.lib" )
+#pragma comment( lib, "kernel32.lib" )
+#endif
+
+/* use CreateThread for CYGWIN, _beginthreadex for all others */
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+
+#if !defined(__CYGWIN__) && !defined(UNDER_CE)
+#define CREATE_THREAD (HANDLE)_beginthreadex
+#undef CLOSE_THREAD_HANDLE /* as per documentation we don't call CloseHandle on a thread created with _beginthreadex */
+#define PA_THREAD_FUNC static unsigned WINAPI
+#define PA_THREAD_ID unsigned
+#else
+#define CREATE_THREAD CreateThread
+#define CLOSE_THREAD_HANDLE CloseHandle
+#define PA_THREAD_FUNC static DWORD WINAPI
+#define PA_THREAD_ID DWORD
+#endif
+
+#if (defined(UNDER_CE))
+#pragma comment(lib, "Coredll.lib")
+#elif (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
+#pragma comment(lib, "winmm.lib")
 #endif
 
+PA_THREAD_FUNC ProcessingThreadProc( void *pArg );
+
+#if !defined(UNDER_CE)
+#define PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT /* use waitable timer where possible, otherwise we use a WaitForSingleObject timeout */
+#endif
+
+#endif /* !PA_WIN_DS_USE_WMME_TIMER */
+
+
 /*
  provided in newer platform sdks and x64
  */
 #ifndef DWORD_PTR
-#define DWORD_PTR DWORD
+    #if defined(_WIN64)
+        #define DWORD_PTR unsigned __int64
+    #else
+        #define DWORD_PTR unsigned long
+    #endif
 #endif
 
 #define PRINT(x) PA_DEBUG(x);
@@ -118,17 +144,24 @@
 
 #define PA_USE_HIGH_LATENCY   (0)
 #if PA_USE_HIGH_LATENCY
-#define PA_WIN_9X_LATENCY     (500)
-#define PA_WIN_NT_LATENCY     (600)
+#define PA_DS_WIN_9X_DEFAULT_LATENCY_     (.500)
+#define PA_DS_WIN_NT_DEFAULT_LATENCY_     (.600)
 #else
-#define PA_WIN_9X_LATENCY     (140)
-#define PA_WIN_NT_LATENCY     (280)
+#define PA_DS_WIN_9X_DEFAULT_LATENCY_     (.140)
+#define PA_DS_WIN_NT_DEFAULT_LATENCY_     (.280)
 #endif
 
-#define PA_WIN_WDM_LATENCY       (120)
+#define PA_DS_WIN_WDM_DEFAULT_LATENCY_    (.120)
+
+/* we allow the polling period to range between 1 and 100ms.
+   prior to August 2011 we limited the minimum polling period to 10ms.
+*/
+#define PA_DS_MINIMUM_POLLING_PERIOD_SECONDS    (0.001) /* 1ms */
+#define PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS    (0.100) /* 100ms */
+#define PA_DS_POLLING_JITTER_SECONDS            (0.001) /* 1ms */
 
 #define SECONDS_PER_MSEC      (0.001)
-#define MSEC_PER_SECOND       (1000)
+#define MSECS_PER_SECOND       (1000)
 
 /* prototypes for functions declared in this file */
 
@@ -176,9 +209,9 @@ static signed long GetStreamWriteAvailable( PaStream* stream );
     PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" )
 
 /************************************************* DX Prototypes **********/
-static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
-                                     LPCTSTR lpszDesc,
-                                     LPCTSTR lpszDrvName,
+static BOOL CALLBACK CollectGUIDsProcA(LPGUID lpGUID,
+                                     LPCSTR lpszDesc,
+                                     LPCSTR lpszDrvName,
                                      LPVOID lpContext );
 
 /************************************************************************************/
@@ -206,7 +239,7 @@ typedef struct
 
     /* implementation specific data goes here */
 
-    char                    comWasInitialized;
+    PaWinUtilComInitializationResult comInitializationResult;
 
 } PaWinDsHostApiRepresentation;
 
@@ -220,45 +253,138 @@ typedef struct PaWinDsStream
     PaUtilBufferProcessor bufferProcessor;
 
 /* DirectSound specific data. */
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+    LPDIRECTSOUNDFULLDUPLEX8 pDirectSoundFullDuplex8;
+#endif
 
 /* Output */
     LPDIRECTSOUND        pDirectSound;
+    LPDIRECTSOUNDBUFFER  pDirectSoundPrimaryBuffer;
     LPDIRECTSOUNDBUFFER  pDirectSoundOutputBuffer;
     DWORD                outputBufferWriteOffsetBytes;     /* last write position */
     INT                  outputBufferSizeBytes;
-    INT                  bytesPerOutputFrame;
+    INT                  outputFrameSizeBytes;
     /* Try to detect play buffer underflows. */
     LARGE_INTEGER        perfCounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */
     LARGE_INTEGER        previousPlayTime;
-    UINT                 previousPlayCursor;
+    DWORD                previousPlayCursor;
     UINT                 outputUnderflowCount;
     BOOL                 outputIsRunning;
-    /* use double which lets us can play for several thousand years with enough precision */
-    double               dsw_framesWritten;
-    double               framesPlayed;
+    INT                  finalZeroBytesWritten; /* used to determine when we've flushed the whole buffer */
+
 /* Input */
     LPDIRECTSOUNDCAPTURE pDirectSoundCapture;
     LPDIRECTSOUNDCAPTUREBUFFER   pDirectSoundInputBuffer;
-    INT                  bytesPerInputFrame;
+    INT                  inputFrameSizeBytes;
     UINT                 readOffset;      /* last read position */
-    UINT                 inputSize;
+    UINT                 inputBufferSizeBytes;
 
     
-    MMRESULT         timerID;
-    int              framesPerDSBuffer;
+    int              hostBufferSizeFrames; /* input and output host ringbuffers have the same number of frames */
     double           framesWritten;
     double           secondsPerHostByte; /* Used to optimize latency calculation for outTime */
+    double           pollingPeriodSeconds;
 
     PaStreamCallbackFlags callbackFlags;
+
+    PaStreamFlags    streamFlags;
+    int              callbackResult;
+    HANDLE           processingCompleted;
     
 /* FIXME - move all below to PaUtilStreamRepresentation */
     volatile int     isStarted;
     volatile int     isActive;
     volatile int     stopProcessing; /* stop thread once existing buffers have been returned */
     volatile int     abortProcessing; /* stop thread immediately */
+
+    UINT             systemTimerResolutionPeriodMs; /* set to 0 if we were unable to set the timer period */ 
+
+#ifdef PA_WIN_DS_USE_WMME_TIMER
+    MMRESULT         timerID;
+#else
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+    HANDLE           waitableTimer;
+#endif
+    HANDLE           processingThread;
+    PA_THREAD_ID     processingThreadId;
+    HANDLE           processingThreadCompleted;
+#endif
+
 } PaWinDsStream;
 
 
+/* Set minimal latency based on the current OS version.
+ * NT has higher latency.
+ */
+static double PaWinDS_GetMinSystemLatencySeconds( void )
+{
+    double minLatencySeconds;
+    /* Set minimal latency based on whether NT or other OS.
+     * NT has higher latency.
+     */
+    OSVERSIONINFO osvi;
+	osvi.dwOSVersionInfoSize = sizeof( osvi );
+	GetVersionEx( &osvi );
+    DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
+    DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
+    DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
+    /* Check for NT */
+	if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
+	{
+		minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_;
+	}
+	else if(osvi.dwMajorVersion >= 5)
+	{
+		minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_;
+	}
+	else
+	{
+		minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_;
+	}
+    return minLatencySeconds;
+}
+
+
+/*************************************************************************
+** Return minimum workable latency required for this host. This is returned
+** As the default stream latency in PaDeviceInfo.
+** Latency can be optionally set by user by setting an environment variable. 
+** For example, to set latency to 200 msec, put:
+**
+**    set PA_MIN_LATENCY_MSEC=200
+**
+** in the AUTOEXEC.BAT file and reboot.
+** If the environment variable is not set, then the latency will be determined
+** based on the OS. Windows NT has higher latency than Win95.
+*/
+#define PA_LATENCY_ENV_NAME  ("PA_MIN_LATENCY_MSEC")
+#define PA_ENV_BUF_SIZE  (32)
+
+static double PaWinDs_GetMinLatencySeconds( double sampleRate )
+{
+    char      envbuf[PA_ENV_BUF_SIZE];
+    DWORD     hresult;
+    double    minLatencySeconds = 0;
+
+    /* Let user determine minimal latency by setting environment variable. */
+    hresult = GetEnvironmentVariableA( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
+    if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
+    {
+        minLatencySeconds = atoi( envbuf ) * SECONDS_PER_MSEC;
+    }
+    else
+    {
+        minLatencySeconds = PaWinDS_GetMinSystemLatencySeconds();
+#if PA_USE_HIGH_LATENCY
+        PRINT(("PA - Minimum Latency set to %f msec!\n", minLatencySeconds * MSECS_PER_SECOND ));
+#endif
+    }
+
+    return minLatencySeconds;
+}
+
+
 /************************************************************************************
 ** Duplicate the input string using the allocations allocator.
 ** A NULL string is converted to a zero length string.
@@ -355,7 +481,7 @@ static PaError ExpandDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidV
             else
             {
                 newItems[i].lpGUID = &newItems[i].guid;
-                memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) );;
+                memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) );
             }
             newItems[i].pnpInterface = guidVector->items[i].pnpInterface;
         }
@@ -388,9 +514,9 @@ static PaError TerminateDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *gu
 /************************************************************************************
 ** Collect preliminary device information during DirectSound enumeration 
 */
-static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
-                                     LPCTSTR lpszDesc,
-                                     LPCTSTR lpszDrvName,
+static BOOL CALLBACK CollectGUIDsProcA(LPGUID lpGUID,
+                                     LPCSTR lpszDesc,
+                                     LPCSTR lpszDrvName,
                                      LPVOID lpContext )
 {
     DSDeviceNameAndGUIDVector *namesAndGUIDs = (DSDeviceNameAndGUIDVector*)lpContext;
@@ -437,6 +563,7 @@ static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
     return TRUE;
 }
 
+
 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
 
 static void *DuplicateWCharString( PaUtilAllocationGroup *allocations, wchar_t *source )
@@ -455,29 +582,38 @@ static BOOL CALLBACK KsPropertySetEnumerateCallback( PDSPROPERTY_DIRECTSOUNDDEVI
     int i;
     DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs = (DSDeviceNamesAndGUIDs*)context;
 
-    if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER )
+    /*
+        Apparently data->Interface can be NULL in some cases. 
+        Possibly virtual devices without hardware.
+        So we check for NULLs now. See mailing list message November 10, 2012:
+        "[Portaudio] portaudio initialization crash in KsPropertySetEnumerateCallback(pa_win_ds.c)"
+    */
+    if( data->Interface )
     {
-        for( i=0; i < deviceNamesAndGUIDs->outputNamesAndGUIDs.count; ++i )
+        if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER )
         {
-            if( deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID
-                && memcmp( &data->DeviceId, deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
+            for( i=0; i < deviceNamesAndGUIDs->outputNamesAndGUIDs.count; ++i )
             {
-                deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].pnpInterface = 
+                if( deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID
+                    && memcmp( &data->DeviceId, deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
+                {
+                    deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].pnpInterface = 
                         (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
-                break;
+                    break;
+                }
             }
         }
-    }
-    else if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE )
-    {
-        for( i=0; i < deviceNamesAndGUIDs->inputNamesAndGUIDs.count; ++i )
+        else if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE )
         {
-            if( deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID
-                && memcmp( &data->DeviceId, deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
+            for( i=0; i < deviceNamesAndGUIDs->inputNamesAndGUIDs.count; ++i )
             {
-                deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].pnpInterface = 
+                if( deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID
+                    && memcmp( &data->DeviceId, deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
+                {
+                    deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].pnpInterface = 
                         (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
-                break;
+                    break;
+                }
             }
         }
     }
@@ -689,7 +825,7 @@ static PaError AddOutputDeviceInfoFromDirectSound(
         else
         {
 
-#ifndef PA_NO_WMME
+#if PA_USE_WMME
             if( caps.dwFlags & DSCAPS_EMULDRIVER )
             {
                 /* If WMME supported, then reject Emulated drivers because they are lousy. */
@@ -722,6 +858,43 @@ static PaError AddOutputDeviceInfoFromDirectSound(
                     winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
                 }
 
+                /* Guess channels count from speaker configuration. We do it only when 
+                   pnpInterface is NULL or when PAWIN_USE_WDMKS_DEVICE_INFO is undefined.
+                */
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+                if( !pnpInterface )
+#endif
+                {
+                    DWORD spkrcfg;
+                    if( SUCCEEDED(IDirectSound_GetSpeakerConfig( lpDirectSound, &spkrcfg )) )
+                    {
+                        int count = 0;
+                        switch (DSSPEAKER_CONFIG(spkrcfg))
+                        {
+                            case DSSPEAKER_HEADPHONE:        count = 2; break;
+                            case DSSPEAKER_MONO:             count = 1; break;
+                            case DSSPEAKER_QUAD:             count = 4; break;
+                            case DSSPEAKER_STEREO:           count = 2; break;
+                            case DSSPEAKER_SURROUND:         count = 4; break;
+                            case DSSPEAKER_5POINT1:          count = 6; break;
+                            case DSSPEAKER_7POINT1:          count = 8; break;
+#ifndef DSSPEAKER_7POINT1_SURROUND
+#define DSSPEAKER_7POINT1_SURROUND 0x00000008
+#endif                            
+                            case DSSPEAKER_7POINT1_SURROUND: count = 8; break;
+#ifndef DSSPEAKER_5POINT1_SURROUND
+#define DSSPEAKER_5POINT1_SURROUND 0x00000009
+#endif
+                            case DSSPEAKER_5POINT1_SURROUND: count = 6; break;
+                        }
+                        if( count )
+                        {
+                            deviceInfo->maxOutputChannels = count;
+                            winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+                        }
+                    }
+                }
+
 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
                 if( pnpInterface )
                 {
@@ -734,11 +907,6 @@ static PaError AddOutputDeviceInfoFromDirectSound(
                 }
 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
 
-                deviceInfo->defaultLowInputLatency = 0.;    /** @todo IMPLEMENT ME */
-                deviceInfo->defaultLowOutputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighInputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighOutputLatency = 0.;  /** @todo IMPLEMENT ME */
-                
                 /* initialize defaultSampleRate */
                 
                 if( caps.dwFlags & DSCAPS_CONTINUOUSRATE )
@@ -765,7 +933,7 @@ static PaError AddOutputDeviceInfoFromDirectSound(
                         ** But it supports continuous sampling.
                         ** So fake range of rates, and hope it really supports it.
                         */
-                        deviceInfo->defaultSampleRate = 44100.0f;
+                        deviceInfo->defaultSampleRate = 48000.0f;  /* assume 48000 as the default */
 
                         DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name ));
                     }
@@ -780,14 +948,19 @@ static PaError AddOutputDeviceInfoFromDirectSound(
                     ** But we know that they really support a range of rates!
                     ** So when we see a ridiculous set of rates, assume it is a range.
                     */
-                  deviceInfo->defaultSampleRate = 44100.0f;
+                  deviceInfo->defaultSampleRate = 48000.0f;  /* assume 48000 as the default */
                   DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name ));
                 }
                 else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
 
-
                 //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate );
                 // dwFlags | DSCAPS_CONTINUOUSRATE 
+
+                deviceInfo->defaultLowInputLatency = 0.;
+                deviceInfo->defaultHighInputLatency = 0.;
+
+                deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
+                deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2;
             }
         }
 
@@ -868,7 +1041,7 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture(
         }
         else
         {
-#ifndef PA_NO_WMME
+#if PA_USE_WMME
             if( caps.dwFlags & DSCAPS_EMULDRIVER )
             {
                 /* If WMME supported, then reject Emulated drivers because they are lousy. */
@@ -896,11 +1069,6 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture(
                 }
 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
 
-                deviceInfo->defaultLowInputLatency = 0.;    /** @todo IMPLEMENT ME */
-                deviceInfo->defaultLowOutputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighInputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighOutputLatency = 0.;  /** @todo IMPLEMENT ME */
-
 /*  constants from a WINE patch by Francois Gouget, see:
     http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
 
@@ -942,7 +1110,7 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture(
                     else if( caps.dwFormats & WAVE_FORMAT_96S16 )
                         deviceInfo->defaultSampleRate = 96000.0;
                     else
-                        deviceInfo->defaultSampleRate = 0.;
+                        deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
                 }
                 else if( caps.dwChannels == 1 )
                 {
@@ -957,9 +1125,15 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture(
                     else if( caps.dwFormats & WAVE_FORMAT_96M16 )
                         deviceInfo->defaultSampleRate = 96000.0;
                     else
-                        deviceInfo->defaultSampleRate = 0.;
+                        deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
                 }
-                else deviceInfo->defaultSampleRate = 0.;
+                else deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
+
+                deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
+                deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2;
+        
+                deviceInfo->defaultLowOutputLatency = 0.;
+                deviceInfo->defaultHighOutputLatency = 0.;
             }
         }
         
@@ -987,32 +1161,15 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde
     int i, deviceCount;
     PaWinDsHostApiRepresentation *winDsHostApi;
     DSDeviceNamesAndGUIDs deviceNamesAndGUIDs;
-
     PaWinDsDeviceInfo *deviceInfoArray;
-    char comWasInitialized = 0;
-
-    /*
-        If COM is already initialized CoInitialize will either return
-        FALSE, or RPC_E_CHANGED_MODE if it was initialised in a different
-        threading mode. In either case we shouldn't consider it an error
-        but we need to be careful to not call CoUninitialize() if 
-        RPC_E_CHANGED_MODE was returned.
-    */
 
-    HRESULT hr = CoInitialize(NULL);
-    if( FAILED(hr) && hr != RPC_E_CHANGED_MODE )
-        return paUnanticipatedHostError;
-
-    if( hr != RPC_E_CHANGED_MODE )
-        comWasInitialized = 1;
+    PaWinDs_InitializeDSoundEntryPoints();
 
     /* initialise guid vectors so they can be safely deleted on error */
     deviceNamesAndGUIDs.winDsHostApi = NULL;
     deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL;
     deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL;
 
-    PaWinDs_InitializeDSoundEntryPoints();
-
     winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) );
     if( !winDsHostApi )
     {
@@ -1020,7 +1177,13 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde
         goto error;
     }
 
-    winDsHostApi->comWasInitialized = comWasInitialized;
+    memset( winDsHostApi, 0, sizeof(PaWinDsHostApiRepresentation) ); /* ensure all fields are zeroed. especially winDsHostApi->allocations */
+
+    result = PaWinUtil_CoInitialize( paDirectSound, &winDsHostApi->comInitializationResult );
+    if( result != paNoError )
+    {
+        goto error;
+    }
 
     winDsHostApi->allocations = PaUtil_CreateAllocationGroup();
     if( !winDsHostApi->allocations )
@@ -1049,9 +1212,9 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde
     if( result != paNoError )
         goto error;
 
-    paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs );
+    paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA( (LPDSENUMCALLBACKA)CollectGUIDsProcA, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs );
 
-    paWinDsDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs );
+    paWinDsDSoundEntryPoints.DirectSoundEnumerateA( (LPDSENUMCALLBACKA)CollectGUIDsProcA, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs );
 
     if( deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError != paNoError )
     {
@@ -1152,22 +1315,10 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde
     return result;
 
 error:
-    if( winDsHostApi )
-    {
-        if( winDsHostApi->allocations )
-        {
-            PaUtil_FreeAllAllocations( winDsHostApi->allocations );
-            PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
-        }
-                
-        PaUtil_FreeMemory( winDsHostApi );
-    }
-
     TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs );
     TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs );
 
-    if( comWasInitialized )
-        CoUninitialize();
+    Terminate( (struct PaUtilHostApiRepresentation *)winDsHostApi );
 
     return result;
 }
@@ -1177,57 +1328,20 @@ error:
 static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
 {
     PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
-    char comWasInitialized = winDsHostApi->comWasInitialized;
 
-    /*
-        IMPLEMENT ME:
-            - clean up any resources not handled by the allocation group
-    */
+    if( winDsHostApi ){
+        if( winDsHostApi->allocations )
+        {
+            PaUtil_FreeAllAllocations( winDsHostApi->allocations );
+            PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
+        }
 
-    if( winDsHostApi->allocations )
-    {
-        PaUtil_FreeAllAllocations( winDsHostApi->allocations );
-        PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
-    }
+        PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult );
 
-    PaUtil_FreeMemory( winDsHostApi );
+        PaUtil_FreeMemory( winDsHostApi );
+    }
 
     PaWinDs_TerminateDSoundEntryPoints();
-
-    if( comWasInitialized )
-        CoUninitialize();
-}
-
-
-/* Set minimal latency based on whether NT or Win95.
- * NT has higher latency.
- */
-static int PaWinDS_GetMinSystemLatency( void )
-{
-    int minLatencyMsec;
-    /* Set minimal latency based on whether NT or other OS.
-     * NT has higher latency.
-     */
-    OSVERSIONINFO osvi;
-	osvi.dwOSVersionInfoSize = sizeof( osvi );
-	GetVersionEx( &osvi );
-    DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
-    DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
-    DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
-    /* Check for NT */
-	if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
-	{
-		minLatencyMsec = PA_WIN_NT_LATENCY;
-	}
-	else if(osvi.dwMajorVersion >= 5)
-	{
-		minLatencyMsec = PA_WIN_WDM_LATENCY;
-	}
-	else
-	{
-		minLatencyMsec = PA_WIN_9X_LATENCY;
-	}
-    return minLatencyMsec;
 }
 
 static PaError ValidateWinDirectSoundSpecificStreamInfo(
@@ -1237,10 +1351,17 @@ static PaError ValidateWinDirectSoundSpecificStreamInfo(
 	if( streamInfo )
 	{
 	    if( streamInfo->size != sizeof( PaWinDirectSoundStreamInfo )
-	            || streamInfo->version != 1 )
+	            || streamInfo->version != 2 )
 	    {
 	        return paIncompatibleHostApiSpecificStreamInfo;
 	    }
+
+        if( streamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+        {
+            if( streamInfo->framesPerBuffer <= 0 )
+                return paIncompatibleHostApiSpecificStreamInfo;
+
+        }
 	}
 
 	return paNoError;
@@ -1342,59 +1463,132 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 }
 
 
-/*************************************************************************
-** Determine minimum number of buffers required for this host based
-** on minimum latency. Latency can be optionally set by user by setting
-** an environment variable. For example, to set latency to 200 msec, put:
-**
-**    set PA_MIN_LATENCY_MSEC=200
-**
-** in the AUTOEXEC.BAT file and reboot.
-** If the environment variable is not set, then the latency will be determined
-** based on the OS. Windows NT has higher latency than Win95.
-*/
-#define PA_LATENCY_ENV_NAME  ("PA_MIN_LATENCY_MSEC")
-#define PA_ENV_BUF_SIZE  (32)
-
-static int PaWinDs_GetMinLatencyFrames( double sampleRate )
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream,
+                                       PaWinDsDeviceInfo *inputDevice,
+                                       PaSampleFormat hostInputSampleFormat,
+                                       WORD inputChannelCount, 
+                                       int bytesPerInputBuffer,
+                                       PaWinWaveFormatChannelMask inputChannelMask,
+                                       PaWinDsDeviceInfo *outputDevice,
+                                       PaSampleFormat hostOutputSampleFormat,
+                                       WORD outputChannelCount, 
+                                       int bytesPerOutputBuffer,
+                                       PaWinWaveFormatChannelMask outputChannelMask,
+                                       unsigned long nFrameRate
+                                        )
 {
-    char      envbuf[PA_ENV_BUF_SIZE];
-    DWORD     hresult;
-    int       minLatencyMsec = 0;
+    HRESULT hr;
+    DSCBUFFERDESC  captureDesc;
+    PaWinWaveFormat captureWaveFormat;
+    DSBUFFERDESC   secondaryRenderDesc;
+    PaWinWaveFormat renderWaveFormat;
+    LPDIRECTSOUNDBUFFER8 pRenderBuffer8;
+    LPDIRECTSOUNDCAPTUREBUFFER8 pCaptureBuffer8;
 
-    /* Let user determine minimal latency by setting environment variable. */
-    hresult = GetEnvironmentVariableA( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
-    if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
+    // capture buffer description
+
+    // only try wave format extensible. assume it's available on all ds 8 systems
+    PaWin_InitializeWaveFormatExtensible( &captureWaveFormat, inputChannelCount, 
+                hostInputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostInputSampleFormat ),
+                nFrameRate, inputChannelMask );
+
+    ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
+    captureDesc.dwSize = sizeof(DSCBUFFERDESC);
+    captureDesc.dwFlags = 0;
+    captureDesc.dwBufferBytes = bytesPerInputBuffer;
+    captureDesc.lpwfxFormat = (WAVEFORMATEX*)&captureWaveFormat;
+
+    // render buffer description
+
+    PaWin_InitializeWaveFormatExtensible( &renderWaveFormat, outputChannelCount, 
+                hostOutputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostOutputSampleFormat ),
+                nFrameRate, outputChannelMask );
+
+    ZeroMemory(&secondaryRenderDesc, sizeof(DSBUFFERDESC));
+    secondaryRenderDesc.dwSize = sizeof(DSBUFFERDESC);
+    secondaryRenderDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+    secondaryRenderDesc.dwBufferBytes = bytesPerOutputBuffer;
+    secondaryRenderDesc.lpwfxFormat = (WAVEFORMATEX*)&renderWaveFormat;
+
+    /* note that we don't create a primary buffer here at all */
+
+    hr = paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8( 
+            inputDevice->lpGUID, outputDevice->lpGUID,
+            &captureDesc, &secondaryRenderDesc,
+            GetDesktopWindow(), /* see InitOutputBuffer() for a discussion of whether this is a good idea */
+            DSSCL_EXCLUSIVE,
+            &stream->pDirectSoundFullDuplex8,
+            &pCaptureBuffer8,
+            &pRenderBuffer8,
+            NULL /* pUnkOuter must be NULL */ 
+        );
+
+    if( hr == DS_OK )
     {
-        minLatencyMsec = atoi( envbuf );
+        PA_DEBUG(("DirectSoundFullDuplexCreate succeeded!\n"));
+
+        /* retrieve the pre ds 8 buffer interfaces which are used by the rest of the code */
+
+        hr = IUnknown_QueryInterface( pCaptureBuffer8, &IID_IDirectSoundCaptureBuffer, (LPVOID *)&stream->pDirectSoundInputBuffer );
+        
+        if( hr == DS_OK )
+            hr = IUnknown_QueryInterface( pRenderBuffer8, &IID_IDirectSoundBuffer, (LPVOID *)&stream->pDirectSoundOutputBuffer );
+
+        /* release the ds 8 interfaces, we don't need them */
+        IUnknown_Release( pCaptureBuffer8 );
+        IUnknown_Release( pRenderBuffer8 );
+
+        if( !stream->pDirectSoundInputBuffer || !stream->pDirectSoundOutputBuffer ){
+            /* couldn't get pre ds 8 interfaces for some reason. clean up. */
+            if( stream->pDirectSoundInputBuffer )
+            {
+                IUnknown_Release( stream->pDirectSoundInputBuffer );
+                stream->pDirectSoundInputBuffer = NULL;
+            }
+
+            if( stream->pDirectSoundOutputBuffer )
+            {
+                IUnknown_Release( stream->pDirectSoundOutputBuffer );
+                stream->pDirectSoundOutputBuffer = NULL;
+            }
+            
+            IUnknown_Release( stream->pDirectSoundFullDuplex8 );
+            stream->pDirectSoundFullDuplex8 = NULL;
+        }
     }
     else
     {
-        minLatencyMsec = PaWinDS_GetMinSystemLatency();
-#if PA_USE_HIGH_LATENCY
-        PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
-#endif
-
+        PA_DEBUG(("DirectSoundFullDuplexCreate failed. hr=%d\n", hr));
     }
 
-    return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC);
+    return hr;
 }
+#endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
 
 
-static HRESULT InitInputBuffer( PaWinDsStream *stream, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask )
+static HRESULT InitInputBuffer( PaWinDsStream *stream, 
+                               PaWinDsDeviceInfo *device, 
+                               PaSampleFormat sampleFormat, 
+                               unsigned long nFrameRate, 
+                               WORD nChannels, 
+                               int bytesPerBuffer, 
+                               PaWinWaveFormatChannelMask channelMask )
 {
     DSCBUFFERDESC  captureDesc;
     PaWinWaveFormat waveFormat;
     HRESULT        result;
     
-    stream->bytesPerInputFrame = nChannels * Pa_GetSampleSize(sampleFormat);
+    if( (result = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( 
+            device->lpGUID, &stream->pDirectSoundCapture, NULL) ) != DS_OK ){
+         ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n"));
+         return result;
+    }
 
-    stream->inputSize = bytesPerBuffer;
-    // ----------------------------------------------------------------------
     // Setup the secondary buffer description
     ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
     captureDesc.dwSize = sizeof(DSCBUFFERDESC);
-    captureDesc.dwFlags =  0;
+    captureDesc.dwFlags = 0;
     captureDesc.dwBufferBytes = bytesPerBuffer;
     captureDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat;
     
@@ -1402,12 +1596,14 @@ static HRESULT InitInputBuffer( PaWinDsStream *stream, PaSampleFormat sampleForm
 
     // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
     PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels, 
-                sampleFormat, nFrameRate, channelMask );
+                sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
+                nFrameRate, channelMask );
 
     if( IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
                   &captureDesc, &stream->pDirectSoundInputBuffer, NULL) != DS_OK )
     {
-        PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat, nFrameRate );
+        PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat, 
+                PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
 
         if ((result = IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
                     &captureDesc, &stream->pDirectSoundInputBuffer, NULL)) != DS_OK) return result;
@@ -1418,28 +1614,23 @@ static HRESULT InitInputBuffer( PaWinDsStream *stream, PaSampleFormat sampleForm
 }
 
 
-static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask )
+static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, 
+                                PaSampleFormat sampleFormat, unsigned long nFrameRate, 
+                                WORD nChannels, int bytesPerBuffer, 
+                                PaWinWaveFormatChannelMask channelMask )
 {
-    /** @todo FIXME: if InitOutputBuffer returns an error I'm not sure it frees all resources cleanly */
-
-    DWORD          dwDataLen;
-    DWORD          playCursor;
     HRESULT        result;
-    LPDIRECTSOUNDBUFFER pPrimaryBuffer;
     HWND           hWnd;
     HRESULT        hr;
     PaWinWaveFormat waveFormat;
     DSBUFFERDESC   primaryDesc;
     DSBUFFERDESC   secondaryDesc;
-    unsigned char* pDSBuffData;
-    LARGE_INTEGER  counterFrequency;
-    int bytesPerSample = Pa_GetSampleSize(sampleFormat);
-
-    stream->outputBufferSizeBytes = bytesPerBuffer;
-    stream->outputIsRunning = FALSE;
-    stream->outputUnderflowCount = 0;
-    stream->dsw_framesWritten = 0;
-    stream->bytesPerOutputFrame = nChannels * bytesPerSample;
+    
+    if( (hr = paWinDsDSoundEntryPoints.DirectSoundCreate( 
+                device->lpGUID, &stream->pDirectSound, NULL )) != DS_OK ){
+        ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n"));
+        return hr;
+    }
 
     // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the
     // applications's window. Also if that window is closed before the Buffer is closed
@@ -1454,7 +1645,7 @@ static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaSampleFormat sampleFor
     hWnd = GetDesktopWindow();
 
     // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz.
-    // Exclusize also prevents unexpected sounds from other apps during a performance.
+    // exclusive also prevents unexpected sounds from other apps during a performance.
     if ((hr = IDirectSound_SetCooperativeLevel( stream->pDirectSound,
               hWnd, DSSCL_EXCLUSIVE)) != DS_OK)
     {
@@ -1472,60 +1663,177 @@ static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaSampleFormat sampleFor
     primaryDesc.lpwfxFormat   = NULL;
     // Create the buffer
     if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
-                  &primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) return result;
+                  &primaryDesc, &stream->pDirectSoundPrimaryBuffer, NULL)) != DS_OK)
+        goto error;
 
     // Set the primary buffer's format
 
     // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
     PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels, 
-                sampleFormat, nFrameRate, channelMask );
+                sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
+                nFrameRate, channelMask );
 
-    if( IDirectSoundBuffer_SetFormat( pPrimaryBuffer, (WAVEFORMATEX*)&waveFormat) != DS_OK )
+    if( IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat) != DS_OK )
     {
-        PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat, nFrameRate );
+        PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat, 
+                PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
 
-        if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, (WAVEFORMATEX*)&waveFormat)) != DS_OK) return result;
+        if((result = IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat)) != DS_OK)
+            goto error;
     }
 
     // ----------------------------------------------------------------------
     // Setup the secondary buffer description
     ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
     secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
-    secondaryDesc.dwFlags =  DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+    secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
     secondaryDesc.dwBufferBytes = bytesPerBuffer;
-    secondaryDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat;
+    secondaryDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat; /* waveFormat contains whatever format was negotiated for the primary buffer above */
     // Create the secondary buffer
     if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
-                  &secondaryDesc, &stream->pDirectSoundOutputBuffer, NULL)) != DS_OK) return result;
-    // Lock the DS buffer
-    if ((result = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, 0, stream->outputBufferSizeBytes, (LPVOID*)&pDSBuffData,
-                                           &dwDataLen, NULL, 0, 0)) != DS_OK) return result;
-    // Zero the DS buffer
-    ZeroMemory(pDSBuffData, dwDataLen);
-    // Unlock the DS buffer
-    if ((result = IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result;
-    if( QueryPerformanceFrequency( &counterFrequency ) )
-    {
-        int framesInBuffer = bytesPerBuffer / (nChannels * bytesPerSample);
-        stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate;
-    }
-    else
+                  &secondaryDesc, &stream->pDirectSoundOutputBuffer, NULL)) != DS_OK)
+      goto error;
+    
+    return DS_OK;
+
+error:
+
+    if( stream->pDirectSoundPrimaryBuffer )
     {
-        stream->perfCounterTicksPerBuffer.QuadPart = 0;
+        IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
+        stream->pDirectSoundPrimaryBuffer = NULL;
     }
-    // Let DSound set the starting write position because if we set it to zero, it looks like the
-    // buffer is full to begin with. This causes a long pause before sound starts when using large buffers.
-    hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
-            &playCursor, &stream->outputBufferWriteOffsetBytes );
-    if( hr != DS_OK )
+
+    return result;
+}
+
+
+static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames, 
+                                    unsigned long *pollingPeriodFrames,
+                                    int isFullDuplex,
+                                    unsigned long suggestedInputLatencyFrames,
+                                    unsigned long suggestedOutputLatencyFrames,
+                                    double sampleRate, unsigned long userFramesPerBuffer )
+{
+    unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS);
+    
+    if( userFramesPerBuffer == paFramesPerBufferUnspecified )
     {
-        return hr;
+        unsigned long targetBufferingLatencyFrames = max( suggestedInputLatencyFrames, suggestedOutputLatencyFrames );
+
+        *pollingPeriodFrames = targetBufferingLatencyFrames / 4;
+        if( *pollingPeriodFrames < minimumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = minimumPollingPeriodFrames;
+        }
+        else if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = maximumPollingPeriodFrames;
+        }
+
+        *hostBufferSizeFrames = *pollingPeriodFrames 
+                + max( *pollingPeriodFrames + pollingJitterFrames, targetBufferingLatencyFrames);
+    }
+    else
+    {
+        unsigned long targetBufferingLatencyFrames = suggestedInputLatencyFrames;
+        if( isFullDuplex )
+        {
+            /* In full duplex streams we know that the buffer adapter adds userFramesPerBuffer
+               extra fixed latency. so we subtract it here as a fixed latency before computing
+               the buffer size. being careful not to produce an unrepresentable negative result.
+               
+               Note: this only works as expected if output latency is greater than input latency.
+               Otherwise we use input latency anyway since we do max(in,out).
+            */
+
+            if( userFramesPerBuffer < suggestedOutputLatencyFrames )
+            {
+                unsigned long adjustedSuggestedOutputLatencyFrames = 
+                        suggestedOutputLatencyFrames - userFramesPerBuffer;
+
+                /* maximum of input and adjusted output suggested latency */
+                if( adjustedSuggestedOutputLatencyFrames > targetBufferingLatencyFrames )
+                    targetBufferingLatencyFrames = adjustedSuggestedOutputLatencyFrames;
+            }
+        }
+        else
+        {
+            /* maximum of input and output suggested latency */
+            if( suggestedOutputLatencyFrames > suggestedInputLatencyFrames )
+                targetBufferingLatencyFrames = suggestedOutputLatencyFrames;
+        }   
+
+        *hostBufferSizeFrames = userFramesPerBuffer 
+                + max( userFramesPerBuffer + pollingJitterFrames, targetBufferingLatencyFrames);
+
+        *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), targetBufferingLatencyFrames / 16 );
+
+        if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = maximumPollingPeriodFrames;
+        }
+    } 
+}
+
+
+static void CalculatePollingPeriodFrames( unsigned long hostBufferSizeFrames, 
+                                    unsigned long *pollingPeriodFrames,
+                                    double sampleRate, unsigned long userFramesPerBuffer )
+{
+    unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS);
+
+    *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), hostBufferSizeFrames / 16 );
+
+    if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+    {
+        *pollingPeriodFrames = maximumPollingPeriodFrames;
     }
-    stream->dsw_framesWritten = stream->outputBufferWriteOffsetBytes / stream->bytesPerOutputFrame;
-    /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */
-    return DS_OK;
 }
 
+
+static void SetStreamInfoLatencies( PaWinDsStream *stream, 
+                                   unsigned long userFramesPerBuffer, 
+                                   unsigned long pollingPeriodFrames,
+                                   double sampleRate )
+{
+    /* compute the stream info actual latencies based on framesPerBuffer, polling period, hostBufferSizeFrames, 
+    and the configuration of the buffer processor */
+
+    unsigned long effectiveFramesPerBuffer = (userFramesPerBuffer == paFramesPerBufferUnspecified)
+                                             ? pollingPeriodFrames
+                                             : userFramesPerBuffer;
+
+    if( stream->bufferProcessor.inputChannelCount > 0 )
+    {
+        /* stream info input latency is the minimum buffering latency 
+           (unlike suggested and default which are *maximums*) */
+        stream->streamRepresentation.streamInfo.inputLatency =
+                (double)(PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)
+                    + effectiveFramesPerBuffer) / sampleRate;
+    }
+    else
+    {
+        stream->streamRepresentation.streamInfo.inputLatency = 0;
+    }
+
+    if( stream->bufferProcessor.outputChannelCount > 0 )
+    {
+        stream->streamRepresentation.streamInfo.outputLatency =
+                (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
+                    + (stream->hostBufferSizeFrames - effectiveFramesPerBuffer)) / sampleRate;
+    }
+    else
+    {
+        stream->streamRepresentation.streamInfo.outputLatency = 0;
+    }
+}
+
+
 /***********************************************************************************/
 /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
 
@@ -1542,14 +1850,19 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     PaError result = paNoError;
     PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
     PaWinDsStream *stream = 0;
+    int bufferProcessorIsInitialized = 0;
+    int streamRepresentationIsInitialized = 0;
     PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo;
     PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
     int inputChannelCount, outputChannelCount;
     PaSampleFormat inputSampleFormat, outputSampleFormat;
     PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+    int userRequestedHostInputBufferSizeFrames = 0;
+    int userRequestedHostOutputBufferSizeFrames = 0;
     unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames;
     PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
     PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask;
+    unsigned long pollingPeriodFrames = 0;
 
     if( inputParameters )
     {
@@ -1578,6 +1891,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 		result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
 		if( result != paNoError ) return result;
 
+        if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+            userRequestedHostInputBufferSizeFrames = inputStreamInfo->framesPerBuffer;
+
         if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseChannelMask )
             inputChannelMask = inputStreamInfo->channelMask;
         else
@@ -1615,6 +1931,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 		result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
 		if( result != paNoError ) return result;   
 
+        if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+            userRequestedHostOutputBufferSizeFrames = outputStreamInfo->framesPerBuffer;
+
         if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseChannelMask )
             outputChannelMask = outputStreamInfo->channelMask;
         else
@@ -1627,6 +1946,16 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         suggestedOutputLatencyFrames = 0;
     }
 
+    /*
+        If low level host buffer size is specified for both input and output
+        the current code requires the sizes to match.
+    */
+
+    if( (userRequestedHostInputBufferSizeFrames > 0 && userRequestedHostOutputBufferSizeFrames > 0)
+            && userRequestedHostInputBufferSizeFrames != userRequestedHostOutputBufferSizeFrames )
+        return paIncompatibleHostApiSpecificStreamInfo;
+
+
 
     /*
         IMPLEMENT ME:
@@ -1678,6 +2007,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                                                &winDsHostApi->blockingStreamInterface, streamCallback, userData );
     }
     
+    streamRepresentationIsInitialized = 1;
+
+    stream->streamFlags = streamFlags;
+
     PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
 
 
@@ -1685,7 +2018,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     {
         /* IMPLEMENT ME - establish which  host formats are available */
         PaSampleFormat nativeInputFormats = paInt16;
-        //PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32;
+        /* PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
 
         hostInputSampleFormat =
             PaUtil_SelectClosestAvailableFormat( nativeInputFormats, inputParameters->sampleFormat );
@@ -1699,7 +2032,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     {
         /* IMPLEMENT ME - establish which  host formats are available */
         PaSampleFormat nativeOutputFormats = paInt16;
-        //PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32;
+        /* PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
 
         hostOutputSampleFormat =
             PaUtil_SelectClosestAvailableFormat( nativeOutputFormats, outputParameters->sampleFormat );
@@ -1713,113 +2046,124 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                     inputChannelCount, inputSampleFormat, hostInputSampleFormat,
                     outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
                     sampleRate, streamFlags, framesPerBuffer,
-                    framesPerBuffer, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */
+                    0, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */
                 /* This next mode is required because DS can split the host buffer when it wraps around. */
                     paUtilVariableHostBufferSizePartialUsageAllowed,
                     streamCallback, userData );
     if( result != paNoError )
         goto error;
 
+    bufferProcessorIsInitialized = 1;
 
-    stream->streamRepresentation.streamInfo.inputLatency =
-            PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor);   /* FIXME: not initialised anywhere else */
-    stream->streamRepresentation.streamInfo.outputLatency =
-            PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor);    /* FIXME: not initialised anywhere else */
-    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
-
-    
+   
 /* DirectSound specific initialization */ 
     {
         HRESULT          hr;
-        int              bytesPerDirectSoundBuffer;
-        int              userLatencyFrames;
-        int              minLatencyFrames;
+        unsigned long    integerSampleRate = (unsigned long) (sampleRate + 0.5);
+        
+        stream->processingCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL );
+        if( stream->processingCompleted == NULL )
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
 
+#ifdef PA_WIN_DS_USE_WMME_TIMER
         stream->timerID = 0;
+#endif
 
-    /* Get system minimum latency. */
-        minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate );
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+        stream->waitableTimer = (HANDLE)CreateWaitableTimer( 0, FALSE, NULL );
+        if( stream->waitableTimer == NULL )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#endif
 
-    /* Let user override latency by passing latency parameter. */
-        userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames)
-                    ? suggestedInputLatencyFrames
-                    : suggestedOutputLatencyFrames;
-        if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames;
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+		stream->processingThreadCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL );
+        if( stream->processingThreadCompleted == NULL )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#endif
+
+        /* set up i/o parameters */
 
-    /* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */
-        if( framesPerBuffer == paFramesPerBufferUnspecified )
+        if( userRequestedHostInputBufferSizeFrames > 0 || userRequestedHostOutputBufferSizeFrames > 0 )
         {
-        /* App support variable framesPerBuffer */
-            stream->framesPerDSBuffer = minLatencyFrames;
+            /* use low level parameters */
 
-            stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate;
+            /* since we use the same host buffer size for input and output
+               we choose the highest user specified value.
+            */
+            stream->hostBufferSizeFrames = max( userRequestedHostInputBufferSizeFrames, userRequestedHostOutputBufferSizeFrames );
+
+            CalculatePollingPeriodFrames( 
+                    stream->hostBufferSizeFrames, &pollingPeriodFrames,
+                    sampleRate, framesPerBuffer );
         }
         else
         {
-        /* Round up to number of buffers needed to guarantee that latency. */
-            int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer;
-            if( numUserBuffers < 1 ) numUserBuffers = 1;
-            numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */
-            stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers;
-
-            stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate;
+            CalculateBufferSettings( &stream->hostBufferSizeFrames, &pollingPeriodFrames,
+                    /* isFullDuplex = */ (inputParameters && outputParameters),
+                    suggestedInputLatencyFrames,
+                    suggestedOutputLatencyFrames, 
+                    sampleRate, framesPerBuffer );
         }
 
-        {
-            /** @todo REVIEW: this calculation seems incorrect to me - rossb. */
-            int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate);
-            PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency ));
-        }
+        stream->pollingPeriodSeconds = pollingPeriodFrames / sampleRate;
+
+        DBUG(("DirectSound host buffer size frames: %d, polling period seconds: %f, @ sr: %f\n", 
+                stream->hostBufferSizeFrames, stream->pollingPeriodSeconds, sampleRate ));
 
 
         /* ------------------ OUTPUT */
         if( outputParameters )
         {
+            LARGE_INTEGER  counterFrequency;
+
             /*
             PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ];
             DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device));
             */
             
-            int bytesPerSample = Pa_GetSampleSize(hostOutputSampleFormat);
-            bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * bytesPerSample;
-            if( bytesPerDirectSoundBuffer < DSBSIZE_MIN )
+            int sampleSizeBytes = Pa_GetSampleSize(hostOutputSampleFormat);
+            stream->outputFrameSizeBytes = outputParameters->channelCount * sampleSizeBytes;
+
+            stream->outputBufferSizeBytes = stream->hostBufferSizeFrames * stream->outputFrameSizeBytes;
+            if( stream->outputBufferSizeBytes < DSBSIZE_MIN )
             {
                 result = paBufferTooSmall;
                 goto error;
             }
-            else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX )
+            else if( stream->outputBufferSizeBytes > DSBSIZE_MAX )
             {
                 result = paBufferTooBig;
                 goto error;
             }
 
-            hr = paWinDsDSoundEntryPoints.DirectSoundCreate( 
-                ((PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device])->lpGUID,
-                &stream->pDirectSound, NULL );
+            /* Calculate value used in latency calculation to avoid real-time divides. */
+            stream->secondsPerHostByte = 1.0 /
+                (stream->bufferProcessor.bytesPerHostOutputSample *
+                outputChannelCount * sampleRate);
 
-            if( hr != DS_OK )
+            stream->outputIsRunning = FALSE;
+            stream->outputUnderflowCount = 0;
+            
+            /* perfCounterTicksPerBuffer is used by QueryOutputSpace for overflow detection */
+            if( QueryPerformanceFrequency( &counterFrequency ) )
             {
-                ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n"));
-                result = paUnanticipatedHostError;
-                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
-                goto error;
+                stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * stream->hostBufferSizeFrames) / integerSampleRate;
             }
-            hr = InitOutputBuffer( stream,
-                                       hostOutputSampleFormat,
-                                       (unsigned long) (sampleRate + 0.5),
-                                       (WORD)outputParameters->channelCount, bytesPerDirectSoundBuffer,
-                                       outputChannelMask );
-            DBUG(("InitOutputBuffer() returns %x\n", hr));
-            if( hr != DS_OK )
+            else
             {
-                result = paUnanticipatedHostError;
-                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
-                goto error;
+                stream->perfCounterTicksPerBuffer.QuadPart = 0;
             }
-            /* Calculate value used in latency calculation to avoid real-time divides. */
-            stream->secondsPerHostByte = 1.0 /
-                (stream->bufferProcessor.bytesPerHostOutputSample *
-                outputChannelCount * sampleRate);
         }
 
         /* ------------------ INPUT */
@@ -1830,33 +2174,85 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
             DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device));
             */
             
-            int bytesPerSample = Pa_GetSampleSize(hostInputSampleFormat);
-            bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * bytesPerSample;
-            if( bytesPerDirectSoundBuffer < DSBSIZE_MIN )
+            int sampleSizeBytes = Pa_GetSampleSize(hostInputSampleFormat);
+            stream->inputFrameSizeBytes = inputParameters->channelCount * sampleSizeBytes;
+
+            stream->inputBufferSizeBytes = stream->hostBufferSizeFrames * stream->inputFrameSizeBytes;
+            if( stream->inputBufferSizeBytes < DSBSIZE_MIN )
             {
                 result = paBufferTooSmall;
                 goto error;
             }
-            else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX )
+            else if( stream->inputBufferSizeBytes > DSBSIZE_MAX )
             {
                 result = paBufferTooBig;
                 goto error;
             }
+        }
+
+        /* open/create the DirectSound buffers */
 
-            hr = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( 
-                    ((PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device])->lpGUID,
-                    &stream->pDirectSoundCapture, NULL );
+        /* interface ptrs should be zeroed when stream is zeroed. */
+        assert( stream->pDirectSoundCapture == NULL );
+        assert( stream->pDirectSoundInputBuffer == NULL );
+        assert( stream->pDirectSound == NULL );
+        assert( stream->pDirectSoundPrimaryBuffer == NULL );
+        assert( stream->pDirectSoundOutputBuffer == NULL );
+        
+
+        if( inputParameters && outputParameters )
+        {
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+            /* try to use the full-duplex DX8 API to create the buffers.
+                if that fails we fall back to the half-duplex API below */
+
+            hr = InitFullDuplexInputOutputBuffers( stream,
+                                       (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
+                                       hostInputSampleFormat,
+                                       (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
+                                       inputChannelMask,
+                                       (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
+                                       hostOutputSampleFormat,
+                                       (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
+                                       outputChannelMask,
+                                       integerSampleRate
+                                        );
+            DBUG(("InitFullDuplexInputOutputBuffers() returns %x\n", hr));
+            /* ignore any error returned by InitFullDuplexInputOutputBuffers. 
+                we retry opening the buffers below */
+#endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
+        }
+
+        /*  create half duplex buffers. also used for full-duplex streams which didn't 
+            succeed when using the full duplex API. that could happen because
+            DX8 or greater isnt installed, the i/o devices aren't the same 
+            physical device. etc.
+        */
+
+        if( outputParameters && !stream->pDirectSoundOutputBuffer )
+        {
+            hr = InitOutputBuffer( stream,
+                                       (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
+                                       hostOutputSampleFormat,
+                                       integerSampleRate,
+                                       (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
+                                       outputChannelMask );
+            DBUG(("InitOutputBuffer() returns %x\n", hr));
             if( hr != DS_OK )
             {
-                ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n"));
                 result = paUnanticipatedHostError;
                 PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
                 goto error;
             }
+        }
+
+        if( inputParameters && !stream->pDirectSoundInputBuffer )
+        {
             hr = InitInputBuffer( stream,
+                                      (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
                                       hostInputSampleFormat,
-                                      (unsigned long) (sampleRate + 0.5),
-                                      (WORD)inputParameters->channelCount, bytesPerDirectSoundBuffer,
+                                      integerSampleRate,
+                                      (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
                                       inputChannelMask );
             DBUG(("InitInputBuffer() returns %x\n", hr));
             if( hr != DS_OK )
@@ -1867,16 +2263,79 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                 goto error;
             }
         }
-
     }
 
+    SetStreamInfoLatencies( stream, framesPerBuffer, pollingPeriodFrames, sampleRate );
+
+    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
     *s = (PaStream*)stream;
 
     return result;
 
 error:
     if( stream )
+    {
+        if( stream->processingCompleted != NULL )
+            CloseHandle( stream->processingCompleted );
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+        if( stream->waitableTimer != NULL )
+            CloseHandle( stream->waitableTimer );
+#endif
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+        if( stream->processingThreadCompleted != NULL )
+            CloseHandle( stream->processingThreadCompleted );
+#endif
+
+        if( stream->pDirectSoundOutputBuffer )
+        {
+            IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
+            IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer );
+            stream->pDirectSoundOutputBuffer = NULL;
+        }
+
+        if( stream->pDirectSoundPrimaryBuffer )
+        {
+            IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
+            stream->pDirectSoundPrimaryBuffer = NULL;
+        }
+
+        if( stream->pDirectSoundInputBuffer )
+        {
+            IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
+            IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer );
+            stream->pDirectSoundInputBuffer = NULL;
+        }
+
+        if( stream->pDirectSoundCapture )
+        {
+            IDirectSoundCapture_Release( stream->pDirectSoundCapture );
+            stream->pDirectSoundCapture = NULL;
+        }
+
+        if( stream->pDirectSound )
+        {
+            IDirectSound_Release( stream->pDirectSound );
+            stream->pDirectSound = NULL;
+        }
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+        if( stream->pDirectSoundFullDuplex8 )
+        {
+            IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 );
+            stream->pDirectSoundFullDuplex8 = NULL;
+        }
+#endif
+        if( bufferProcessorIsInitialized )
+            PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+
+        if( streamRepresentationIsInitialized )
+            PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+
         PaUtil_FreeMemory( stream );
+    }
 
     return result;
 }
@@ -1901,9 +2360,11 @@ static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
     {
         return hr;
     }
+
     // Determine size of gap between playIndex and WriteIndex that we cannot write into.
     playWriteGap = writeCursor - playCursor;
     if( playWriteGap < 0 ) playWriteGap += stream->outputBufferSizeBytes; // unwrap
+
     /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */
     /* Attempt to detect playCursor wrap-around and correct it. */
     if( stream->outputIsRunning && (stream->perfCounterTicksPerBuffer.QuadPart != 0) )
@@ -1914,13 +2375,16 @@ static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
         long            bytesPlayed;
         long            bytesExpected;
         long            buffersWrapped;
+
         QueryPerformanceCounter( &currentTime );
         elapsedTime.QuadPart = currentTime.QuadPart - stream->previousPlayTime.QuadPart;
         stream->previousPlayTime = currentTime;
+
         /* How many bytes does DirectSound say have been played. */
         bytesPlayed = playCursor - stream->previousPlayCursor;
         if( bytesPlayed < 0 ) bytesPlayed += stream->outputBufferSizeBytes; // unwrap
         stream->previousPlayCursor = playCursor;
+
         /* Calculate how many bytes we would have expected to been played by now. */
         bytesExpected = (long) ((elapsedTime.QuadPart * stream->outputBufferSizeBytes) / stream->perfCounterTicksPerBuffer.QuadPart);
         buffersWrapped = (bytesExpected - bytesPlayed) / stream->outputBufferSizeBytes;
@@ -1929,11 +2393,10 @@ static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
             playCursor += (buffersWrapped * stream->outputBufferSizeBytes);
             bytesPlayed += (buffersWrapped * stream->outputBufferSizeBytes);
         }
-        /* Maintain frame output cursor. */
-        stream->framesPlayed += (bytesPlayed / stream->bytesPerOutputFrame);
     }
     numBytesEmpty = playCursor - stream->outputBufferWriteOffsetBytes;
     if( numBytesEmpty < 0 ) numBytesEmpty += stream->outputBufferSizeBytes; // unwrap offset
+
     /* Have we underflowed? */
     if( numBytesEmpty > (stream->outputBufferSizeBytes - playWriteGap) )
     {
@@ -1941,6 +2404,17 @@ static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
         {
             stream->outputUnderflowCount += 1;
         }
+
+        /*
+            From MSDN:
+                The write cursor indicates the position at which it is safe  
+            to write new data to the buffer. The write cursor always leads the
+            play cursor, typically by about 15 milliseconds' worth of audio
+            data.
+                It is always safe to change data that is behind the position 
+            indicated by the lpdwCurrentPlayCursor parameter.
+        */
+
         stream->outputBufferWriteOffsetBytes = writeCursor;
         numBytesEmpty = stream->outputBufferSizeBytes - playWriteGap;
     }
@@ -1949,20 +2423,20 @@ static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
 }
 
 /***********************************************************************************/
-static PaError Pa_TimeSlice( PaWinDsStream *stream )
+static int TimeSlice( PaWinDsStream *stream )
 {
-    PaError           result = 0;   /* FIXME: this should be declared int and this function should also return that type (same as stream callback return type)*/
     long              numFrames = 0;
     long              bytesEmpty = 0;
     long              bytesFilled = 0;
     long              bytesToXfer = 0;
-    long              framesToXfer = 0;
+    long              framesToXfer = 0; /* the number of frames we'll process this tick */
     long              numInFramesReady = 0;
     long              numOutFramesReady = 0;
     long              bytesProcessed;
     HRESULT           hresult;
     double            outputLatency = 0;
-    PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */
+    double            inputLatency = 0;
+    PaStreamCallbackTimeInfo timeInfo = {0,0,0};
     
 /* Input */
     LPBYTE            lpInBuf1 = NULL;
@@ -1989,13 +2463,14 @@ static PaError Pa_TimeSlice( PaWinDsStream *stream )
         if( hr == DS_OK )
         {
             filled = readPos - stream->readOffset;
-            if( filled < 0 ) filled += stream->inputSize; // unwrap offset
+            if( filled < 0 ) filled += stream->inputBufferSizeBytes; // unwrap offset
             bytesFilled = filled;
+
+            inputLatency = ((double)bytesFilled) * stream->secondsPerHostByte;
         }
             // FIXME: what happens if IDirectSoundCaptureBuffer_GetCurrentPosition fails?
 
-        framesToXfer = numInFramesReady = bytesFilled / stream->bytesPerInputFrame;
-        outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte;
+        framesToXfer = numInFramesReady = bytesFilled / stream->inputFrameSizeBytes; 
 
         /** @todo Check for overflow */
     }
@@ -2005,28 +2480,36 @@ static PaError Pa_TimeSlice( PaWinDsStream *stream )
     {
         UINT previousUnderflowCount = stream->outputUnderflowCount;
         QueryOutputSpace( stream, &bytesEmpty );
-        framesToXfer = numOutFramesReady = bytesEmpty / stream->bytesPerOutputFrame;
+        framesToXfer = numOutFramesReady = bytesEmpty / stream->outputFrameSizeBytes;
 
         /* Check for underflow */
+		/* FIXME QueryOutputSpace should not adjust underflow count as a side effect. 
+			A query function should be a const operator on the stream and return a flag on underflow. */
         if( stream->outputUnderflowCount != previousUnderflowCount )
             stream->callbackFlags |= paOutputUnderflow;
+
+        /* We are about to compute audio into the first byte of empty space in the output buffer.
+           This audio will reach the DAC after all of the current (non-empty) audio
+           in the buffer has played. Therefore the output time is the current time
+           plus the time it takes to play the non-empty bytes in the buffer,
+           computed here:
+        */
+        outputLatency = ((double)(stream->outputBufferSizeBytes - bytesEmpty)) * stream->secondsPerHostByte;
     }
 
-    if( (numInFramesReady > 0) && (numOutFramesReady > 0) )
+    /* if it's a full duplex stream, set framesToXfer to the minimum of input and output frames ready */
+    if( stream->bufferProcessor.inputChannelCount > 0 && stream->bufferProcessor.outputChannelCount > 0 )
     {
         framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady;
     }
 
     if( framesToXfer > 0 )
     {
-
         PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
 
     /* The outputBufferDacTime parameter should indicates the time at which
         the first sample of the output buffer is heard at the DACs. */
         timeInfo.currentTime = PaUtil_GetTime();
-        timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency;
-
 
         PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags );
         stream->callbackFlags = 0;
@@ -2034,7 +2517,9 @@ static PaError Pa_TimeSlice( PaWinDsStream *stream )
     /* Input */
         if( stream->bufferProcessor.inputChannelCount > 0 )
         {
-            bytesToXfer = framesToXfer * stream->bytesPerInputFrame;
+            timeInfo.inputBufferAdcTime = timeInfo.currentTime - inputLatency; 
+
+            bytesToXfer = framesToXfer * stream->inputFrameSizeBytes;
             hresult = IDirectSoundCaptureBuffer_Lock ( stream->pDirectSoundInputBuffer,
                 stream->readOffset, bytesToXfer,
                 (void **) &lpInBuf1, &dwInSize1,
@@ -2042,18 +2527,19 @@ static PaError Pa_TimeSlice( PaWinDsStream *stream )
             if (hresult != DS_OK)
             {
                 ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult));
-                result = paUnanticipatedHostError;
-                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult );
+                /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */
+                PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */
+                stream->callbackResult = paComplete;
                 goto error2;
             }
 
-            numFrames = dwInSize1 / stream->bytesPerInputFrame;
+            numFrames = dwInSize1 / stream->inputFrameSizeBytes;
             PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames );
             PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 );
         /* Is input split into two regions. */
             if( dwInSize2 > 0 )
             {
-                numFrames = dwInSize2 / stream->bytesPerInputFrame;
+                numFrames = dwInSize2 / stream->inputFrameSizeBytes;
                 PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames );
                 PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 );
             }
@@ -2062,7 +2548,14 @@ static PaError Pa_TimeSlice( PaWinDsStream *stream )
     /* Output */
         if( stream->bufferProcessor.outputChannelCount > 0 )
         {
-            bytesToXfer = framesToXfer * stream->bytesPerOutputFrame;
+            /*
+			We don't currently add outputLatency here because it appears to produce worse
+			results than not adding it. Need to do more testing to verify this.
+            */
+            /* timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; */
+            timeInfo.outputBufferDacTime = timeInfo.currentTime;
+
+            bytesToXfer = framesToXfer * stream->outputFrameSizeBytes;
             hresult = IDirectSoundBuffer_Lock ( stream->pDirectSoundOutputBuffer,
                 stream->outputBufferWriteOffsetBytes, bytesToXfer,
                 (void **) &lpOutBuf1, &dwOutSize1,
@@ -2070,26 +2563,26 @@ static PaError Pa_TimeSlice( PaWinDsStream *stream )
             if (hresult != DS_OK)
             {
                 ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult));
-                result = paUnanticipatedHostError;
-                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult );
+                /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */
+                PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */
+                stream->callbackResult = paComplete;
                 goto error1;
             }
 
-            numFrames = dwOutSize1 / stream->bytesPerOutputFrame;
+            numFrames = dwOutSize1 / stream->outputFrameSizeBytes;
             PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames );
             PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 );
 
         /* Is output split into two regions. */
             if( dwOutSize2 > 0 )
             {
-                numFrames = dwOutSize2 / stream->bytesPerOutputFrame;
+                numFrames = dwOutSize2 / stream->outputFrameSizeBytes;
                 PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames );
                 PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 );
             }
         }
 
-        result = paContinue;
-        numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &result );
+        numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &stream->callbackResult );
         stream->framesWritten += numFrames;
         
         if( stream->bufferProcessor.outputChannelCount > 0 )
@@ -2097,10 +2590,9 @@ static PaError Pa_TimeSlice( PaWinDsStream *stream )
         /* FIXME: an underflow could happen here */
 
         /* Update our buffer offset and unlock sound buffer */
-            bytesProcessed = numFrames * stream->bytesPerOutputFrame;
+            bytesProcessed = numFrames * stream->outputFrameSizeBytes;
             stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + bytesProcessed) % stream->outputBufferSizeBytes;
             IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
-            stream->dsw_framesWritten += numFrames;
         }
 
 error1:
@@ -2109,17 +2601,24 @@ error1:
         /* FIXME: an overflow could happen here */
 
         /* Update our buffer offset and unlock sound buffer */
-            bytesProcessed = numFrames * stream->bytesPerInputFrame;
-            stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputSize;
+            bytesProcessed = numFrames * stream->inputFrameSizeBytes;
+            stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputBufferSizeBytes;
             IDirectSoundCaptureBuffer_Unlock( stream->pDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
         }
 error2:
 
-        PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames );
+        PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames );        
+    }
 
+    if( stream->callbackResult == paComplete && !PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
+    {
+        /* don't return completed until the buffer processor has been drained */
+        return paContinue;
+    }
+    else
+    {
+        return stream->callbackResult;
     }
-    
-    return result;
 }
 /*******************************************************************/
 
@@ -2131,7 +2630,7 @@ static HRESULT ZeroAvailableOutputSpace( PaWinDsStream *stream )
     DWORD dwsize1 = 0;
     DWORD dwsize2 = 0;
     long  bytesEmpty;
-    hr = QueryOutputSpace( stream, &bytesEmpty ); // updates framesPlayed
+    hr = QueryOutputSpace( stream, &bytesEmpty );
     if (hr != DS_OK) return hr;
     if( bytesEmpty == 0 ) return DS_OK;
     // Lock free space in the DS
@@ -2149,15 +2648,17 @@ static HRESULT ZeroAvailableOutputSpace( PaWinDsStream *stream )
         // Update our buffer offset and unlock sound buffer
         stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + dwsize1 + dwsize2) % stream->outputBufferSizeBytes;
         IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2);
-        stream->dsw_framesWritten += bytesEmpty / stream->bytesPerOutputFrame;
+
+        stream->finalZeroBytesWritten += dwsize1 + dwsize2;
     }
     return hr;
 }
 
 
-static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2)
+static void CALLBACK TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2)
 {
     PaWinDsStream *stream;
+    int isFinished = 0;
 
     /* suppress unused variable warnings */
     (void) uID;
@@ -2172,41 +2673,115 @@ static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWO
     {
         if( stream->abortProcessing )
         {
-            stream->isActive = 0;
+            isFinished = 1;
         }
         else if( stream->stopProcessing )
         {
             if( stream->bufferProcessor.outputChannelCount > 0 )
             {
                 ZeroAvailableOutputSpace( stream );
-                /* clear isActive when all sound played */
-                if( stream->framesPlayed >= stream->framesWritten )
+                if( stream->finalZeroBytesWritten >= stream->outputBufferSizeBytes )
                 {
-                    stream->isActive = 0;
+                    /* once we've flushed the whole output buffer with zeros we know all data has been played */
+                    isFinished = 1;
                 }
             }
             else
             {
-                stream->isActive = 0;
+                isFinished = 1;
             }
         }
         else
         {
-            if( Pa_TimeSlice( stream ) != 0)  /* Call time slice independant of timing method. */
+            int callbackResult = TimeSlice( stream );
+            if( callbackResult != paContinue )
             {
-                /* FIXME implement handling of paComplete and paAbort if possible */
+                /* FIXME implement handling of paComplete and paAbort if possible 
+                   At the moment this should behave as if paComplete was called and 
+                   flush the buffer.
+                */
+
                 stream->stopProcessing = 1;
             }
         }
 
-        if( !stream->isActive )
+        if( isFinished )
         {
             if( stream->streamRepresentation.streamFinishedCallback != 0 )
                 stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+
+            stream->isActive = 0; /* don't set this until the stream really is inactive */
+            SetEvent( stream->processingCompleted );
         }
     }
 }
 
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+
+static void CALLBACK WaitableTimerAPCProc(
+   LPVOID lpArg,               // Data value
+   DWORD dwTimerLowValue,      // Timer low value
+   DWORD dwTimerHighValue )    // Timer high value
+
+{
+    (void)dwTimerLowValue;
+    (void)dwTimerHighValue;
+
+    TimerCallback( 0, 0, (DWORD_PTR)lpArg, 0, 0 );
+}
+
+#endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */
+
+
+PA_THREAD_FUNC ProcessingThreadProc( void *pArg )
+{
+    PaWinDsStream *stream = (PaWinDsStream *)pArg;
+    LARGE_INTEGER dueTime;
+    int timerPeriodMs;
+
+    timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND);
+    if( timerPeriodMs < 1 )
+        timerPeriodMs = 1;
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+    assert( stream->waitableTimer != NULL );
+
+    /* invoke first timeout immediately */
+    dueTime.LowPart = timerPeriodMs * 1000 * 10;
+    dueTime.HighPart = 0;
+
+    /* tick using waitable timer */
+    if( SetWaitableTimer( stream->waitableTimer, &dueTime, timerPeriodMs, WaitableTimerAPCProc, pArg, FALSE ) != 0 )
+    {
+        DWORD wfsoResult = 0;
+        do
+        {
+            /* wait for processingCompleted to be signaled or our timer APC to be called */
+            wfsoResult = WaitForSingleObjectEx( stream->processingCompleted, timerPeriodMs * 10, /* alertable = */ TRUE );
+
+        }while( wfsoResult == WAIT_TIMEOUT || wfsoResult == WAIT_IO_COMPLETION );
+    }
+
+    CancelWaitableTimer( stream->waitableTimer );
+
+#else
+
+    /* tick using WaitForSingleObject timout */
+    while ( WaitForSingleObject( stream->processingCompleted, timerPeriodMs ) == WAIT_TIMEOUT )
+    {
+        TimerCallback( 0, 0, (DWORD_PTR)pArg, 0, 0 );
+    }
+#endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */
+
+    SetEvent( stream->processingThreadCompleted );
+
+    return 0;
+}
+
+#endif /* !PA_WIN_DS_USE_WMME_TIMER */
+
 /***********************************************************************************
     When CloseStream() is called, the multi-api layer ensures that
     the stream has already been stopped or aborted.
@@ -2216,6 +2791,17 @@ static PaError CloseStream( PaStream* s )
     PaError result = paNoError;
     PaWinDsStream *stream = (PaWinDsStream*)s;
 
+    CloseHandle( stream->processingCompleted );
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+    if( stream->waitableTimer != NULL )
+        CloseHandle( stream->waitableTimer );
+#endif
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+	CloseHandle( stream->processingThreadCompleted );
+#endif
+
     // Cleanup the sound buffers
     if( stream->pDirectSoundOutputBuffer )
     {
@@ -2224,6 +2810,12 @@ static PaError CloseStream( PaStream* s )
         stream->pDirectSoundOutputBuffer = NULL;
     }
 
+    if( stream->pDirectSoundPrimaryBuffer )
+    {
+        IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
+        stream->pDirectSoundPrimaryBuffer = NULL;
+    }
+
     if( stream->pDirectSoundInputBuffer )
     {
         IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
@@ -2243,6 +2835,14 @@ static PaError CloseStream( PaStream* s )
         stream->pDirectSound = NULL;
     }
 
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+    if( stream->pDirectSoundFullDuplex8 )
+    {
+        IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 );
+        stream->pDirectSoundFullDuplex8 = NULL;
+    }
+#endif
+
     PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
     PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
     PaUtil_FreeMemory( stream );
@@ -2251,17 +2851,58 @@ static PaError CloseStream( PaStream* s )
 }
 
 /***********************************************************************************/
+static HRESULT ClearOutputBuffer( PaWinDsStream *stream )
+{
+    PaError          result = paNoError;
+    unsigned char*   pDSBuffData;
+    DWORD            dwDataLen;
+    HRESULT          hr;
+
+    hr = IDirectSoundBuffer_SetCurrentPosition( stream->pDirectSoundOutputBuffer, 0 );
+    DBUG(("PaHost_ClearOutputBuffer: IDirectSoundBuffer_SetCurrentPosition returned = 0x%X.\n", hr));
+    if( hr != DS_OK )
+        return hr;
+
+    // Lock the DS buffer
+    if ((hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, 0, stream->outputBufferSizeBytes, (LPVOID*)&pDSBuffData,
+                                           &dwDataLen, NULL, 0, 0)) != DS_OK )
+        return hr;
+
+    // Zero the DS buffer
+    ZeroMemory(pDSBuffData, dwDataLen);
+    // Unlock the DS buffer
+    if ((hr = IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK)
+        return hr;
+    
+    // Let DSound set the starting write position because if we set it to zero, it looks like the
+    // buffer is full to begin with. This causes a long pause before sound starts when using large buffers.
+    if ((hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
+            &stream->previousPlayCursor, &stream->outputBufferWriteOffsetBytes )) != DS_OK)
+        return hr;
+
+    /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */
+
+    return DS_OK;
+}
+
 static PaError StartStream( PaStream *s )
 {
     PaError          result = paNoError;
     PaWinDsStream   *stream = (PaWinDsStream*)s;
     HRESULT          hr;
-
+        
+    stream->callbackResult = paContinue;
     PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
     
+    ResetEvent( stream->processingCompleted );
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+	ResetEvent( stream->processingThreadCompleted );
+#endif
+
     if( stream->bufferProcessor.inputChannelCount > 0 )
     {
-        // Start the buffer playback
+        // Start the buffer capture
         if( stream->pDirectSoundInputBuffer != NULL ) // FIXME: not sure this check is necessary
         {
             hr = IDirectSoundCaptureBuffer_Start( stream->pDirectSoundInputBuffer, DSCBSTART_LOOPING );
@@ -2281,19 +2922,13 @@ static PaError StartStream( PaStream *s )
 
     stream->abortProcessing = 0;
     stream->stopProcessing = 0;
-    stream->isActive = 1;
 
     if( stream->bufferProcessor.outputChannelCount > 0 )
     {
-        /* Give user callback a chance to pre-fill buffer. REVIEW - i thought we weren't pre-filling, rb. */
-        result = Pa_TimeSlice( stream );
-        if( result != paNoError ) return result; // FIXME - what if finished?
-
         QueryPerformanceCounter( &stream->previousPlayTime );
-        stream->previousPlayCursor = 0;
-        stream->framesPlayed = 0;
-        hr = IDirectSoundBuffer_SetCurrentPosition( stream->pDirectSoundOutputBuffer, 0 );
-        DBUG(("PaHost_StartOutput: IDirectSoundBuffer_SetCurrentPosition returned = 0x%X.\n", hr));
+        stream->finalZeroBytesWritten = 0;
+
+        hr = ClearOutputBuffer( stream );
         if( hr != DS_OK )
         {
             result = paUnanticipatedHostError;
@@ -2301,6 +2936,17 @@ static PaError StartStream( PaStream *s )
             goto error;
         }
 
+        if( stream->streamRepresentation.streamCallback && (stream->streamFlags & paPrimeOutputBuffersUsingStreamCallback) )
+        {
+            stream->callbackFlags = paPrimingOutput;
+
+            TimeSlice( stream );
+            /* we ignore the return value from TimeSlice here and start the stream as usual.
+                The first timer callback will detect if the callback has completed. */
+
+            stream->callbackFlags = 0;
+        }
+
         // Start the buffer playback in a loop.
         if( stream->pDirectSoundOutputBuffer != NULL ) // FIXME: not sure this needs to be checked here
         {
@@ -2316,29 +2962,92 @@ static PaError StartStream( PaStream *s )
         }
     }
 
-
-    /* Create timer that will wake us up so we can fill the DSound buffer. */
+    if( stream->streamRepresentation.streamCallback )
     {
-        int resolution;
-        int framesPerWakeup = stream->framesPerDSBuffer / 4;
-        int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate;
-        if( msecPerWakeup < 10 ) msecPerWakeup = 10;
-        else if( msecPerWakeup > 100 ) msecPerWakeup = 100;
-        resolution = msecPerWakeup/4;
-        stream->timerID = timeSetEvent( msecPerWakeup, resolution, (LPTIMECALLBACK) Pa_TimerCallback,
-                                             (DWORD_PTR) stream, TIME_PERIODIC );
-    }
-    if( stream->timerID == 0 )
-    {
-        stream->isActive = 0;
-        result = paUnanticipatedHostError;
-        PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
-        goto error;
+        TIMECAPS timecaps;
+        int timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND);
+        if( timerPeriodMs < 1 )
+            timerPeriodMs = 1;
+
+        /* set windows scheduler granularity only as fine as needed, no finer */
+        /* Although this is not fully documented by MS, it appears that
+           timeBeginPeriod() affects the scheduling granulatity of all timers
+           including Waitable Timer Objects. So we always call timeBeginPeriod, whether
+           we're using an MM timer callback via timeSetEvent or not.
+        */
+        assert( stream->systemTimerResolutionPeriodMs == 0 );
+        if( timeGetDevCaps( &timecaps, sizeof(TIMECAPS) ) == MMSYSERR_NOERROR && timecaps.wPeriodMin > 0 )
+        {
+            /* aim for resolution 4 times higher than polling rate */
+            stream->systemTimerResolutionPeriodMs = (UINT)((stream->pollingPeriodSeconds * MSECS_PER_SECOND) * .25);
+            if( stream->systemTimerResolutionPeriodMs < timecaps.wPeriodMin )
+                stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMin;
+            if( stream->systemTimerResolutionPeriodMs > timecaps.wPeriodMax )
+                stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMax;
+
+            if( timeBeginPeriod( stream->systemTimerResolutionPeriodMs ) != MMSYSERR_NOERROR )
+                stream->systemTimerResolutionPeriodMs = 0; /* timeBeginPeriod failed, so we don't need to call timeEndPeriod() later */
+        }
+
+
+#ifdef PA_WIN_DS_USE_WMME_TIMER
+        /* Create timer that will wake us up so we can fill the DSound buffer. */
+        /* We have deprecated timeSetEvent because all MM timer callbacks
+           are serialised onto a single thread. Which creates problems with multiple
+           PA streams, or when also using timers for other time critical tasks
+        */
+        stream->timerID = timeSetEvent( timerPeriodMs, stream->systemTimerResolutionPeriodMs, (LPTIMECALLBACK) TimerCallback,
+                                             (DWORD_PTR) stream, TIME_PERIODIC | TIME_KILL_SYNCHRONOUS );
+    
+        if( stream->timerID == 0 )
+        {
+            stream->isActive = 0;
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#else
+		/* Create processing thread which calls TimerCallback */
+
+		stream->processingThread = CREATE_THREAD( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId );
+        if( !stream->processingThread )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+
+        if( !SetThreadPriority( stream->processingThread, THREAD_PRIORITY_TIME_CRITICAL ) )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#endif
     }
 
-    stream->isStarted = TRUE;
+    stream->isActive = 1;
+    stream->isStarted = 1;
+
+    assert( result == paNoError );
+    return result;
 
 error:
+
+    if( stream->pDirectSoundOutputBuffer != NULL && stream->outputIsRunning )
+        IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
+    stream->outputIsRunning = FALSE;
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+    if( stream->processingThread )
+    {
+#ifdef CLOSE_THREAD_HANDLE
+        CLOSE_THREAD_HANDLE( stream->processingThread ); /* Delete thread. */
+#endif
+        stream->processingThread = NULL;
+    }
+#endif
+
     return result;
 }
 
@@ -2351,20 +3060,40 @@ static PaError StopStream( PaStream *s )
     HRESULT          hr;
     int timeoutMsec;
 
-    stream->stopProcessing = 1;
-    /* Set timeout at 20% beyond maximum time we might wait. */
-    timeoutMsec = (int) (1200.0 * stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate);
-    while( stream->isActive && (timeoutMsec > 0)  )
+    if( stream->streamRepresentation.streamCallback )
     {
-        Sleep(10);
-        timeoutMsec -= 10;
+        stream->stopProcessing = 1;
+
+        /* Set timeout at 4 times maximum time we might wait. */
+        timeoutMsec = (int) (4 * MSECS_PER_SECOND * (stream->hostBufferSizeFrames / stream->streamRepresentation.streamInfo.sampleRate));
+
+        WaitForSingleObject( stream->processingCompleted, timeoutMsec );
     }
+
+#ifdef PA_WIN_DS_USE_WMME_TIMER
     if( stream->timerID != 0 )
     {
         timeKillEvent(stream->timerID);  /* Stop callback timer. */
         stream->timerID = 0;
     }
+#else
+    if( stream->processingThread )
+    {
+		if( WaitForSingleObject( stream->processingThreadCompleted, 30*100 ) == WAIT_TIMEOUT )
+			return paUnanticipatedHostError;
+
+#ifdef CLOSE_THREAD_HANDLE
+        CloseHandle( stream->processingThread ); /* Delete thread. */
+        stream->processingThread = NULL;
+#endif
+
+    }
+#endif
 
+    if( stream->systemTimerResolutionPeriodMs > 0 ){
+        timeEndPeriod( stream->systemTimerResolutionPeriodMs );
+        stream->systemTimerResolutionPeriodMs = 0;
+    }  
 
     if( stream->bufferProcessor.outputChannelCount > 0 )
     {
@@ -2374,6 +3103,9 @@ static PaError StopStream( PaStream *s )
             stream->outputIsRunning = FALSE;
             // FIXME: what happens if IDirectSoundBuffer_Stop returns an error?
             hr = IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
+
+            if( stream->pDirectSoundPrimaryBuffer )
+                IDirectSoundBuffer_Stop( stream->pDirectSoundPrimaryBuffer ); /* FIXME we never started the primary buffer so I'm not sure we need to stop it */
         }
     }
 
@@ -2387,7 +3119,7 @@ static PaError StopStream( PaStream *s )
         }
     }
 
-    stream->isStarted = FALSE;
+    stream->isStarted = 0;
 
     return result;
 }
@@ -2508,5 +3240,3 @@ static signed long GetStreamWriteAvailable( PaStream* s )
 }
 
 #endif
-
-
diff --git a/external/portaudio/pa_win_ds.h b/external/portaudio/pa_win_ds.h
index e658ee6..aff8c33 100644
--- a/external/portaudio/pa_win_ds.h
+++ b/external/portaudio/pa_win_ds.h
@@ -39,10 +39,10 @@
  */
 
 /** @file
+ @ingroup public_header
  @brief DirectSound-specific PortAudio API extension header file.
 */
 
-
 #include "portaudio.h"
 #include "pa_win_waveformat.h"
 
@@ -59,28 +59,23 @@ extern "C"
 typedef struct PaWinDirectSoundStreamInfo{
     unsigned long size;             /**< sizeof(PaWinDirectSoundStreamInfo) */
     PaHostApiTypeId hostApiType;    /**< paDirectSound */
-    unsigned long version;          /**< 1 */
-
-    unsigned long flags;
+    unsigned long version;          /**< 2 */
 
-    /* low-level latency setting support
-        TODO ** NOT IMPLEMENTED **
-        These settings control the number and size of host buffers in order
-        to set latency. They will be used instead of the generic parameters
-        to Pa_OpenStream() if flags contains the paWinDirectSoundUseLowLevelLatencyParameters
-        flag.
+    unsigned long flags;            /**< enable other features of this struct */
 
-        If PaWinDirectSoundStreamInfo structures with paWinDirectSoundUseLowLevelLatencyParameters
-        are supplied for both input and output in a full duplex stream, then the
-        input and output framesPerBuffer must be the same, or the larger of the
-        two must be a multiple of the smaller, otherwise a
-        paIncompatibleHostApiSpecificStreamInfo error will be returned from
-        Pa_OpenStream().
+    /** 
+       low-level latency setting support
+       Sets the size of the DirectSound host buffer.
+       When flags contains the paWinDirectSoundUseLowLevelLatencyParameters
+       this size will be used instead of interpreting the generic latency 
+       parameters to Pa_OpenStream(). If the flag is not set this value is ignored.
 
-    unsigned long framesPerBuffer;
+       If the stream is a full duplex stream the implementation requires that
+       the values of framesPerBuffer for input and output match (if both are specified).
     */
+    unsigned long framesPerBuffer;
 
-    /*
+    /**
         support for WAVEFORMATEXTENSIBLE channel masks. If flags contains
         paWinDirectSoundUseChannelMask this allows you to specify which speakers 
         to address in a multichannel stream. Constants for channelMask
diff --git a/external/portaudio/pa_win_ds_dynlink.c b/external/portaudio/pa_win_ds_dynlink.c
index f968653..c4e3c4e 100644
--- a/external/portaudio/pa_win_ds_dynlink.c
+++ b/external/portaudio/pa_win_ds_dynlink.c
@@ -41,11 +41,11 @@
 
 /**
  @file
- @ingroup hostaip_src
+ @ingroup hostapi_src
 */
 
-#undef UNICODE
 #include "pa_win_ds_dynlink.h"
+#include "pa_debugprint.h"
 
 PaWinDsDSoundEntryPoints paWinDsDSoundEntryPoints = { 0, 0, 0, 0, 0, 0, 0 };
 
@@ -102,6 +102,33 @@ static HRESULT WINAPI DummyDirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA lpDSCE
     return E_NOTIMPL;
 }
 
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+static HRESULT WINAPI DummyDirectSoundFullDuplexCreate8(
+         LPCGUID pcGuidCaptureDevice,
+         LPCGUID pcGuidRenderDevice,
+         LPCDSCBUFFERDESC pcDSCBufferDesc,
+         LPCDSBUFFERDESC pcDSBufferDesc,
+         HWND hWnd,
+         DWORD dwLevel,
+         LPDIRECTSOUNDFULLDUPLEX * ppDSFD,
+         LPDIRECTSOUNDCAPTUREBUFFER8 * ppDSCBuffer8,
+         LPDIRECTSOUNDBUFFER8 * ppDSBuffer8,
+         LPUNKNOWN pUnkOuter)
+{
+    (void)pcGuidCaptureDevice; /* unused parameter */
+    (void)pcGuidRenderDevice; /* unused parameter */
+    (void)pcDSCBufferDesc; /* unused parameter */
+    (void)pcDSBufferDesc; /* unused parameter */
+    (void)hWnd; /* unused parameter */
+    (void)dwLevel; /* unused parameter */
+    (void)ppDSFD; /* unused parameter */
+    (void)ppDSCBuffer8; /* unused parameter */
+    (void)ppDSBuffer8; /* unused parameter */
+    (void)pUnkOuter; /* unused parameter */
+
+    return E_NOTIMPL;
+}
+#endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
 
 void PaWinDs_InitializeDSoundEntryPoints(void)
 {
@@ -149,9 +176,22 @@ void PaWinDs_InitializeDSoundEntryPoints(void)
                 GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateA" );
         if( paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA == NULL )
             paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA;
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+        paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 =
+                (HRESULT (WINAPI *)(LPCGUID, LPCGUID, LPCDSCBUFFERDESC, LPCDSBUFFERDESC,
+                                    HWND, DWORD, LPDIRECTSOUNDFULLDUPLEX *, LPDIRECTSOUNDCAPTUREBUFFER8 *, 
+                                    LPDIRECTSOUNDBUFFER8 *, LPUNKNOWN))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundFullDuplexCreate" );
+        if( paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 = DummyDirectSoundFullDuplexCreate8;
+#endif
     }
     else
     {
+        DWORD errorCode = GetLastError(); // 126 (0x7E) == ERROR_MOD_NOT_FOUND
+        PA_DEBUG(("Couldn't load dsound.dll error code: %d \n",errorCode));
+
         /* initialize with dummy entry points to make live easy when ds isn't present */
         paWinDsDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate;
         paWinDsDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW;
@@ -159,6 +199,9 @@ void PaWinDs_InitializeDSoundEntryPoints(void)
         paWinDsDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate;
         paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW;
         paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA;
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+        paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 = DummyDirectSoundFullDuplexCreate8;
+#endif
     }
 }
 
diff --git a/external/portaudio/pa_win_ds_dynlink.h b/external/portaudio/pa_win_ds_dynlink.h
index 11d35da..fd6d8fe 100644
--- a/external/portaudio/pa_win_ds_dynlink.h
+++ b/external/portaudio/pa_win_ds_dynlink.h
@@ -41,7 +41,7 @@
 
 /**
  @file
- @ingroup hostaip_src
+ @ingroup hostapi_src
 */
 
 #ifndef INCLUDED_PA_DSOUND_DYNLINK_H
@@ -56,9 +56,13 @@
 #endif
 
 /*
-  We are only using DX3 in here, no need to polute the namespace - davidv
+  Use the earliest version of DX required, no need to polute the namespace
 */
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+#define DIRECTSOUND_VERSION 0x0800
+#else
 #define DIRECTSOUND_VERSION 0x0300
+#endif
 #include <dsound.h>
 
 #ifdef __cplusplus
@@ -80,6 +84,13 @@ typedef struct
     HRESULT (WINAPI *DirectSoundCaptureCreate)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN);
     HRESULT (WINAPI *DirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
     HRESULT (WINAPI *DirectSoundCaptureEnumerateA)(LPDSENUMCALLBACKA, LPVOID);
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+    HRESULT (WINAPI *DirectSoundFullDuplexCreate8)(
+                LPCGUID, LPCGUID, LPCDSCBUFFERDESC, LPCDSBUFFERDESC,
+                HWND, DWORD, LPDIRECTSOUNDFULLDUPLEX *, LPDIRECTSOUNDCAPTUREBUFFER8 *, 
+                LPDIRECTSOUNDBUFFER8 *, LPUNKNOWN );
+#endif
 }PaWinDsDSoundEntryPoints;
 
 extern PaWinDsDSoundEntryPoints paWinDsDSoundEntryPoints;
diff --git a/external/portaudio/pa_win_hostapis.c b/external/portaudio/pa_win_hostapis.c
index e7524f7..a6398b2 100644
--- a/external/portaudio/pa_win_hostapis.c
+++ b/external/portaudio/pa_win_hostapis.c
@@ -1,10 +1,10 @@
 #ifdef _WIN32
 /*
- * $Id: pa_win_hostapis.c 1097 2006-08-26 08:27:53Z rossb $
+ * $Id: pa_win_hostapis.c 1728 2011-08-18 03:31:51Z rossb $
  * Portable Audio I/O Library Windows initialization table
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ * Copyright (c) 1999-2008 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -40,15 +40,19 @@
 /** @file
  @ingroup win_src
 
-    Win32 host API initialization function table.
-
-    @todo Consider using PA_USE_WMME etc instead of PA_NO_WMME. This is what
-    the Unix version does, we should consider being consistent.
+    @brief Win32 host API initialization function table.
 */
 
+/* This is needed to make this source file depend on CMake option changes
+   and at the same time make it transparent for clients not using CMake.
+*/
+#ifdef PORTAUDIO_CMAKE_GENERATED
+#include "options_cmake.h"
+#endif
 
 #include "pa_hostapi.h"
 
+
 #ifdef __cplusplus
 extern "C"
 {
@@ -59,7 +63,7 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
 PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
 PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
 PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
-PaError PaWinWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
 
 #ifdef __cplusplus
 }
@@ -68,14 +72,13 @@ PaError PaWinWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApi
 
 PaUtilHostApiInitializer *paHostApiInitializers[] =
     {
-        PaSkeleton_Initialize,   // 0; just for testing
-        PaWinMme_Initialize,   // 1
-        //PaWinDs_Initialize,   // 2
-        //PaWinWdm_Initialize,   // 3
+        PaWinMme_Initialize,   // 0 ppgb
+        //PaWinDs_Initialize,   // 1 ppgb
+        //PaWinWdm_Initialize,   // 2 ppgb
         //PaAsio_Initialize,
 		//PaWinWasapi_Initialize,
         0   /* NULL terminated array */
     };
 
-int paDefaultHostApiIndex = 1;
+int paDefaultHostApiIndex = 0;   // ppgb
 #endif
diff --git a/external/portaudio/pa_win_util.c b/external/portaudio/pa_win_util.c
index 1de8073..76135e7 100644
--- a/external/portaudio/pa_win_util.c
+++ b/external/portaudio/pa_win_util.c
@@ -1,11 +1,11 @@
 #ifdef _WIN32
 /*
- * $Id: pa_win_util.c 1197 2007-05-04 13:07:10Z gordon_gidluck $
+ * $Id: pa_win_util.c 1584 2011-02-02 18:58:17Z rossb $
  * Portable Audio I/O Library
  * Win32 platform-specific support functions
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2000 Ross Bencina
+ * Copyright (c) 1999-2008 Ross Bencina
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -41,18 +41,18 @@
 /** @file
  @ingroup win_src
 
- Win32 platform-specific support functions.
-
-    @todo Implement workaround for QueryPerformanceCounter() skipping forward
-    bug. (see msdn kb Q274323).
+ @brief Win32 implementation of platform-specific PaUtil support functions.
 */
-
-#undef UNICODE
+ 
 #include <windows.h>
 #include <mmsystem.h> /* for timeGetTime() */
 
 #include "pa_util.h"
 
+#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) && !defined(_WIN32_WCE) /* MSC version 6 and above */
+#pragma comment( lib, "winmm.lib" )
+#endif
+
 
 /*
    Track memory allocations to avoid leaks.
@@ -127,13 +127,18 @@ double PaUtil_GetTime( void )
 
     if( usePerformanceCounter_ )
     {
-        /* FIXME:
-            according to this knowledge-base article, QueryPerformanceCounter
-            can skip forward by seconds!
+        /*
+            Note: QueryPerformanceCounter has a known issue where it can skip forward
+            by a few seconds (!) due to a hardware bug on some PCI-ISA bridge hardware.
+            This is documented here:
             http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323&
 
-            it may be better to use the rtdsc instruction using inline asm,
-            however then a method is needed to calculate a ticks/seconds ratio.
+            The work-arounds are not very paletable and involve querying GetTickCount 
+            at every time step.
+
+            Using rdtsc is not a good option on multi-core systems.
+
+            For now we just use QueryPerformanceCounter(). It's good, most of the time.
         */
         QueryPerformanceCounter( &time );
         return time.QuadPart * secondsPerTick_;
@@ -147,4 +152,5 @@ double PaUtil_GetTime( void )
 #endif                
     }
 }
+
 #endif
diff --git a/external/portaudio/pa_win_wasapi.h b/external/portaudio/pa_win_wasapi.h
new file mode 100644
index 0000000..64eb49b
--- /dev/null
+++ b/external/portaudio/pa_win_wasapi.h
@@ -0,0 +1,391 @@
+#ifndef PA_WIN_WASAPI_H
+#define PA_WIN_WASAPI_H
+/*
+ * $Id:  $
+ * PortAudio Portable Real-Time Audio Library
+ * DirectSound specific extensions
+ *
+ * Copyright (c) 1999-2007 Ross Bencina and Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+/** @file
+ @ingroup public_header
+ @brief WASAPI-specific PortAudio API extension header file.
+*/
+
+#include "portaudio.h"
+#include "pa_win_waveformat.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+/* Setup flags */
+typedef enum PaWasapiFlags
+{
+    /* puts WASAPI into exclusive mode */
+    paWinWasapiExclusive                = (1 << 0),
+
+    /* allows to skip internal PA processing completely */
+    paWinWasapiRedirectHostProcessor    = (1 << 1),
+
+    /* assigns custom channel mask */
+    paWinWasapiUseChannelMask           = (1 << 2),
+
+    /* selects non-Event driven method of data read/write
+       Note: WASAPI Event driven core is capable of 2ms latency!!!, but Polling
+             method can only provide 15-20ms latency. */
+    paWinWasapiPolling                  = (1 << 3),
+
+    /* forces custom thread priority setting. must be used if PaWasapiStreamInfo::threadPriority 
+       is set to custom value. */
+    paWinWasapiThreadPriority           = (1 << 4)
+}
+PaWasapiFlags;
+#define paWinWasapiExclusive             (paWinWasapiExclusive)
+#define paWinWasapiRedirectHostProcessor (paWinWasapiRedirectHostProcessor)
+#define paWinWasapiUseChannelMask        (paWinWasapiUseChannelMask)
+#define paWinWasapiPolling               (paWinWasapiPolling)
+#define paWinWasapiThreadPriority        (paWinWasapiThreadPriority)
+
+
+/* Host processor. Allows to skip internal PA processing completely. 
+   You must set paWinWasapiRedirectHostProcessor flag to PaWasapiStreamInfo::flags member
+   in order to have host processor redirected to your callback.
+   Use with caution! inputFrames and outputFrames depend solely on final device setup.
+   To query maximal values of inputFrames/outputFrames use PaWasapi_GetFramesPerHostBuffer.
+*/
+typedef void (*PaWasapiHostProcessorCallback) (void *inputBuffer,  long inputFrames,
+                                               void *outputBuffer, long outputFrames,
+                                               void *userData);
+
+/* Device role */
+typedef enum PaWasapiDeviceRole
+{
+    eRoleRemoteNetworkDevice = 0,
+    eRoleSpeakers,
+    eRoleLineLevel,
+    eRoleHeadphones,
+    eRoleMicrophone,
+    eRoleHeadset,
+    eRoleHandset,
+    eRoleUnknownDigitalPassthrough,
+    eRoleSPDIF,
+    eRoleHDMI,
+    eRoleUnknownFormFactor
+}
+PaWasapiDeviceRole;
+
+
+/* Jack connection type */
+typedef enum PaWasapiJackConnectionType
+{
+    eJackConnTypeUnknown,
+    eJackConnType3Point5mm,
+    eJackConnTypeQuarter,
+    eJackConnTypeAtapiInternal,
+    eJackConnTypeRCA,
+    eJackConnTypeOptical,
+    eJackConnTypeOtherDigital,
+    eJackConnTypeOtherAnalog,
+    eJackConnTypeMultichannelAnalogDIN,
+    eJackConnTypeXlrProfessional,
+    eJackConnTypeRJ11Modem,
+    eJackConnTypeCombination
+} 
+PaWasapiJackConnectionType;
+
+
+/* Jack geometric location */
+typedef enum PaWasapiJackGeoLocation
+{
+	eJackGeoLocUnk = 0,
+    eJackGeoLocRear = 0x1, /* matches EPcxGeoLocation::eGeoLocRear */
+    eJackGeoLocFront,
+    eJackGeoLocLeft,
+    eJackGeoLocRight,
+    eJackGeoLocTop,
+    eJackGeoLocBottom,
+    eJackGeoLocRearPanel,
+    eJackGeoLocRiser,
+    eJackGeoLocInsideMobileLid,
+    eJackGeoLocDrivebay,
+    eJackGeoLocHDMI,
+    eJackGeoLocOutsideMobileLid,
+    eJackGeoLocATAPI,
+    eJackGeoLocReserved5,
+    eJackGeoLocReserved6,
+} 
+PaWasapiJackGeoLocation;
+
+
+/* Jack general location */
+typedef enum PaWasapiJackGenLocation
+{
+    eJackGenLocPrimaryBox = 0,
+    eJackGenLocInternal,
+    eJackGenLocSeparate,
+    eJackGenLocOther
+} 
+PaWasapiJackGenLocation;
+
+
+/* Jack's type of port */
+typedef enum PaWasapiJackPortConnection
+{
+    eJackPortConnJack = 0,
+    eJackPortConnIntegratedDevice,
+    eJackPortConnBothIntegratedAndJack,
+    eJackPortConnUnknown
+} 
+PaWasapiJackPortConnection;
+
+
+/* Thread priority */
+typedef enum PaWasapiThreadPriority
+{
+    eThreadPriorityNone = 0,
+    eThreadPriorityAudio,            //!< Default for Shared mode.
+    eThreadPriorityCapture,
+    eThreadPriorityDistribution,
+    eThreadPriorityGames,
+    eThreadPriorityPlayback,
+    eThreadPriorityProAudio,        //!< Default for Exclusive mode.
+    eThreadPriorityWindowManager
+}
+PaWasapiThreadPriority;
+
+
+/* Stream descriptor. */
+typedef struct PaWasapiJackDescription 
+{
+    unsigned long              channelMapping;
+    unsigned long              color; /* derived from macro: #define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16))) */
+    PaWasapiJackConnectionType connectionType;
+    PaWasapiJackGeoLocation    geoLocation;
+    PaWasapiJackGenLocation    genLocation;
+    PaWasapiJackPortConnection portConnection;
+    unsigned int               isConnected;
+}
+PaWasapiJackDescription;
+
+
+/* Stream descriptor. */
+typedef struct PaWasapiStreamInfo 
+{
+    unsigned long size;             /**< sizeof(PaWasapiStreamInfo) */
+    PaHostApiTypeId hostApiType;    /**< paWASAPI */
+    unsigned long version;          /**< 1 */
+
+    unsigned long flags;            /**< collection of PaWasapiFlags */
+
+    /* Support for WAVEFORMATEXTENSIBLE channel masks. If flags contains
+       paWinWasapiUseChannelMask this allows you to specify which speakers 
+       to address in a multichannel stream. Constants for channelMask
+       are specified in pa_win_waveformat.h. Will be used only if 
+       paWinWasapiUseChannelMask flag is specified.
+    */
+    PaWinWaveFormatChannelMask channelMask;
+
+    /* Delivers raw data to callback obtained from GetBuffer() methods skipping 
+       internal PortAudio processing inventory completely. userData parameter will 
+       be the same that was passed to Pa_OpenStream method. Will be used only if 
+       paWinWasapiRedirectHostProcessor flag is specified.
+    */
+    PaWasapiHostProcessorCallback hostProcessorOutput;
+    PaWasapiHostProcessorCallback hostProcessorInput;
+
+    /* Specifies thread priority explicitly. Will be used only if paWinWasapiThreadPriority flag
+       is specified.
+
+       Please note, if Input/Output streams are opened simultaniously (Full-Duplex mode)
+       you shall specify same value for threadPriority or othervise one of the values will be used
+       to setup thread priority.
+    */
+    PaWasapiThreadPriority threadPriority;
+} 
+PaWasapiStreamInfo;
+
+
+/** Returns default sound format for device. Format is represented by PaWinWaveFormat or 
+    WAVEFORMATEXTENSIBLE structure.
+
+ @param pFormat Pointer to PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure.
+ @param nFormatSize Size of PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure in bytes.
+ @param nDevice Device index.
+
+ @return Non-negative value indicating the number of bytes copied into format decriptor
+         or, a PaErrorCode (which are always negative) if PortAudio is not initialized
+         or an error is encountered.
+*/
+int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int nFormatSize, PaDeviceIndex nDevice );
+
+
+/** Returns device role (PaWasapiDeviceRole enum).
+
+ @param nDevice device index.
+
+ @return Non-negative value indicating device role or, a PaErrorCode (which are always negative)
+         if PortAudio is not initialized or an error is encountered.
+*/
+int/*PaWasapiDeviceRole*/ PaWasapi_GetDeviceRole( PaDeviceIndex nDevice );
+
+
+/** Boost thread priority of calling thread (MMCSS). Use it for Blocking Interface only for thread
+    which makes calls to Pa_WriteStream/Pa_ReadStream.
+
+ @param hTask Handle to pointer to priority task. Must be used with PaWasapi_RevertThreadPriority
+              method to revert thread priority to initial state.
+
+ @param nPriorityClass Id of thread priority of PaWasapiThreadPriority type. Specifying 
+                       eThreadPriorityNone does nothing.
+
+ @return Error code indicating success or failure.
+ @see    PaWasapi_RevertThreadPriority
+*/
+PaError PaWasapi_ThreadPriorityBoost( void **hTask, PaWasapiThreadPriority nPriorityClass );
+
+
+/** Boost thread priority of calling thread (MMCSS). Use it for Blocking Interface only for thread
+    which makes calls to Pa_WriteStream/Pa_ReadStream.
+
+ @param  hTask Task handle obtained by PaWasapi_BoostThreadPriority method.
+ @return Error code indicating success or failure.
+ @see    PaWasapi_BoostThreadPriority
+*/
+PaError PaWasapi_ThreadPriorityRevert( void *hTask );
+
+
+/** Get number of frames per host buffer. This is maximal value of frames of WASAPI buffer which 
+    can be locked for operations. Use this method as helper to findout maximal values of 
+    inputFrames/outputFrames of PaWasapiHostProcessorCallback.
+
+ @param  pStream Pointer to PaStream to query.
+ @param  nInput  Pointer to variable to receive number of input frames. Can be NULL.
+ @param  nOutput Pointer to variable to receive number of output frames. Can be NULL.
+ @return Error code indicating success or failure.
+ @see    PaWasapiHostProcessorCallback
+*/
+PaError PaWasapi_GetFramesPerHostBuffer( PaStream *pStream, unsigned int *nInput, unsigned int *nOutput );
+
+
+/** Get number of jacks associated with a WASAPI device.  Use this method to determine if
+    there are any jacks associated with the provided WASAPI device.  Not all audio devices
+	will support this capability.  This is valid for both input and output devices.
+ @param  nDevice  device index.
+ @param  jcount   Number of jacks is returned in this variable
+ @return Error code indicating success or failure
+ @see PaWasapi_GetJackDescription
+ */
+PaError PaWasapi_GetJackCount(PaDeviceIndex nDevice, int *jcount);
+
+
+/** Get the jack description associated with a WASAPI device and jack number
+    Before this function is called, use PaWasapi_GetJackCount to determine the
+	number of jacks associated with device.  If jcount is greater than zero, then
+	each jack from 0 to jcount can be queried with this function to get the jack
+	description.
+ @param  nDevice  device index.
+ @param  jindex   Which jack to return information
+ @param  KSJACK_DESCRIPTION This structure filled in on success.
+ @return Error code indicating success or failure
+ @see PaWasapi_GetJackCount
+ */
+PaError PaWasapi_GetJackDescription(PaDeviceIndex nDevice, int jindex, PaWasapiJackDescription *pJackDescription);
+
+
+/*
+    IMPORTANT:
+
+    WASAPI is implemented for Callback and Blocking interfaces. It supports Shared and Exclusive
+    share modes. 
+    
+    Exclusive Mode:
+
+        Exclusive mode allows to deliver audio data directly to hardware bypassing
+        software mixing.
+        Exclusive mode is specified by 'paWinWasapiExclusive' flag.
+
+    Callback Interface:
+
+        Provides best audio quality with low latency. Callback interface is implemented in 
+        two versions:
+
+        1) Event-Driven:
+        This is the most powerful WASAPI implementation which provides glitch-free
+        audio at around 3ms latency in Exclusive mode. Lowest possible latency for this mode is 
+        3 ms for HD Audio class audio chips. For the Shared mode latency can not be 
+		lower than 20 ms.
+
+        2) Poll-Driven:
+        Polling is another 2-nd method to operate with WASAPI. It is less efficient than Event-Driven
+        and provides latency at around 10-13ms. Polling must be used to overcome a system bug
+        under Windows Vista x64 when application is WOW64(32-bit) and Event-Driven method simply 
+        times out (event handle is never signalled on buffer completion). Please note, such WOW64 bug 
+        does not exist in Vista x86 or Windows 7.
+        Polling can be setup by speciying 'paWinWasapiPolling' flag. Our WASAPI implementation detects
+        WOW64 bug and sets 'paWinWasapiPolling' automatically.
+
+    Thread priority:
+
+        Normally thread priority is set automatically and does not require modification. Although
+        if user wants some tweaking thread priority can be modified by setting 'paWinWasapiThreadPriority'
+        flag and specifying 'PaWasapiStreamInfo::threadPriority' with value from PaWasapiThreadPriority 
+        enum.
+
+    Blocking Interface:
+
+        Blocking interface is implemented but due to above described Poll-Driven method can not
+        deliver lowest possible latency. Specifying too low latency in Shared mode will result in 
+        distorted audio although Exclusive mode adds stability.
+
+    Pa_IsFormatSupported:
+
+        To check format with correct Share Mode (Exclusive/Shared) you must supply
+        PaWasapiStreamInfo with flags paWinWasapiExclusive set through member of 
+        PaStreamParameters::hostApiSpecificStreamInfo structure.
+
+    Pa_OpenStream:
+
+        To set desired Share Mode (Exclusive/Shared) you must supply
+        PaWasapiStreamInfo with flags paWinWasapiExclusive set through member of 
+        PaStreamParameters::hostApiSpecificStreamInfo structure.
+*/
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PA_WIN_WASAPI_H */                                  
diff --git a/external/portaudio/pa_win_waveformat.c b/external/portaudio/pa_win_waveformat.c
index 375ee04..d02449a 100644
--- a/external/portaudio/pa_win_waveformat.c
+++ b/external/portaudio/pa_win_waveformat.c
@@ -37,7 +37,6 @@
  * license above.
  */
 
-#undef UNICODE
 #include <windows.h>
 #include <mmsystem.h>
 
@@ -49,30 +48,27 @@
 #define  WAVE_FORMAT_EXTENSIBLE         0xFFFE
 #endif
 
-#if !defined(WAVE_FORMAT_IEEE_FLOAT)
-#define  WAVE_FORMAT_IEEE_FLOAT         0x0003
-#endif
-
-static GUID pawin_ksDataFormatSubtypePcm = 
+static GUID pawin_ksDataFormatSubtypeGuidBase = 
 	{ (USHORT)(WAVE_FORMAT_PCM), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 };
 
-static GUID pawin_ksDataFormatSubtypeIeeeFloat = 
-	{ (USHORT)(WAVE_FORMAT_IEEE_FLOAT), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 };
 
+int PaWin_SampleFormatToLinearWaveFormatTag( PaSampleFormat sampleFormat )
+{
+    if( sampleFormat == paFloat32 )
+        return PAWIN_WAVE_FORMAT_IEEE_FLOAT;
+    
+    return PAWIN_WAVE_FORMAT_PCM;
+}
 
 
 void PaWin_InitializeWaveFormatEx( PaWinWaveFormat *waveFormat, 
-		int numChannels, PaSampleFormat sampleFormat, double sampleRate )
+		int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate )
 {
 	WAVEFORMATEX *waveFormatEx = (WAVEFORMATEX*)waveFormat;
     int bytesPerSample = Pa_GetSampleSize(sampleFormat);
 	unsigned long bytesPerFrame = numChannels * bytesPerSample;
-
-    if( sampleFormat == paFloat32 )
-        waveFormatEx->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
-    else
-        waveFormatEx->wFormatTag = WAVE_FORMAT_PCM;
 	
+    waveFormatEx->wFormatTag = waveFormatTag;
 	waveFormatEx->nChannels = (WORD)numChannels;
 	waveFormatEx->nSamplesPerSec = (DWORD)sampleRate;
 	waveFormatEx->nAvgBytesPerSec = waveFormatEx->nSamplesPerSec * bytesPerFrame;
@@ -83,12 +79,13 @@ void PaWin_InitializeWaveFormatEx( PaWinWaveFormat *waveFormat,
 
 
 void PaWin_InitializeWaveFormatExtensible( PaWinWaveFormat *waveFormat, 
-		int numChannels, PaSampleFormat sampleFormat, double sampleRate,
+		int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate,
 		PaWinWaveFormatChannelMask channelMask )
 {
 	WAVEFORMATEX *waveFormatEx = (WAVEFORMATEX*)waveFormat;
     int bytesPerSample = Pa_GetSampleSize(sampleFormat);
 	unsigned long bytesPerFrame = numChannels * bytesPerSample;
+    GUID guid;
 
 	waveFormatEx->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
 	waveFormatEx->nChannels = (WORD)numChannels;
@@ -102,16 +99,12 @@ void PaWin_InitializeWaveFormatExtensible( PaWinWaveFormat *waveFormat,
 			waveFormatEx->wBitsPerSample;
 
 	*((DWORD*)&waveFormat->fields[PAWIN_INDEXOF_DWCHANNELMASK]) = channelMask;
-			
-    if( sampleFormat == paFloat32 )
-        *((GUID*)&waveFormat->fields[PAWIN_INDEXOF_SUBFORMAT]) =
-            pawin_ksDataFormatSubtypeIeeeFloat;
-    else
-        *((GUID*)&waveFormat->fields[PAWIN_INDEXOF_SUBFORMAT]) =
-            pawin_ksDataFormatSubtypePcm;
+		
+    guid = pawin_ksDataFormatSubtypeGuidBase;
+    guid.Data1 = (USHORT)waveFormatTag;
+    *((GUID*)&waveFormat->fields[PAWIN_INDEXOF_SUBFORMAT]) = guid;
 }
 
-
 PaWinWaveFormatChannelMask PaWin_DefaultChannelMask( int numChannels )
 {
 	switch( numChannels ){
@@ -136,11 +129,16 @@ PaWinWaveFormatChannelMask PaWin_DefaultChannelMask( int numChannels )
 			return PAWIN_SPEAKER_5POINT1; 
         /* case 7: */
 		case 8:
-			return PAWIN_SPEAKER_7POINT1;
+            /* RoBi: PAWIN_SPEAKER_7POINT1_SURROUND fits normal surround sound setups better than PAWIN_SPEAKER_7POINT1, f.i. NVidia HDMI Audio
+               output is silent on channels 5&6 with NVidia drivers, and channel 7&8 with Micrsoft HD Audio driver using PAWIN_SPEAKER_7POINT1. 
+               With PAWIN_SPEAKER_7POINT1_SURROUND both setups work OK. */
+			return PAWIN_SPEAKER_7POINT1_SURROUND;
 	}
 
     /* Apparently some Audigy drivers will output silence 
        if the direct-out constant (0) is used. So this is not ideal.    
+
+       RoBi 2012-12-19: Also, NVidia driver seem to output garbage instead. Again not very ideal.
     */
 	return  PAWIN_SPEAKER_DIRECTOUT;
 
@@ -159,4 +157,5 @@ PaWinWaveFormatChannelMask PaWin_DefaultChannelMask( int numChannels )
         }
     */
 }
+
 #endif
diff --git a/external/portaudio/pa_win_waveformat.h b/external/portaudio/pa_win_waveformat.h
index 46ce9cc..dce18c0 100644
--- a/external/portaudio/pa_win_waveformat.h
+++ b/external/portaudio/pa_win_waveformat.h
@@ -40,6 +40,7 @@
  */
 
 /** @file
+ @ingroup public_header
  @brief Windows specific PortAudio API extension and utilities header file.
 */
 
@@ -156,16 +157,34 @@ typedef struct{
 #define PAWIN_INDEXOF_DWCHANNELMASK			(PAWIN_SIZEOF_WAVEFORMATEX+2)
 #define PAWIN_INDEXOF_SUBFORMAT				(PAWIN_SIZEOF_WAVEFORMATEX+6)
 
+
+/*
+    Valid values to pass for the waveFormatTag PaWin_InitializeWaveFormatEx and
+    PaWin_InitializeWaveFormatExtensible functions below. These must match
+    the standard Windows WAVE_FORMAT_* values.
+*/
+#define PAWIN_WAVE_FORMAT_PCM               (1)
+#define PAWIN_WAVE_FORMAT_IEEE_FLOAT        (3)
+#define PAWIN_WAVE_FORMAT_DOLBY_AC3_SPDIF   (0x0092)
+#define PAWIN_WAVE_FORMAT_WMA_SPDIF         (0x0164)
+
+
+/*
+    returns PAWIN_WAVE_FORMAT_PCM or PAWIN_WAVE_FORMAT_IEEE_FLOAT
+    depending on the sampleFormat parameter.
+*/
+int PaWin_SampleFormatToLinearWaveFormatTag( PaSampleFormat sampleFormat );
+
 /*
 	Use the following two functions to initialize the waveformat structure.
 */
 
 void PaWin_InitializeWaveFormatEx( PaWinWaveFormat *waveFormat, 
-		int numChannels, PaSampleFormat sampleFormat, double sampleRate );
+		int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate );
 
 
 void PaWin_InitializeWaveFormatExtensible( PaWinWaveFormat *waveFormat, 
-		int numChannels, PaSampleFormat sampleFormat, double sampleRate,
+		int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate,
 	    PaWinWaveFormatChannelMask channelMask );
 
 
diff --git a/external/portaudio/pa_win_wdmks.c b/external/portaudio/pa_win_wdmks.c
index b72daa3..f969e14 100644
--- a/external/portaudio/pa_win_wdmks.c
+++ b/external/portaudio/pa_win_wdmks.c
@@ -1,70 +1,75 @@
 /*
- * $Id: pa_win_wdmks.c 1263 2007-08-27 22:59:09Z rossb $
- * PortAudio Windows WDM-KS interface
- *
- * Author: Andrew Baldwin
- * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2004 Andrew Baldwin, Ross Bencina, Phil Burk
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files
- * (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge,
- * publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
+* $Id: pa_win_wdmks.c 1885 2012-12-28 16:54:25Z robiwan $
+* PortAudio Windows WDM-KS interface
+*
+* Author: Andrew Baldwin, Robert Bielik (WaveRT)
+* Based on the Open Source API proposed by Ross Bencina
+* Copyright (c) 1999-2004 Andrew Baldwin, Ross Bencina, Phil Burk
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files
+* (the "Software"), to deal in the Software without restriction,
+* including without limitation the rights to use, copy, modify, merge,
+* publish, distribute, sublicense, and/or sell copies of the Software,
+* and to permit persons to whom the Software is furnished to do so,
+* subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
 
 /*
- * The text above constitutes the entire PortAudio license; however, 
- * the PortAudio community also makes the following non-binding requests:
- *
- * Any person wishing to distribute modifications to the Software is
- * requested to send the modifications to the original developer so that
- * they can be incorporated into the canonical version. It is also 
- * requested that these non-binding requests be included along with the 
- * license above.
- */
+* The text above constitutes the entire PortAudio license; however, 
+* the PortAudio community also makes the following non-binding requests:
+*
+* Any person wishing to distribute modifications to the Software is
+* requested to send the modifications to the original developer so that
+* they can be incorporated into the canonical version. It is also 
+* requested that these non-binding requests be included along with the 
+* license above.
+*/
 
 /** @file
- @ingroup hostaip_src
- @brief Portaudio WDM-KS host API.
-
- @note This is the implementation of the Portaudio host API using the
- Windows WDM/Kernel Streaming API in order to enable very low latency
- playback and recording on all modern Windows platforms (e.g. 2K, XP)
- Note: This API accesses the device drivers below the usual KMIXER
- component which is normally used to enable multi-client mixing and
- format conversion. That means that it will lock out all other users
- of a device for the duration of active stream using those devices
+ at ingroup hostapi_src
+ at brief Portaudio WDM-KS host API.
+
+ at note This is the implementation of the Portaudio host API using the
+Windows WDM/Kernel Streaming API in order to enable very low latency
+playback and recording on all modern Windows platforms (e.g. 2K, XP, Vista, Win7)
+Note: This API accesses the device drivers below the usual KMIXER
+component which is normally used to enable multi-client mixing and
+format conversion. That means that it will lock out all other users
+of a device for the duration of active stream using those devices
 */
 
 #include <stdio.h>
 
+#if (defined(_WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
+#pragma comment( lib, "setupapi.lib" )
+#endif
+
 /* Debugging/tracing support */
 
 #define PA_LOGE_
 #define PA_LOGL_
 
 #ifdef __GNUC__
-    #include <initguid.h>
-    #define _WIN32_WINNT 0x0501
-    #define WINVER 0x0501
+#include <initguid.h>
+#define _WIN32_WINNT 0x0501
+#define WINVER 0x0501
 #endif
 
 #include <string.h> /* strlen() */
 #include <assert.h>
+#include <wchar.h>  /* iswspace() */
 
 #include "pa_util.h"
 #include "pa_allocation.h"
@@ -74,96 +79,195 @@
 #include "pa_process.h"
 #include "portaudio.h"
 #include "pa_debugprint.h"
+#include "pa_memorybarrier.h"
+#include "pa_ringbuffer.h"
+#include "pa_trace.h"
+#include "pa_win_waveformat.h"
+
+#include "pa_win_wdmks.h"
 
 #include <windows.h>
 #include <winioctl.h>
+#include <process.h>
 
-// ppgb 20080707:
-    #define _INC_MMSYSTEM
-#define _INC_MMREG
-    #define DYNAMIC_GUID_THUNK(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
-    #define DYNAMIC_GUID(data) DYNAMIC_GUID_THUNK(data)
-    #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
-    #define DEFINE_GUID_THUNK(name,guid) DEFINE_GUID(name,guid)
-    #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK( n, STATIC_##n )
-    #if !defined( DEFINE_WAVEFORMATEX_GUID )
-        #define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
-    #endif
-    #define  WAVE_FORMAT_ADPCM      0x0002
-    #define  WAVE_FORMAT_IEEE_FLOAT 0x0003
-    #define  WAVE_FORMAT_ALAW       0x0006
-    #define  WAVE_FORMAT_MULAW      0x0007
-    #define  WAVE_FORMAT_MPEG       0x0050
-    #define  WAVE_FORMAT_DRM        0x0009
+#include <math.h>
+
+#ifdef _MSC_VER
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#endif
+
+/* The PA_HP_TRACE macro is used in RT parts, so it can be switched off without affecting
+the rest of the debug tracing */
+#if 1
+#define PA_HP_TRACE(x)  PaUtil_AddHighSpeedLogMessage x ;
+#else
+#define PA_HP_TRACE(x)
+#endif
+
+/* A define that selects whether the resulting pin names are chosen from pin category
+instead of the available pin names, who sometimes can be quite cheesy, like "Volume control".
+Default is to use the pin category.
+*/
+#ifndef PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES
+#define PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES  1
+#endif
 
 #ifdef __GNUC__
-    #undef PA_LOGE_
-    #define PA_LOGE_ PA_DEBUG(("%s {\n",__FUNCTION__))
-    #undef PA_LOGL_
-    #define PA_LOGL_ PA_DEBUG(("} %s\n",__FUNCTION__))
-    /* These defines are set in order to allow the WIndows DirectX
-     * headers to compile with a GCC compiler such as MinGW
-     * NOTE: The headers may generate a few warning in GCC, but
-     * they should compile */
-    #define _INC_MMSYSTEM
-    #define _INC_MMREG
-    #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
-    #define DEFINE_GUID_THUNK(name,guid) DEFINE_GUID(name,guid)
-    #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK( n, STATIC_##n )
-    #if !defined( DEFINE_WAVEFORMATEX_GUID )
-        #define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
-    #endif
-    #define  WAVE_FORMAT_ADPCM      0x0002
-    #define  WAVE_FORMAT_IEEE_FLOAT 0x0003
-    #define  WAVE_FORMAT_ALAW       0x0006
-    #define  WAVE_FORMAT_MULAW      0x0007
-    #define  WAVE_FORMAT_MPEG       0x0050
-    #define  WAVE_FORMAT_DRM        0x0009
-    #define DYNAMIC_GUID_THUNK(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
-    #define DYNAMIC_GUID(data) DYNAMIC_GUID_THUNK(data)
+#undef PA_LOGE_
+#define PA_LOGE_ PA_DEBUG(("%s {\n",__FUNCTION__))
+#undef PA_LOGL_
+#define PA_LOGL_ PA_DEBUG(("} %s\n",__FUNCTION__))
+/* These defines are set in order to allow the WIndows DirectX
+* headers to compile with a GCC compiler such as MinGW
+* NOTE: The headers may generate a few warning in GCC, but
+* they should compile */
+#define _INC_MMSYSTEM
+#define _INC_MMREG
+#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
+#define DEFINE_GUID_THUNK(name,guid) DEFINE_GUID(name,guid)
+#define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK( n, STATIC_##n )
+#if !defined( DEFINE_WAVEFORMATEX_GUID )
+#define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
+#endif
+#define  WAVE_FORMAT_ADPCM      0x0002
+#define  WAVE_FORMAT_IEEE_FLOAT 0x0003
+#define  WAVE_FORMAT_ALAW       0x0006
+#define  WAVE_FORMAT_MULAW      0x0007
+#define  WAVE_FORMAT_MPEG       0x0050
+#define  WAVE_FORMAT_DRM        0x0009
+#define DYNAMIC_GUID_THUNK(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
+#define DYNAMIC_GUID(data) DYNAMIC_GUID_THUNK(data)
+#endif
+
+/* use CreateThread for CYGWIN/Windows Mobile, _beginthreadex for all others */
+#if !defined(__CYGWIN__) && !defined(_WIN32_WCE)
+#define CREATE_THREAD_FUNCTION (HANDLE)_beginthreadex
+#define PA_THREAD_FUNC static unsigned WINAPI
+#else
+#define CREATE_THREAD_FUNCTION CreateThread
+#define PA_THREAD_FUNC static DWORD WINAPI
 #endif
 
 #ifdef _MSC_VER
-    #define DYNAMIC_GUID(data) {data}
-    #define _INC_MMREG
-    #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
-    #undef DEFINE_GUID
-    #define DEFINE_GUID(n,data) EXTERN_C const GUID n = {data}
-    #define DEFINE_GUID_THUNK(n,data) DEFINE_GUID(n,data)
-    #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n)
-    #if !defined( DEFINE_WAVEFORMATEX_GUID )
-        #define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
-    #endif
-    #define  WAVE_FORMAT_ADPCM      0x0002
-    #define  WAVE_FORMAT_IEEE_FLOAT 0x0003
-    #define  WAVE_FORMAT_ALAW       0x0006
-    #define  WAVE_FORMAT_MULAW      0x0007
-    #define  WAVE_FORMAT_MPEG       0x0050
-    #define  WAVE_FORMAT_DRM        0x0009
+#define NOMMIDS
+#define DYNAMIC_GUID(data) {data}
+#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
+#undef DEFINE_GUID
+#define DEFINE_GUID(n,data) EXTERN_C const GUID n = {data}
+#define DEFINE_GUID_THUNK(n,data) DEFINE_GUID(n,data)
+#define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n)
+#endif
+
+#include <setupapi.h>
+
+#ifndef EXTERN_C
+#define EXTERN_C extern
 #endif
 
+#if defined(__GNUC__)
+
+/* For MinGW we reference mingw-include files supplied with WASAPI */
+#define WINBOOL BOOL
+
+#include "../wasapi/mingw-include/ks.h"
+#include "../wasapi/mingw-include/ksmedia.h"
+
+#else
+
+#include <mmreg.h>
 #include <ks.h>
+
+/* Note that Windows SDK V6.0A or later is needed for WaveRT specific structs to be present in
+   ksmedia.h. Also make sure that the SDK include path is before other include paths (that may contain
+   an "old" ksmedia.h), so the proper ksmedia.h is used */
 #include <ksmedia.h>
-#include <tchar.h>
+
+#endif
+
 #include <assert.h>
 #include <stdio.h>
 
 /* These next definitions allow the use of the KSUSER DLL */
-typedef /*KSDDKAPI ppgb 20080707*/ DWORD WINAPI KSCREATEPIN(HANDLE, PKSPIN_CONNECT, ACCESS_MASK, PHANDLE);
+typedef /*KSDDKAPI*/ DWORD WINAPI KSCREATEPIN(HANDLE, PKSPIN_CONNECT, ACCESS_MASK, PHANDLE);
 extern HMODULE      DllKsUser;
 extern KSCREATEPIN* FunctionKsCreatePin;
 
+/* These definitions allows the use of AVRT.DLL on Vista and later OSs */
+typedef enum _PA_AVRT_PRIORITY
+{
+    PA_AVRT_PRIORITY_LOW = -1,
+    PA_AVRT_PRIORITY_NORMAL,
+    PA_AVRT_PRIORITY_HIGH,
+    PA_AVRT_PRIORITY_CRITICAL
+} PA_AVRT_PRIORITY, *PPA_AVRT_PRIORITY;
+
+typedef struct
+{
+    HINSTANCE hInstance;
+
+    HANDLE  (WINAPI *AvSetMmThreadCharacteristics) (LPCSTR, LPDWORD);
+    BOOL    (WINAPI *AvRevertMmThreadCharacteristics) (HANDLE);
+    BOOL    (WINAPI *AvSetMmThreadPriority) (HANDLE, PA_AVRT_PRIORITY);
+} PaWinWDMKSAvRtEntryPoints;
+
+static PaWinWDMKSAvRtEntryPoints paWinWDMKSAvRtEntryPoints = {0};
+
+/* An unspecified channel count (-1) is not treated correctly, so we replace it with
+* an arbitrarily large number */ 
+#define MAXIMUM_NUMBER_OF_CHANNELS 256
+
 /* Forward definition to break circular type reference between pin and filter */
 struct __PaWinWdmFilter;
 typedef struct __PaWinWdmFilter PaWinWdmFilter;
 
+struct __PaWinWdmPin;
+typedef struct __PaWinWdmPin PaWinWdmPin;
+
+struct __PaWinWdmStream;
+typedef struct __PaWinWdmStream PaWinWdmStream;
+
+/* Function prototype for getting audio position */
+typedef PaError (*FunctionGetPinAudioPosition)(PaWinWdmPin*, unsigned long*);
+
+/* Function prototype for memory barrier */
+typedef void (*FunctionMemoryBarrier)(void);
+
+struct __PaProcessThreadInfo;
+typedef struct __PaProcessThreadInfo PaProcessThreadInfo;
+
+typedef PaError (*FunctionPinHandler)(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+
+typedef enum __PaStreamStartEnum
+{
+    StreamStart_kOk,
+    StreamStart_kFailed,
+    StreamStart_kCnt
+} PaStreamStartEnum;
+
+/* Multiplexed input structure.
+*  Very often several physical inputs are multiplexed through a MUX node (represented in the topology filter) */
+typedef struct __PaWinWdmMuxedInput
+{
+    wchar_t                     friendlyName[MAX_PATH];
+    ULONG                       muxPinId;
+    ULONG                       muxNodeId;
+    ULONG                       endpointPinId;
+} PaWinWdmMuxedInput;
+
 /* The Pin structure
- * A pin is an input or output node, e.g. for audio flow */
-typedef struct __PaWinWdmPin
+* A pin is an input or output node, e.g. for audio flow */
+struct __PaWinWdmPin
 {
     HANDLE                      handle;
+    PaWinWdmMuxedInput**        inputs;
+    unsigned                    inputCount;
+    wchar_t                     friendlyName[MAX_PATH];
+
     PaWinWdmFilter*             parentFilter;
+    PaWDMKSSubType              pinKsSubType;
     unsigned long               pinId;
+    unsigned long               endpointPinId;  /* For output pins */
     KSPIN_CONNECT*              pinConnect;
     unsigned long               pinConnectSize;
     KSDATAFORMAT_WAVEFORMATEX*  ksDataFormatWfx;
@@ -175,26 +279,46 @@ typedef struct __PaWinWdmPin
     unsigned long               frameSize;
     int                         maxChannels;
     unsigned long               formats;
-    int                         bestSampleRate;
-}
-PaWinWdmPin;
+    int                         defaultSampleRate;
+    ULONG                       *positionRegister;  /* WaveRT */
+    ULONG                       hwLatency;          /* WaveRT */
+    FunctionMemoryBarrier       fnMemBarrier;       /* WaveRT */
+    FunctionGetPinAudioPosition fnAudioPosition;    /* WaveRT */
+    FunctionPinHandler          fnEventHandler;
+    FunctionPinHandler          fnSubmitHandler;
+};
 
 /* The Filter structure
- * A filter has a number of pins and a "friendly name" */
+* A filter has a number of pins and a "friendly name" */
 struct __PaWinWdmFilter
 {
     HANDLE         handle;
+    PaWinWDMKSDeviceInfo    devInfo;  /* This will hold information that is exposed in PaDeviceInfo */
+
+    DWORD            deviceNode;
     int            pinCount;
     PaWinWdmPin**  pins;
-    TCHAR          filterName[MAX_PATH];
-    TCHAR          friendlyName[MAX_PATH];
-    int            maxInputChannels;
-    int            maxOutputChannels;
-    unsigned long  formats;
+    PaWinWdmFilter*  topologyFilter;
+    wchar_t          friendlyName[MAX_PATH];
+    int              validPinCount;
     int            usageCount;
-    int            bestSampleRate;
+    KSMULTIPLE_ITEM* connections;
+    KSMULTIPLE_ITEM* nodes;
+    int              filterRefCount;
 };
 
+
+typedef struct __PaWinWdmDeviceInfo
+{
+    PaDeviceInfo    inheritedDeviceInfo;
+    char            compositeName[MAX_PATH];   /* Composite name consists of pin name + device name in utf8 */
+    PaWinWdmFilter* filter;
+    unsigned long   pin;
+    int             muxPosition;    /* Used only for input devices */
+    int             endpointPinId;
+}
+PaWinWdmDeviceInfo;
+
 /* PaWinWdmHostApiRepresentation - host api datastructure specific to this implementation */
 typedef struct __PaWinWdmHostApiRepresentation
 {
@@ -203,59 +327,105 @@ typedef struct __PaWinWdmHostApiRepresentation
     PaUtilStreamInterface        blockingStreamInterface;
 
     PaUtilAllocationGroup*       allocations;
-    PaWinWdmFilter**             filters;
-    int                          filterCount;
+    int                          deviceCount;
 }
 PaWinWdmHostApiRepresentation;
 
-typedef struct __PaWinWdmDeviceInfo
-{
-    PaDeviceInfo     inheritedDeviceInfo;
-    PaWinWdmFilter*  filter;
-}
-PaWinWdmDeviceInfo;
-
 typedef struct __DATAPACKET
 {
     KSSTREAM_HEADER  Header;
     OVERLAPPED       Signal;
 } DATAPACKET;
 
+typedef struct __PaIOPacket
+{
+    DATAPACKET*     packet;
+    unsigned        startByte;
+    unsigned        lengthBytes;
+} PaIOPacket;
+
+typedef struct __PaWinWdmIOInfo
+{
+    PaWinWdmPin*        pPin;
+    char*               hostBuffer;
+    unsigned            hostBufferSize;
+    unsigned            framesPerBuffer;
+    unsigned            bytesPerFrame;
+    unsigned            bytesPerSample;
+    unsigned            noOfPackets;    /* Only used in WaveCyclic */
+    HANDLE              *events;        /* noOfPackets handles (WaveCyclic) 1 (WaveRT) */
+    DATAPACKET          *packets;       /* noOfPackets packets (WaveCyclic) 2 (WaveRT) */
+    /* WaveRT polled mode */
+    unsigned            lastPosition; 
+    unsigned            pollCntr;
+} PaWinWdmIOInfo;
+
 /* PaWinWdmStream - a stream data structure specifically for this implementation */
-typedef struct __PaWinWdmStream
+struct __PaWinWdmStream
 {
     PaUtilStreamRepresentation  streamRepresentation;
+    PaWDMKSSpecificStreamInfo   hostApiStreamInfo;    /* This holds info that is exposed through PaStreamInfo */
     PaUtilCpuLoadMeasurer       cpuLoadMeasurer;
     PaUtilBufferProcessor       bufferProcessor;
 
-    PaWinWdmPin*                recordingPin;
-    PaWinWdmPin*                playbackPin;
-    char*                       hostBuffer;
-    unsigned long               framesPerHostIBuffer;
-    unsigned long               framesPerHostOBuffer;
-    int                         bytesPerInputFrame;
-    int                         bytesPerOutputFrame;
+#if PA_TRACE_REALTIME_EVENTS
+    LogHandle                   hLog;
+#endif
+
+    PaUtilAllocationGroup*      allocGroup;
+    PaWinWdmIOInfo              capture;
+    PaWinWdmIOInfo              render;
     int                         streamStarted;
     int                         streamActive;
     int                         streamStop;
     int                         streamAbort;
     int                         oldProcessPriority;
     HANDLE                      streamThread;
-    HANDLE                      events[5];  /* 2 play + 2 record packets + abort events */
-    DATAPACKET                  packets[4]; /* 2 play + 2 record */
+    HANDLE                      eventAbort;
+    HANDLE                      eventStreamStart[StreamStart_kCnt];        /* 0 = OK, 1 = Failed */
+    PaError                     threadResult;
     PaStreamFlags               streamFlags;
+
+    /* Capture ring buffer */
+    PaUtilRingBuffer            ringBuffer;
+    char*                       ringBufferData;
+
     /* These values handle the case where the user wants to use fewer
-     * channels than the device has */
+    * channels than the device has */
     int                         userInputChannels;
     int                         deviceInputChannels;
     int                         userOutputChannels;
     int                         deviceOutputChannels;
-    int                         inputSampleSize;
-    int                         outputSampleSize;
-}
-PaWinWdmStream;
+};
 
-#include <setupapi.h>
+/* Gather all processing variables in a struct */
+struct __PaProcessThreadInfo 
+{
+    PaWinWdmStream              *stream;
+    PaStreamCallbackTimeInfo    ti;
+    PaStreamCallbackFlags       underover;
+    int                         cbResult;
+    volatile int                pending;
+    volatile int                priming;
+    volatile int                pinsStarted;
+    unsigned long               timeout;
+    unsigned                    captureHead;
+    unsigned                    captureTail;
+    unsigned                    renderHead;
+    unsigned                    renderTail;
+    PaIOPacket                  capturePackets[4];
+    PaIOPacket                  renderPackets[4];
+};
+
+/* Used for transferring device infos during scanning / rescanning */
+typedef struct __PaWinWDMScanDeviceInfosResults
+{ 
+    PaDeviceInfo **deviceInfos;
+    PaDeviceIndex defaultInputDevice;
+    PaDeviceIndex defaultOutputDevice;
+} PaWinWDMScanDeviceInfosResults;
+
+static const unsigned cPacketsArrayMask = 3;
 
 HMODULE      DllKsUser = NULL;
 KSCREATEPIN* FunctionKsCreatePin = NULL;
@@ -267,7 +437,7 @@ extern "C"
 {
 #endif /* __cplusplus */
 
-PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+    PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
 
 #ifdef __cplusplus
 }
@@ -275,37 +445,50 @@ PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
 
 /* Low level I/O functions */
 static PaError WdmSyncIoctl(HANDLE handle,
-    unsigned long ioctlNumber,
-    void* inBuffer,
-    unsigned long inBufferCount,
-    void* outBuffer,
-    unsigned long outBufferCount,
-    unsigned long* bytesReturned);
+                            unsigned long ioctlNumber,
+                            void* inBuffer,
+                            unsigned long inBufferCount,
+                            void* outBuffer,
+                            unsigned long outBufferCount,
+                            unsigned long* bytesReturned);
+
 static PaError WdmGetPropertySimple(HANDLE handle,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    void* value,
-    unsigned long valueCount,
-    void* instance,
-    unsigned long instanceCount);
+                                    const GUID* const guidPropertySet,
+                                    unsigned long property,
+                                    void* value,
+                                    unsigned long valueCount);
+
 static PaError WdmSetPropertySimple(HANDLE handle,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    void* value,
-    unsigned long valueCount,
-    void* instance,
-    unsigned long instanceCount);
+                                    const GUID* const guidPropertySet,
+                                    unsigned long property,
+                                    void* value,
+                                    unsigned long valueCount,
+                                    void* instance,
+                                    unsigned long instanceCount);
+
 static PaError WdmGetPinPropertySimple(HANDLE  handle,
-    unsigned long pinId,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    void* value,
-    unsigned long valueCount);
+                                       unsigned long pinId,
+                                       const GUID* const guidPropertySet,
+                                       unsigned long property,
+                                       void* value,
+                                       unsigned long valueCount,
+                                       unsigned long* byteCount);
+
 static PaError WdmGetPinPropertyMulti(HANDLE  handle,
-    unsigned long pinId,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    KSMULTIPLE_ITEM** ksMultipleItem);
+                                      unsigned long pinId,
+                                      const GUID* const guidPropertySet,
+                                      unsigned long property,
+                                      KSMULTIPLE_ITEM** ksMultipleItem);
+
+static PaError WdmGetPropertyMulti(HANDLE handle,
+                                   const GUID* const guidPropertySet,
+                                   unsigned long property,
+                                   KSMULTIPLE_ITEM** ksMultipleItem);
+
+static PaError WdmSetMuxNodeProperty(HANDLE handle,
+                                     ULONG nodeId,
+                                     ULONG pinId);
+
 
 /** Pin management functions */
 static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error);
@@ -316,49 +499,47 @@ static PaError PinInstantiate(PaWinWdmPin* pin);
 static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state);
 static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format);
 static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format);
+/* WaveRT support */
+static PaError PinQueryNotificationSupport(PaWinWdmPin* pPin, BOOL* pbResult);
+static PaError PinGetBuffer(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier);
+static PaError PinRegisterPositionRegister(PaWinWdmPin* pPin);
+static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle);
+static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle);
+static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChipsetDelay, ULONG* pCodecDelay);
+static PaError PinGetAudioPositionDirect(PaWinWdmPin* pPin, ULONG* pPosition);
+static PaError PinGetAudioPositionViaIOCTL(PaWinWdmPin* pPin, ULONG* pPosition);
 
 /* Filter management functions */
-static PaWinWdmFilter* FilterNew(
-    TCHAR* filterName,
-    TCHAR* friendlyName,
-    PaError* error);
+static PaWinWdmFilter* FilterNew(PaWDMKSType type, DWORD devNode, const wchar_t* filterName, const wchar_t* friendlyName, PaError* error);
+static PaError FilterInitializePins(PaWinWdmFilter* filter);
 static void FilterFree(PaWinWdmFilter* filter);
-static PaWinWdmPin* FilterCreateRenderPin(
-    PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error);
-static PaWinWdmPin* FilterFindViableRenderPin(
-    PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error);
-static PaError FilterCanCreateRenderPin(
-    PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex);
-static PaWinWdmPin* FilterCreateCapturePin(
-    PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error);
-static PaWinWdmPin* FilterFindViableCapturePin(
-    PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error);
-static PaError FilterCanCreateCapturePin(
-    PaWinWdmFilter* filter,
-    const WAVEFORMATEX* pwfx);
-static PaError FilterUse(
-    PaWinWdmFilter* filter);
-static void FilterRelease(
-    PaWinWdmFilter* filter);
+static void FilterAddRef(PaWinWdmFilter* filter);
+static PaWinWdmPin* FilterCreatePin(
+                                    PaWinWdmFilter* filter,
+                                    int pinId,
+                                    const WAVEFORMATEX* wfex,
+                                    PaError* error);
+static PaError FilterUse(PaWinWdmFilter* filter);
+static void FilterRelease(PaWinWdmFilter* filter);
+
+/* Hot plug functions */
+static BOOL IsDeviceTheSame(const PaWinWdmDeviceInfo* pDev1,
+                            const PaWinWdmDeviceInfo* pDev2);
 
 /* Interface functions */
 static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
 static PaError IsFormatSupported(
-    struct PaUtilHostApiRepresentation *hostApi,
+struct PaUtilHostApiRepresentation *hostApi,
     const PaStreamParameters *inputParameters,
     const PaStreamParameters *outputParameters,
     double sampleRate );
+
+static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void **newDeviceInfos, int *newDeviceCount );
+static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *deviceInfos, int deviceCount );
+static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, int deviceCount );
+
 static PaError OpenStream(
-    struct PaUtilHostApiRepresentation *hostApi,
+struct PaUtilHostApiRepresentation *hostApi,
     PaStream** s,
     const PaStreamParameters *inputParameters,
     const PaStreamParameters *outputParameters,
@@ -376,27 +557,108 @@ static PaError IsStreamActive( PaStream *stream );
 static PaTime GetStreamTime( PaStream *stream );
 static double GetStreamCpuLoad( PaStream* stream );
 static PaError ReadStream(
-    PaStream* stream,
-    void *buffer,
-    unsigned long frames );
+                          PaStream* stream,
+                          void *buffer,
+                          unsigned long frames );
 static PaError WriteStream(
-    PaStream* stream,
-    const void *buffer,
-    unsigned long frames );
+                           PaStream* stream,
+                           const void *buffer,
+                           unsigned long frames );
 static signed long GetStreamReadAvailable( PaStream* stream );
 static signed long GetStreamWriteAvailable( PaStream* stream );
 
 /* Utility functions */
 static unsigned long GetWfexSize(const WAVEFORMATEX* wfex);
-static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi);
+static PaWinWdmFilter** BuildFilterList(int* filterCount, int* noOfPaDevices, PaError* result);
 static BOOL PinWrite(HANDLE h, DATAPACKET* p);
 static BOOL PinRead(HANDLE h, DATAPACKET* p);
 static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples);
 static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples);
-static DWORD WINAPI ProcessingThread(LPVOID pParam);
+PA_THREAD_FUNC ProcessingThread(void*);
+
+/* Pin handler functions */
+static PaError PaPinCaptureEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinCaptureSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+
+static PaError PaPinRenderEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinRenderSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+
+static PaError PaPinCaptureEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinCaptureEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinCaptureSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinCaptureSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+
+static PaError PaPinRenderEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinRenderEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinRenderSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
+static PaError PaPinRenderSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);
 
 /* Function bodies */
 
+#if defined(_DEBUG) && defined(PA_ENABLE_DEBUG_OUTPUT)
+#define PA_WDMKS_SET_TREF
+static PaTime tRef = 0;
+
+static void PaWinWdmDebugPrintf(const char* fmt, ...)
+{
+    va_list list;
+    char buffer[1024];
+    PaTime t = PaUtil_GetTime() - tRef;
+    va_start(list, fmt);
+    _vsnprintf(buffer, 1023, fmt, list);
+    va_end(list);
+    PaUtil_DebugPrint("%6.3lf: %s", t, buffer);
+}
+
+#ifdef PA_DEBUG
+#undef PA_DEBUG
+#define PA_DEBUG(x)    PaWinWdmDebugPrintf x ;
+#endif
+#endif
+
+static BOOL IsDeviceTheSame(const PaWinWdmDeviceInfo* pDev1,
+                            const PaWinWdmDeviceInfo* pDev2)
+{
+    if (pDev1 == NULL || pDev2 == NULL)
+        return FALSE;
+
+    if (pDev1 == pDev2)
+        return TRUE;
+
+    if (strcmp(pDev1->compositeName, pDev2->compositeName) == 0)
+        return TRUE;
+
+    return FALSE;
+}
+
+static BOOL IsEarlierThanVista()
+{
+    OSVERSIONINFO osvi;
+    osvi.dwOSVersionInfoSize = sizeof(osvi);
+    if (GetVersionEx(&osvi) && osvi.dwMajorVersion<6)
+    {
+        return TRUE;
+    }
+    return FALSE;
+}
+
+
+
+static void MemoryBarrierDummy(void)
+{
+    /* Do nothing */
+}
+
+static void MemoryBarrierRead(void)
+{
+    PaUtil_ReadMemoryBarrier();
+}
+
+static void MemoryBarrierWrite(void)
+{
+    PaUtil_WriteMemoryBarrier();
+}
+
 static unsigned long GetWfexSize(const WAVEFORMATEX* wfex)
 {
     if( wfex->wFormatTag == WAVE_FORMAT_PCM )
@@ -409,133 +671,103 @@ static unsigned long GetWfexSize(const WAVEFORMATEX* wfex)
     }
 }
 
+static void PaWinWDM_SetLastErrorInfo(long errCode, const char* fmt, ...)
+{
+    va_list list;
+    char buffer[1024];
+    va_start(list, fmt);
+    _vsnprintf(buffer, 1023, fmt, list);
+    va_end(list);
+    PaUtil_SetLastHostErrorInfo(paWDMKS, errCode, buffer);
+}
+
 /*
 Low level pin/filter access functions
 */
 static PaError WdmSyncIoctl(
-    HANDLE handle,
-    unsigned long ioctlNumber,
-    void* inBuffer,
-    unsigned long inBufferCount,
-    void* outBuffer,
-    unsigned long outBufferCount,
-    unsigned long* bytesReturned)
+                            HANDLE handle,
+                            unsigned long ioctlNumber,
+                            void* inBuffer,
+                            unsigned long inBufferCount,
+                            void* outBuffer,
+                            unsigned long outBufferCount,
+                            unsigned long* bytesReturned)
 {
     PaError result = paNoError;
-    OVERLAPPED overlapped;
-    int boolResult;
-    unsigned long dummyBytesReturned;
-    unsigned long error;
+    unsigned long dummyBytesReturned = 0;
+    BOOL bRes;
 
     if( !bytesReturned )
     {
-        /* User a dummy as the caller hasn't supplied one */
+        /* Use a dummy as the caller hasn't supplied one */
         bytesReturned = &dummyBytesReturned;
     }
 
-    FillMemory((void *)&overlapped,sizeof(overlapped),0);
-    overlapped.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
-    if( !overlapped.hEvent )
-    {
-          result = paInsufficientMemory;
-        goto error;
-    }
-    overlapped.hEvent = (HANDLE)((DWORD_PTR)overlapped.hEvent | 0x1);
-
-    boolResult = DeviceIoControl(handle, ioctlNumber, inBuffer, inBufferCount,
-        outBuffer, outBufferCount, bytesReturned, &overlapped);
-    if( !boolResult )
+    bRes = DeviceIoControl(handle, ioctlNumber, inBuffer, inBufferCount, outBuffer, outBufferCount, bytesReturned, NULL);
+    if (!bRes)
     {
-        error = GetLastError();
-        if( error == ERROR_IO_PENDING )
-        {
-            error = WaitForSingleObject(overlapped.hEvent,INFINITE);
-            if( error != WAIT_OBJECT_0 )
-            {
-                result = paUnanticipatedHostError;
-                goto error;
-            }
-        }
-        else if((( error == ERROR_INSUFFICIENT_BUFFER ) ||
-                  ( error == ERROR_MORE_DATA )) &&
-                  ( ioctlNumber == IOCTL_KS_PROPERTY ) &&
-                  ( outBufferCount == 0 ))
-        {
-            boolResult = TRUE;
-        }
-        else
+        unsigned long error = GetLastError();
+        if ( !(((error == ERROR_INSUFFICIENT_BUFFER ) || ( error == ERROR_MORE_DATA )) && 
+            ( ioctlNumber == IOCTL_KS_PROPERTY ) &&
+            ( outBufferCount == 0 ) ) ) 
         {
+            KSPROPERTY* ksProperty = (KSPROPERTY*)inBuffer;
+
+            PaWinWDM_SetLastErrorInfo(result, "WdmSyncIoctl: DeviceIoControl GLE = 0x%08X (prop_set = {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}, prop_id = %u)",
+                error,
+                ksProperty->Set.Data1, ksProperty->Set.Data2, ksProperty->Set.Data3,
+                ksProperty->Set.Data4[0], ksProperty->Set.Data4[1],
+                ksProperty->Set.Data4[2], ksProperty->Set.Data4[3],
+                ksProperty->Set.Data4[4], ksProperty->Set.Data4[5],
+                ksProperty->Set.Data4[6], ksProperty->Set.Data4[7],
+                ksProperty->Id
+                );
             result = paUnanticipatedHostError;
         }
     }
-    if( !boolResult )
-        *bytesReturned = 0;
-
-error:
-    if( overlapped.hEvent )
-    {
-            CloseHandle( overlapped.hEvent );
-    }
     return result;
 }
 
 static PaError WdmGetPropertySimple(HANDLE handle,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    void* value,
-    unsigned long valueCount,
-    void* instance,
-    unsigned long instanceCount)
+                                    const GUID* const guidPropertySet,
+                                    unsigned long property,
+                                    void* value,
+                                    unsigned long valueCount)
 {
     PaError result;
-    KSPROPERTY* ksProperty;
-    unsigned long propertyCount;
-
-    propertyCount = sizeof(KSPROPERTY) + instanceCount;
-    ksProperty = (KSPROPERTY*)PaUtil_AllocateMemory( propertyCount );
-    if( !ksProperty )
-    {
-        return paInsufficientMemory;
-    }
-
-    FillMemory((void*)ksProperty,sizeof(ksProperty),0);
-    ksProperty->Set = *guidPropertySet;
-    ksProperty->Id = property;
-    ksProperty->Flags = KSPROPERTY_TYPE_GET;
+    KSPROPERTY ksProperty;
 
-    if( instance )
-    {
-        memcpy( (void*)(((char*)ksProperty)+sizeof(KSPROPERTY)), instance, instanceCount );
-    }
+    ksProperty.Set = *guidPropertySet;
+    ksProperty.Id = property;
+    ksProperty.Flags = KSPROPERTY_TYPE_GET;
 
     result = WdmSyncIoctl(
-                handle,
-                IOCTL_KS_PROPERTY,
-                ksProperty,
-                propertyCount,
-                value,
-                valueCount,
-                NULL);
-
-    PaUtil_FreeMemory( ksProperty );
+        handle,
+        IOCTL_KS_PROPERTY,
+        &ksProperty,
+        sizeof(KSPROPERTY),
+        value,
+        valueCount,
+        NULL);
+
     return result;
 }
 
 static PaError WdmSetPropertySimple(
-    HANDLE handle,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    void* value,
-    unsigned long valueCount,
-    void* instance,
-    unsigned long instanceCount)
+                                    HANDLE handle,
+                                    const GUID* const guidPropertySet,
+                                    unsigned long property,
+                                    void* value,
+                                    unsigned long valueCount,
+                                    void* instance,
+                                    unsigned long instanceCount)
 {
     PaError result;
     KSPROPERTY* ksProperty;
     unsigned long propertyCount  = 0;
 
     propertyCount = sizeof(KSPROPERTY) + instanceCount;
-    ksProperty = (KSPROPERTY*)PaUtil_AllocateMemory( propertyCount );
+    ksProperty = (KSPROPERTY*)_alloca( propertyCount );
     if( !ksProperty )
     {
         return paInsufficientMemory;
@@ -551,25 +783,25 @@ static PaError WdmSetPropertySimple(
     }
 
     result = WdmSyncIoctl(
-                handle,
-                IOCTL_KS_PROPERTY,
-                ksProperty,
-                propertyCount,
-                value,
-                valueCount,
-                NULL);
-
-    PaUtil_FreeMemory( ksProperty );
+        handle,
+        IOCTL_KS_PROPERTY,
+        ksProperty,
+        propertyCount,
+        value,
+        valueCount,
+        NULL);
+
     return result;
 }
 
 static PaError WdmGetPinPropertySimple(
-    HANDLE  handle,
-    unsigned long pinId,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    void* value,
-    unsigned long valueCount)
+                                       HANDLE  handle,
+                                       unsigned long pinId,
+                                       const GUID* const guidPropertySet,
+                                       unsigned long property,
+                                       void* value,
+                                       unsigned long valueCount,
+                                       unsigned long *byteCount)
 {
     PaError result;
 
@@ -581,23 +813,23 @@ static PaError WdmGetPinPropertySimple(
     ksPProp.Reserved = 0;
 
     result = WdmSyncIoctl(
-                handle,
-                IOCTL_KS_PROPERTY,
-                &ksPProp,
-                sizeof(KSP_PIN),
-                value,
-                valueCount,
-                NULL);
+        handle,
+        IOCTL_KS_PROPERTY,
+        &ksPProp,
+        sizeof(KSP_PIN),
+        value,
+        valueCount,
+        byteCount);
 
     return result;
 }
 
 static PaError WdmGetPinPropertyMulti(
-    HANDLE handle,
-    unsigned long pinId,
-    const GUID* const guidPropertySet,
-    unsigned long property,
-    KSMULTIPLE_ITEM** ksMultipleItem)
+                                      HANDLE handle,
+                                      unsigned long pinId,
+                                      const GUID* const guidPropertySet,
+                                      unsigned long property,
+                                      KSMULTIPLE_ITEM** ksMultipleItem)
 {
     PaError result;
     unsigned long multipleItemSize = 0;
@@ -610,13 +842,13 @@ static PaError WdmGetPinPropertyMulti(
     ksPProp.Reserved = 0;
 
     result = WdmSyncIoctl(
-                handle,
-                IOCTL_KS_PROPERTY,
-                &ksPProp.Property,
-                sizeof(KSP_PIN),
-                NULL,
-                0,
-                &multipleItemSize);
+        handle,
+        IOCTL_KS_PROPERTY,
+        &ksPProp.Property,
+        sizeof(KSP_PIN),
+        NULL,
+        0,
+        &multipleItemSize);
     if( result != paNoError )
     {
         return result;
@@ -629,13 +861,13 @@ static PaError WdmGetPinPropertyMulti(
     }
 
     result = WdmSyncIoctl(
-                handle,
-                IOCTL_KS_PROPERTY,
-                &ksPProp,
-                sizeof(KSP_PIN),
-                (void*)*ksMultipleItem,
-                multipleItemSize,
-                NULL);
+        handle,
+        IOCTL_KS_PROPERTY,
+        &ksPProp,
+        sizeof(KSP_PIN),
+        (void*)*ksMultipleItem,
+        multipleItemSize,
+        NULL);
 
     if( result != paNoError )
     {
@@ -645,119 +877,514 @@ static PaError WdmGetPinPropertyMulti(
     return result;
 }
 
-
-/*
-Create a new pin object belonging to a filter
-The pin object holds all the configuration information about the pin
-before it is opened, and then the handle of the pin after is opened
-*/
-static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error)
+static PaError WdmGetPropertyMulti(HANDLE handle,
+                                   const GUID* const guidPropertySet,
+                                   unsigned long property,
+                                   KSMULTIPLE_ITEM** ksMultipleItem)
 {
-    PaWinWdmPin* pin;
     PaError result;
-    unsigned long i;
-    KSMULTIPLE_ITEM* item = NULL;
-    KSIDENTIFIER* identifier;
-    KSDATARANGE* dataRange;
+    unsigned long multipleItemSize = 0;
+    KSPROPERTY ksProp;
 
-    PA_LOGE_;
-    PA_DEBUG(("Creating pin %d:\n",pinId));
+    ksProp.Set = *guidPropertySet;
+    ksProp.Id = property;
+    ksProp.Flags = KSPROPERTY_TYPE_GET;
 
-    /* Allocate the new PIN object */
-    pin = (PaWinWdmPin*)PaUtil_AllocateMemory( sizeof(PaWinWdmPin) );
-    if( !pin )
+    result = WdmSyncIoctl(
+        handle,
+        IOCTL_KS_PROPERTY,
+        &ksProp,
+        sizeof(KSPROPERTY),
+        NULL,
+        0,
+        &multipleItemSize);
+    if( result != paNoError )
     {
-        result = paInsufficientMemory;
-        goto error;
+        return result;
     }
 
-    /* Zero the pin object */
-    /* memset( (void*)pin, 0, sizeof(PaWinWdmPin) ); */
-
-    pin->parentFilter = parentFilter;
-    pin->pinId = pinId;
-
-    /* Allocate a connect structure */
-    pin->pinConnectSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX);
-    pin->pinConnect = (KSPIN_CONNECT*)PaUtil_AllocateMemory( pin->pinConnectSize );
-    if( !pin->pinConnect )
+    *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize );
+    if( !*ksMultipleItem )
     {
-        result = paInsufficientMemory;
-        goto error;
+        return paInsufficientMemory;
     }
 
-    /* Configure the connect structure with default values */
-    pin->pinConnect->Interface.Set               = KSINTERFACESETID_Standard;
-    pin->pinConnect->Interface.Id                = KSINTERFACE_STANDARD_STREAMING;
-    pin->pinConnect->Interface.Flags             = 0;
-    pin->pinConnect->Medium.Set                  = KSMEDIUMSETID_Standard;
-    pin->pinConnect->Medium.Id                   = KSMEDIUM_TYPE_ANYINSTANCE;
-    pin->pinConnect->Medium.Flags                = 0;
-    pin->pinConnect->PinId                       = pinId;
-    pin->pinConnect->PinToHandle                 = NULL;
-    pin->pinConnect->Priority.PriorityClass      = KSPRIORITY_NORMAL;
-    pin->pinConnect->Priority.PrioritySubClass   = 1;
-    pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)(pin->pinConnect + 1);
-    pin->ksDataFormatWfx->DataFormat.FormatSize  = sizeof(KSDATAFORMAT_WAVEFORMATEX);
-    pin->ksDataFormatWfx->DataFormat.Flags       = 0;
-    pin->ksDataFormatWfx->DataFormat.Reserved    = 0;
-    pin->ksDataFormatWfx->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO;
-    pin->ksDataFormatWfx->DataFormat.SubFormat   = KSDATAFORMAT_SUBTYPE_PCM;
-    pin->ksDataFormatWfx->DataFormat.Specifier   = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX;
-
-    pin->frameSize = 0; /* Unknown until we instantiate pin */
+    result = WdmSyncIoctl(
+        handle,
+        IOCTL_KS_PROPERTY,
+        &ksProp,
+        sizeof(KSPROPERTY),
+        (void*)*ksMultipleItem,
+        multipleItemSize,
+        NULL);
 
-    /* Get the COMMUNICATION property */
-    result = WdmGetPinPropertySimple(
-        parentFilter->handle,
-        pinId,
-        &KSPROPSETID_Pin,
-        KSPROPERTY_PIN_COMMUNICATION,
-        &pin->communication,
-        sizeof(KSPIN_COMMUNICATION));
     if( result != paNoError )
-        goto error;
-
-    if( /*(pin->communication != KSPIN_COMMUNICATION_SOURCE) &&*/
-         (pin->communication != KSPIN_COMMUNICATION_SINK) &&
-         (pin->communication != KSPIN_COMMUNICATION_BOTH) )
     {
-        PA_DEBUG(("Not source/sink\n"));
-        result = paInvalidDevice;
-        goto error;
+        PaUtil_FreeMemory( ksMultipleItem );
     }
 
-    /* Get dataflow information */
-    result = WdmGetPinPropertySimple(
-        parentFilter->handle,
-        pinId,
-        &KSPROPSETID_Pin,
-        KSPROPERTY_PIN_DATAFLOW,
-        &pin->dataFlow,
-        sizeof(KSPIN_DATAFLOW));
-
-    if( result != paNoError )
-        goto error;
+    return result;
+}
 
-    /* Get the INTERFACE property list */
-    result = WdmGetPinPropertyMulti(
-        parentFilter->handle,
-        pinId,
-        &KSPROPSETID_Pin,
-        KSPROPERTY_PIN_INTERFACES,
-        &item);
+static PaError WdmSetMuxNodeProperty(HANDLE handle,
+                                     ULONG nodeId,
+                                     ULONG pinId)
+{
+    PaError result = paNoError;
+    KSNODEPROPERTY prop;
+    prop.Property.Set = KSPROPSETID_Audio;
+    prop.Property.Id = KSPROPERTY_AUDIO_MUX_SOURCE;
+    prop.Property.Flags = KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_TOPOLOGY;
+    prop.NodeId = nodeId;
+    prop.Reserved = 0;
 
-    if( result != paNoError )
-        goto error;
+    result = WdmSyncIoctl(handle, IOCTL_KS_PROPERTY, &prop, sizeof(KSNODEPROPERTY), &pinId, sizeof(ULONG), NULL);
 
-    identifier = (KSIDENTIFIER*)(item+1);
+    return result;
+}
 
-    /* Check that at least one interface is STANDARD_STREAMING */
+/* Used when traversing topology for outputs */
+static const KSTOPOLOGY_CONNECTION* GetConnectionTo(const KSTOPOLOGY_CONNECTION* pFrom, PaWinWdmFilter* filter, int muxIdx)
+{
+    unsigned i;
+    const KSTOPOLOGY_CONNECTION* retval = NULL;
+    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
+    (void)muxIdx;
+    PA_DEBUG(("GetConnectionTo: Checking %u connections... (pFrom = %p)", filter->connections->Count, pFrom));
+    for (i = 0; i < filter->connections->Count; ++i)
+    {
+        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
+        if (pConn == pFrom)
+            continue;
+
+        if (pConn->FromNode == pFrom->ToNode)
+        {
+            retval = pConn;
+            break;
+        }
+    }
+    PA_DEBUG(("GetConnectionTo: Returning %p\n", retval));
+    return retval;
+}
+
+/* Used when traversing topology for inputs */
+static const KSTOPOLOGY_CONNECTION* GetConnectionFrom(const KSTOPOLOGY_CONNECTION* pTo, PaWinWdmFilter* filter, int muxIdx)
+{
+    unsigned i;
+    const KSTOPOLOGY_CONNECTION* retval = NULL;
+    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
+    int muxCntr = 0;
+    PA_DEBUG(("GetConnectionFrom: Checking %u connections... (pTo = %p)\n", filter->connections->Count, pTo));
+    for (i = 0; i < filter->connections->Count; ++i)
+    {
+        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
+        if (pConn == pTo)
+            continue;
+
+        if (pConn->ToNode == pTo->FromNode)
+        {
+            if (muxIdx >= 0)
+            {
+                if (muxCntr < muxIdx)
+                {
+                    ++muxCntr;
+                    continue;
+                }
+            }
+            retval = pConn;
+            break;
+        }
+    }
+    PA_DEBUG(("GetConnectionFrom: Returning %p\n", retval));
+    return retval;
+}
+
+static ULONG GetNumberOfConnectionsTo(const KSTOPOLOGY_CONNECTION* pTo, PaWinWdmFilter* filter)
+{
+    ULONG retval = 0;
+    unsigned i;
+    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
+    PA_DEBUG(("GetNumberOfConnectionsTo: Checking %u connections...", filter->connections->Count));
+    for (i = 0; i < filter->connections->Count; ++i)
+    {
+        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
+        if (pConn->ToNode == pTo->FromNode &&
+            (pTo->FromNode != KSFILTER_NODE || pConn->ToNodePin == pTo->FromNodePin))
+        {
+            ++retval;
+        }
+    }
+    return retval;
+}
+
+typedef const KSTOPOLOGY_CONNECTION *(*TFnGetConnection)(const KSTOPOLOGY_CONNECTION*, PaWinWdmFilter*, int);
+
+static const KSTOPOLOGY_CONNECTION* FindStartConnectionFrom(ULONG startPin, PaWinWdmFilter* filter)
+{
+    unsigned i;
+    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
+    PA_DEBUG(("FindStartConnectionFrom: Checking %u connections...", filter->connections->Count));
+    for (i = 0; i < filter->connections->Count; ++i)
+    {
+        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
+        if (pConn->ToNode == KSFILTER_NODE && pConn->ToNodePin == startPin)
+        {
+            return pConn;
+        }
+    }
+
+    assert(FALSE);
+    return 0;
+}
+
+static const KSTOPOLOGY_CONNECTION* FindStartConnectionTo(ULONG startPin, PaWinWdmFilter* filter)
+{
+    unsigned i;
+    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
+    PA_DEBUG(("FindStartConnectionTo: Checking %u connections...", filter->connections->Count));
+    for (i = 0; i < filter->connections->Count; ++i)
+    {
+        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
+        if (pConn->FromNode == KSFILTER_NODE && pConn->FromNodePin == startPin)
+        {
+            return pConn;
+        }
+    }
+
+    assert(FALSE);
+    return 0;
+}
+
+static ULONG GetConnectedPin(ULONG startPin, BOOL forward, PaWinWdmFilter* filter, int muxPosition, ULONG *muxInputPinId, ULONG *muxNodeId)
+{
+    const KSTOPOLOGY_CONNECTION *conn = NULL; 
+    TFnGetConnection fnGetConnection = forward ? GetConnectionTo : GetConnectionFrom ;
+    while (1)
+    {
+        if (conn == NULL)
+        {
+            conn = forward ? FindStartConnectionTo(startPin, filter) : FindStartConnectionFrom(startPin, filter);
+        }
+        else
+        {
+            conn = fnGetConnection(conn, filter, -1);
+        }
+
+        /* Handling case of erroneous connection list */
+        if (conn == NULL)
+        {
+            break;
+        }
+
+        if (forward ? conn->ToNode == KSFILTER_NODE : conn->FromNode == KSFILTER_NODE)
+        {
+            return forward ? conn->ToNodePin : conn->FromNodePin;
+        }
+        else
+        {
+            PA_DEBUG(("GetConnectedPin: count=%d, forward=%d, muxPosition=%d\n", filter->nodes->Count, forward, muxPosition));
+            if (filter->nodes->Count > 0 && !forward && muxPosition >= 0)
+            {
+                const GUID* nodes = (const GUID*)(filter->nodes + 1);
+                if (IsEqualGUID(&nodes[conn->FromNode], &KSNODETYPE_MUX))
+                {
+                    ULONG nConn = GetNumberOfConnectionsTo(conn, filter);
+                    conn = fnGetConnection(conn, filter, muxPosition);
+                    if (conn == NULL)
+                    {
+                        break;
+                    }
+                    if (muxInputPinId != 0)
+                    {
+                        *muxInputPinId = conn->ToNodePin;
+                    }
+                    if (muxNodeId != 0)
+                    {
+                        *muxNodeId = conn->ToNode;
+                    }
+                }
+            }
+        }
+    }
+    return KSFILTER_NODE;
+}
+
+static void DumpConnectionsAndNodes(PaWinWdmFilter* filter)
+{
+    unsigned i;
+    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
+    const GUID* nodes = (const GUID*)(filter->nodes + 1);
+
+    PA_DEBUG(("DumpConnectionsAndNodes: connections=%d, nodes=%d\n", filter->connections->Count, filter->nodes->Count));
+
+    for (i=0; i < filter->connections->Count; ++i)
+    {
+        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
+        PA_DEBUG(("  Connection: %u - FromNode=%u,FromPin=%u -> ToNode=%u,ToPin=%u\n", 
+            i,
+            pConn->FromNode, pConn->FromNodePin,
+            pConn->ToNode, pConn->ToNodePin
+            ));
+    }
+
+    for (i=0; i < filter->nodes->Count; ++i)
+    {
+        const GUID* pConn = nodes + i;
+        PA_DEBUG(("  Node: %d - {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n",
+            i,
+            pConn->Data1, pConn->Data2, pConn->Data3,
+            pConn->Data4[0], pConn->Data4[1],
+            pConn->Data4[2], pConn->Data4[3],
+            pConn->Data4[4], pConn->Data4[5],
+            pConn->Data4[6], pConn->Data4[7]
+        ));
+    }
+
+}
+
+typedef struct __PaUsbTerminalGUIDToName 
+{
+    USHORT     usbGUID;
+    wchar_t    name[64];
+} PaUsbTerminalGUIDToName;
+
+static const PaUsbTerminalGUIDToName kNames[] =
+{
+    /* Types copied from: http://msdn.microsoft.com/en-us/library/ff537742(v=vs.85).aspx */
+    /* Input terminal types */
+    { 0x0201, L"Microphone" },
+    { 0x0202, L"Desktop Microphone" },
+    { 0x0203, L"Personal Microphone" },
+    { 0x0204, L"Omni Directional Microphone" },
+    { 0x0205, L"Microphone Array" },
+    { 0x0206, L"Processing Microphone Array" },
+    /* Output terminal types */
+    { 0x0301, L"Speakers" },
+    { 0x0302, L"Headphones" },
+    { 0x0303, L"Head Mounted Display Audio" },
+    { 0x0304, L"Desktop Speaker" },
+    { 0x0305, L"Room Speaker" },
+    { 0x0306, L"Communication Speaker" },
+    { 0x0307, L"LFE Speakers" },
+    /* External terminal types */
+    { 0x0601, L"Analog" },
+    { 0x0602, L"Digital" },
+    { 0x0603, L"Line" },
+    { 0x0604, L"Audio" },
+    { 0x0605, L"SPDIF" },
+};
+
+static const unsigned kNamesCnt = sizeof(kNames)/sizeof(PaUsbTerminalGUIDToName);
+
+static int PaUsbTerminalGUIDToNameCmp(const void* lhs, const void* rhs)
+{
+    const PaUsbTerminalGUIDToName* pL = (const PaUsbTerminalGUIDToName*)lhs;
+    const PaUsbTerminalGUIDToName* pR = (const PaUsbTerminalGUIDToName*)rhs;
+    return ((int)(pL->usbGUID) - (int)(pR->usbGUID));
+}
+
+static PaError GetNameFromCategory(const GUID* pGUID, BOOL input, wchar_t* name, unsigned length)
+{
+    PaError result = paUnanticipatedHostError;
+    USHORT usbTerminalGUID = (USHORT)(pGUID->Data1 - 0xDFF219E0);
+
+    if (input && usbTerminalGUID >= 0x301 && usbTerminalGUID < 0x400)
+    {
+        /* Output terminal name for an input !? Set it to Line! */
+        usbTerminalGUID = 0x603;
+    }
+    if (!input && usbTerminalGUID >= 0x201 && usbTerminalGUID < 0x300)
+    {
+        /* Input terminal name for an output !? Set it to Line! */
+        usbTerminalGUID = 0x603;
+    }
+    if (usbTerminalGUID >= 0x201 && usbTerminalGUID < 0x713)
+    {
+        PaUsbTerminalGUIDToName s = { usbTerminalGUID };
+        const PaUsbTerminalGUIDToName* ptr = bsearch(
+            &s,
+            kNames,
+            kNamesCnt,
+            sizeof(PaUsbTerminalGUIDToName),
+            PaUsbTerminalGUIDToNameCmp
+            );
+        if (ptr != 0)
+        {
+            PA_DEBUG(("GetNameFromCategory: USB GUID %04X -> '%S'\n", usbTerminalGUID, ptr->name));
+
+            if (name != NULL && length > 0)
+            {
+                int n = _snwprintf(name, length, L"%s", ptr->name);
+                if (usbTerminalGUID >= 0x601 && usbTerminalGUID < 0x700)
+                {
+                    _snwprintf(name + n, length - n, L" %s", (input ? L"In":L"Out"));
+                }
+            }
+            result = paNoError;
+        }
+    }
+    else
+    {
+        PaWinWDM_SetLastErrorInfo(result, "GetNameFromCategory: usbTerminalGUID = %04X ", usbTerminalGUID);
+    }
+    return result;
+}
+
+static BOOL IsFrequencyWithinRange(const KSDATARANGE_AUDIO* range, int frequency)
+{
+    if (frequency < (int)range->MinimumSampleFrequency)
+        return FALSE;
+    if (frequency > (int)range->MaximumSampleFrequency)
+        return FALSE;
+    return TRUE;
+}
+
+static BOOL IsBitsWithinRange(const KSDATARANGE_AUDIO* range, int noOfBits)
+{
+    if (noOfBits < (int)range->MinimumBitsPerSample)
+        return FALSE;
+    if (noOfBits > (int)range->MaximumBitsPerSample)
+        return FALSE;
+    return TRUE;
+}
+
+/* Note: Somewhat different order compared to WMME implementation, as we want to focus on fidelity first */
+static const int defaultSampleRateSearchOrder[] =
+{ 44100, 48000, 88200, 96000, 192000, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000 };
+static const int defaultSampleRateSearchOrderCount = sizeof(defaultSampleRateSearchOrder)/sizeof(defaultSampleRateSearchOrder[0]);
+
+static int DefaultSampleFrequencyIndex(const KSDATARANGE_AUDIO* range)
+{
+    int i;
+
+    for(i=0; i < defaultSampleRateSearchOrderCount; ++i)
+    {
+        int currentFrequency = defaultSampleRateSearchOrder[i];
+
+        if (IsFrequencyWithinRange(range, currentFrequency))
+        {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+/*
+Create a new pin object belonging to a filter
+The pin object holds all the configuration information about the pin
+before it is opened, and then the handle of the pin after is opened
+*/
+static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error)
+{
+    PaWinWdmPin* pin;
+    PaError result;
+    unsigned long i;
+    KSMULTIPLE_ITEM* item = NULL;
+    KSIDENTIFIER* identifier;
+    KSDATARANGE* dataRange;
+    const ULONG streamingId = (parentFilter->devInfo.streamingType == Type_kWaveRT) ? KSINTERFACE_STANDARD_LOOPED_STREAMING : KSINTERFACE_STANDARD_STREAMING;
+    int defaultSampleRateIndex = defaultSampleRateSearchOrderCount;
+
+    PA_LOGE_;
+    PA_DEBUG(("PinNew: Creating pin %d:\n",pinId));
+
+    /* Allocate the new PIN object */
+    pin = (PaWinWdmPin*)PaUtil_AllocateMemory( sizeof(PaWinWdmPin) );
+    if( !pin )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    /* Zero the pin object */
+    /* memset( (void*)pin, 0, sizeof(PaWinWdmPin) ); */
+
+    pin->parentFilter = parentFilter;
+    pin->pinId = pinId;
+
+    /* Allocate a connect structure */
+    pin->pinConnectSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX);
+    pin->pinConnect = (KSPIN_CONNECT*)PaUtil_AllocateMemory( pin->pinConnectSize );
+    if( !pin->pinConnect )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    /* Configure the connect structure with default values */
+    pin->pinConnect->Interface.Set               = KSINTERFACESETID_Standard;
+    pin->pinConnect->Interface.Id                = streamingId;
+    pin->pinConnect->Interface.Flags             = 0;
+    pin->pinConnect->Medium.Set                  = KSMEDIUMSETID_Standard;
+    pin->pinConnect->Medium.Id                   = KSMEDIUM_TYPE_ANYINSTANCE;
+    pin->pinConnect->Medium.Flags                = 0;
+    pin->pinConnect->PinId                       = pinId;
+    pin->pinConnect->PinToHandle                 = NULL;
+    pin->pinConnect->Priority.PriorityClass      = KSPRIORITY_NORMAL;
+    pin->pinConnect->Priority.PrioritySubClass   = 1;
+    pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)(pin->pinConnect + 1);
+    pin->ksDataFormatWfx->DataFormat.FormatSize  = sizeof(KSDATAFORMAT_WAVEFORMATEX);
+    pin->ksDataFormatWfx->DataFormat.Flags       = 0;
+    pin->ksDataFormatWfx->DataFormat.Reserved    = 0;
+    pin->ksDataFormatWfx->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO;
+    pin->ksDataFormatWfx->DataFormat.SubFormat   = KSDATAFORMAT_SUBTYPE_PCM;
+    pin->ksDataFormatWfx->DataFormat.Specifier   = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX;
+
+    pin->frameSize = 0; /* Unknown until we instantiate pin */
+
+    /* Get the COMMUNICATION property */
+    result = WdmGetPinPropertySimple(
+        parentFilter->handle,
+        pinId,
+        &KSPROPSETID_Pin,
+        KSPROPERTY_PIN_COMMUNICATION,
+        &pin->communication,
+        sizeof(KSPIN_COMMUNICATION),
+        NULL);
+    if( result != paNoError )
+        goto error;
+
+    if( /*(pin->communication != KSPIN_COMMUNICATION_SOURCE) &&*/
+        (pin->communication != KSPIN_COMMUNICATION_SINK) &&
+        (pin->communication != KSPIN_COMMUNICATION_BOTH) )
+    {
+        PA_DEBUG(("PinNew: Not source/sink\n"));
+        result = paInvalidDevice;
+        goto error;
+    }
+
+    /* Get dataflow information */
+    result = WdmGetPinPropertySimple(
+        parentFilter->handle,
+        pinId,
+        &KSPROPSETID_Pin,
+        KSPROPERTY_PIN_DATAFLOW,
+        &pin->dataFlow,
+        sizeof(KSPIN_DATAFLOW),
+        NULL);
+
+    if( result != paNoError )
+        goto error;
+
+    /* Get the INTERFACE property list */
+    result = WdmGetPinPropertyMulti(
+        parentFilter->handle,
+        pinId,
+        &KSPROPSETID_Pin,
+        KSPROPERTY_PIN_INTERFACES,
+        &item);
+
+    if( result != paNoError )
+        goto error;
+
+    identifier = (KSIDENTIFIER*)(item+1);
+
+    /* Check that at least one interface is STANDARD_STREAMING */
     result = paUnanticipatedHostError;
     for( i = 0; i < item->Count; i++ )
     {
-        if( !memcmp( (void*)&identifier[i].Set, (void*)&KSINTERFACESETID_Standard, sizeof( GUID ) ) &&
-            ( identifier[i].Id == KSINTERFACE_STANDARD_STREAMING ) )
+        if( IsEqualGUID(&identifier[i].Set, &KSINTERFACESETID_Standard) && ( identifier[i].Id == streamingId ) )
         {
             result = paNoError;
             break;
@@ -766,7 +1393,7 @@ static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, Pa
 
     if( result != paNoError )
     {
-        PA_DEBUG(("No standard streaming\n"));
+        PA_DEBUG(("PinNew: No %s streaming\n", streamingId==KSINTERFACE_STANDARD_LOOPED_STREAMING?"looped":"standard"));
         goto error;
     }
 
@@ -791,8 +1418,7 @@ static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, Pa
     result = paUnanticipatedHostError;
     for( i = 0; i < item->Count; i++ )
     {
-        if( !memcmp( (void*)&identifier[i].Set, (void*)&KSMEDIUMSETID_Standard, sizeof( GUID ) ) &&
-           ( identifier[i].Id == KSMEDIUM_STANDARD_DEVIO ) )
+        if( IsEqualGUID(&identifier[i].Set, &KSMEDIUMSETID_Standard) && ( identifier[i].Id == KSMEDIUM_STANDARD_DEVIO ) )
         {
             result = paNoError;
             break;
@@ -825,53 +1451,66 @@ static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, Pa
     result = paUnanticipatedHostError;
     dataRange = pin->dataRanges;
     pin->maxChannels = 0;
-    pin->bestSampleRate = 0;
+    pin->defaultSampleRate = 0;
     pin->formats = 0;
-    for( i = 0; i <pin->dataRangesItem->Count; i++)
+    PA_DEBUG(("PinNew: Checking %u no of dataranges...\n", pin->dataRangesItem->Count));
+    for( i = 0; i < pin->dataRangesItem->Count; i++)
     {
-        PA_DEBUG(("DR major format %x\n",*(unsigned long*)(&(dataRange->MajorFormat))));
+        PA_DEBUG(("PinNew: DR major format %x\n",*(unsigned long*)(&(dataRange->MajorFormat))));
         /* Check that subformat is WAVEFORMATEX, PCM or WILDCARD */
         if( IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat) ||
-            !memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_PCM, sizeof ( GUID ) ) ||
-            ( !memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof ( GUID ) ) &&
-            ( !memcmp((void*)&dataRange->MajorFormat, (void*)&KSDATAFORMAT_TYPE_AUDIO, sizeof ( GUID ) ) ) ) )
+            IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM) ||
+            IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) ||
+            IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD) ||
+            IsEqualGUID(&dataRange->MajorFormat, &KSDATAFORMAT_TYPE_AUDIO) )
         {
+            int defaultIndex;
             result = paNoError;
             /* Record the maximum possible channels with this pin */
-            PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels));
-            if( (int)((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels > pin->maxChannels )
+            if( ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels == (ULONG) -1 )
+            {
+                pin->maxChannels = MAXIMUM_NUMBER_OF_CHANNELS;
+            }
+            else if( (int) ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels > pin->maxChannels )
             {
-                pin->maxChannels = ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels;
-                /*PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels));*/
+                pin->maxChannels = (int) ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels;
             }
+            PA_DEBUG(("PinNew: MaxChannel: %d\n",pin->maxChannels));
+
             /* Record the formats (bit depths) that are supported */
-            if( ((KSDATARANGE_AUDIO*)dataRange)->MinimumBitsPerSample <= 16 )
+            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 8) )
             {
-                pin->formats |= paInt16;
-                PA_DEBUG(("Format 16 bit supported\n"));
+                pin->formats |= paInt8;
+                PA_DEBUG(("PinNew: Format PCM 8 bit supported\n"));
             }
-            if( ((KSDATARANGE_AUDIO*)dataRange)->MaximumBitsPerSample >= 24 )
+            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 16) )
             {
-                pin->formats |= paInt24;
-                PA_DEBUG(("Format 24 bit supported\n"));
+                pin->formats |= paInt16;
+                PA_DEBUG(("PinNew: Format PCM 16 bit supported\n"));
             }
-            if( ( pin->bestSampleRate != 48000) &&
-                (((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 48000) &&
-                (((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 48000) )
+            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 24) )
             {
-                pin->bestSampleRate = 48000;
-                PA_DEBUG(("48kHz supported\n"));
+                pin->formats |= paInt24;
+                PA_DEBUG(("PinNew: Format PCM 24 bit supported\n"));
             }
-            else if(( pin->bestSampleRate != 48000) && ( pin->bestSampleRate != 44100 ) &&
-                (((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 44100) &&
-                (((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 44100) )
+            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 32) )
             {
-                pin->bestSampleRate = 44100;
-                PA_DEBUG(("44.1kHz supported\n"));
+                if (IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
+                {
+                    pin->formats |= paFloat32;
+                    PA_DEBUG(("PinNew: Format IEEE float 32 bit supported\n"));
+                }
+                else
+                {
+                    pin->formats |= paInt32;
+                    PA_DEBUG(("PinNew: Format PCM 32 bit supported\n"));
+                }
             }
-            else
+
+            defaultIndex = DefaultSampleFrequencyIndex((KSDATARANGE_AUDIO*)dataRange);
+            if (defaultIndex >= 0 && defaultIndex < defaultSampleRateIndex)
             {
-                pin->bestSampleRate = ((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency;
+                defaultSampleRateIndex = defaultIndex;
             }
         }
         dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize);
@@ -880,18 +1519,433 @@ static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, Pa
     if( result != paNoError )
         goto error;
 
-    /* Get instance information */
+    /* If none of the frequencies searched for are present, there's something seriously wrong */
+    if (defaultSampleRateIndex == defaultSampleRateSearchOrderCount)
+    {
+        PA_DEBUG(("PinNew: No default sample rate found, skipping pin!\n"));
+        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "PinNew: No default sample rate found");
+        result = paUnanticipatedHostError;
+        goto error;
+    }
+
+    /* Set the default sample rate */
+    pin->defaultSampleRate = defaultSampleRateSearchOrder[defaultSampleRateIndex];
+    PA_DEBUG(("PinNew: Default sample rate = %d Hz\n", pin->defaultSampleRate));
+
+    /* Get instance information */
     result = WdmGetPinPropertySimple(
         parentFilter->handle,
         pinId,
         &KSPROPSETID_Pin,
         KSPROPERTY_PIN_CINSTANCES,
         &pin->instances,
-        sizeof(KSPIN_CINSTANCES));
+        sizeof(KSPIN_CINSTANCES),
+        NULL);
 
     if( result != paNoError )
         goto error;
 
+    /* If WaveRT, check if pin supports notification mode */
+    if (parentFilter->devInfo.streamingType == Type_kWaveRT)
+    {
+        BOOL bSupportsNotification = FALSE;
+        if (PinQueryNotificationSupport(pin, &bSupportsNotification) == paNoError)
+        {
+            pin->pinKsSubType = bSupportsNotification ? SubType_kNotification : SubType_kPolled;
+        }
+    }
+
+    /* Query pin name (which means we need to traverse to non IRP pin, via physical connection to topology filter pin, through
+    its nodes to the endpoint pin, and get that ones name... phew...) */
+    PA_DEBUG(("PinNew: Finding topology pin...\n"));
+
+    {
+        ULONG topoPinId = GetConnectedPin(pinId, (pin->dataFlow == KSPIN_DATAFLOW_IN), parentFilter, -1, NULL, NULL);
+        const wchar_t kInputName[] = L"Input";
+        const wchar_t kOutputName[] = L"Output";
+
+        if (topoPinId != KSFILTER_NODE)
+        {
+            /* Get physical connection for topo pin */
+            unsigned long cbBytes = 0;
+            PA_DEBUG(("PinNew: Getting physical connection...\n"));
+            result = WdmGetPinPropertySimple(parentFilter->handle,
+                topoPinId,
+                &KSPROPSETID_Pin,
+                KSPROPERTY_PIN_PHYSICALCONNECTION,
+                0,
+                0,
+                &cbBytes
+                );
+
+            if (result != paNoError)
+            {
+                /* No physical connection -> there is no topology filter! So we get the name of the pin! */
+                PA_DEBUG(("PinNew: No physical connection! Getting the pin name\n"));
+                result = WdmGetPinPropertySimple(parentFilter->handle,
+                    topoPinId,
+                    &KSPROPSETID_Pin,
+                    KSPROPERTY_PIN_NAME,
+                    pin->friendlyName,
+                    MAX_PATH,
+                    NULL);
+                if (result != paNoError)
+                {
+                    GUID category = {0};
+
+                    /* Get pin category information */
+                    result = WdmGetPinPropertySimple(parentFilter->handle,
+                        topoPinId,
+                        &KSPROPSETID_Pin,
+                        KSPROPERTY_PIN_CATEGORY,
+                        &category,
+                        sizeof(GUID),
+                        NULL);
+
+                    if (result == paNoError)
+                    {
+                        result = GetNameFromCategory(&category, (pin->dataFlow == KSPIN_DATAFLOW_OUT), pin->friendlyName, MAX_PATH);
+                    }
+                }
+
+                /* Make sure pin gets a name here... */
+                if (wcslen(pin->friendlyName) == 0)
+                {
+                    wcscpy(pin->friendlyName, (pin->dataFlow == KSPIN_DATAFLOW_IN) ? kOutputName : kInputName);
+#ifdef UNICODE
+                    PA_DEBUG(("PinNew: Setting pin friendly name to '%s'\n", pin->friendlyName));
+#else
+                    PA_DEBUG(("PinNew: Setting pin friendly name to '%S'\n", pin->friendlyName));
+#endif
+                }
+
+                /* This is then == the endpoint pin */
+                pin->endpointPinId = (pin->dataFlow == KSPIN_DATAFLOW_IN) ? pinId : topoPinId;
+            }
+            else
+            {
+                KSPIN_PHYSICALCONNECTION* pc = (KSPIN_PHYSICALCONNECTION*)PaUtil_AllocateMemory(cbBytes + 2);
+                PA_DEBUG(("PinNew: Physical connection found!\n"));
+                if (pc == NULL)
+                {
+                    result = paInsufficientMemory;
+                    goto error;
+                }
+                result = WdmGetPinPropertySimple(parentFilter->handle,
+                    topoPinId,
+                    &KSPROPSETID_Pin,
+                    KSPROPERTY_PIN_PHYSICALCONNECTION,
+                    pc,
+                    cbBytes,
+                    NULL
+                    );
+                if (result == paNoError)
+                {
+                    wchar_t symbLinkName[MAX_PATH];
+                    wcsncpy(symbLinkName, pc->SymbolicLinkName, MAX_PATH);
+                    if (symbLinkName[1] == TEXT('?'))
+                    {
+                        symbLinkName[1] = TEXT('\\');
+                    }
+
+                    if (pin->parentFilter->topologyFilter == NULL)
+                    {
+                        PA_DEBUG(("PinNew: Creating topology filter '%S'\n", symbLinkName));
+
+                        pin->parentFilter->topologyFilter = FilterNew(Type_kNotUsed, 0, symbLinkName, L"", &result);
+                        if (pin->parentFilter->topologyFilter == NULL)
+                        {
+                            PA_DEBUG(("PinNew: Failed creating topology filter\n"));
+                            result = paUnanticipatedHostError;
+                            PaWinWDM_SetLastErrorInfo(result, "Failed to create topology filter '%S'", symbLinkName);
+                            goto error;
+                        }
+
+                        /* Copy info so we have it in device info */
+                        wcsncpy(pin->parentFilter->devInfo.topologyPath, symbLinkName, MAX_PATH);
+                    }
+                    else
+                    {
+                        /* Must be the same */
+                        assert(wcscmp(symbLinkName, pin->parentFilter->topologyFilter->devInfo.filterPath) == 0);
+                    }
+
+                    PA_DEBUG(("PinNew: Opening topology filter..."));
+
+                    result = FilterUse(pin->parentFilter->topologyFilter);
+                    if (result == paNoError)
+                    {
+                        unsigned long endpointPinId;
+
+                        if (pin->dataFlow == KSPIN_DATAFLOW_IN)
+                        {
+                            /* The "endpointPinId" is what WASAPI looks at for pin names */
+                            GUID category = {0};
+
+                            PA_DEBUG(("PinNew: Checking for output endpoint pin id...\n"));
+
+                            endpointPinId = GetConnectedPin(pc->Pin, TRUE, pin->parentFilter->topologyFilter, -1, NULL, NULL);
+
+                            if (endpointPinId == KSFILTER_NODE)
+                            {
+                                result = paUnanticipatedHostError;
+                                PaWinWDM_SetLastErrorInfo(result, "Failed to get endpoint pin ID on topology filter!");
+                                goto error;
+                            }
+
+                            PA_DEBUG(("PinNew: Found endpoint pin id %u\n", endpointPinId));
+
+                            /* Get pin category information */
+                            result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
+                                endpointPinId,
+                                &KSPROPSETID_Pin,
+                                KSPROPERTY_PIN_CATEGORY,
+                                &category,
+                                sizeof(GUID),
+                                NULL);
+
+                            if (result == paNoError)
+                            {
+#if !PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES
+                                wchar_t pinName[MAX_PATH];
+
+                                PA_DEBUG(("PinNew: Getting pin name property..."));
+
+                                /* Ok, try pin name also, and favor that if available */
+                                result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
+                                    endpointPinId,
+                                    &KSPROPSETID_Pin,
+                                    KSPROPERTY_PIN_NAME,
+                                    pinName,
+                                    MAX_PATH,
+                                    NULL);
+
+                                if (result == paNoError && wcslen(pinName)>0)
+                                {
+                                    wcsncpy(pin->friendlyName, pinName, MAX_PATH);
+                                }
+                                else
+#endif
+                                {
+                                    result = GetNameFromCategory(&category, (pin->dataFlow == KSPIN_DATAFLOW_OUT), pin->friendlyName, MAX_PATH);
+                                }
+
+                                if (wcslen(pin->friendlyName) == 0)
+                                {
+                                    wcscpy(pin->friendlyName, L"Output");
+                                }
+#ifdef UNICODE
+                                PA_DEBUG(("PinNew: Pin name '%s'\n", pin->friendlyName));
+#else
+                                PA_DEBUG(("PinNew: Pin name '%S'\n", pin->friendlyName));
+#endif                                
+                            }
+
+                            /* Set endpoint pin ID (this is the topology INPUT pin, since portmixer will always traverse the
+                            filter in audio streaming direction, see http://msdn.microsoft.com/en-us/library/windows/hardware/ff536331(v=vs.85).aspx
+                            for more information)
+                            */
+                            pin->endpointPinId = pc->Pin;
+                        }
+                        else
+                        {
+                            unsigned muxCount = 0;
+                            int muxPos = 0;
+                            /* Max 64 multiplexer inputs... sanity check :) */
+                            for (i = 0; i < 64; ++i)
+                            {
+                                ULONG muxNodeIdTest = (unsigned)-1;
+                                PA_DEBUG(("PinNew: Checking for input endpoint pin id (%d)...\n", i));
+
+                                endpointPinId = GetConnectedPin(pc->Pin,
+                                    FALSE,
+                                    pin->parentFilter->topologyFilter,
+                                    (int)i,
+                                    NULL,
+                                    &muxNodeIdTest);
+
+
+                                if (endpointPinId == KSFILTER_NODE)
+                                {
+                                    /* We're done */
+                                    PA_DEBUG(("PinNew: Done with inputs.\n", endpointPinId));
+                                    break;
+                                }
+                                else
+                                {
+                                    /* The "endpointPinId" is what WASAPI looks at for pin names */
+                                    GUID category = {0};
+
+                                    PA_DEBUG(("PinNew: Found endpoint pin id %u\n", endpointPinId));
+
+                                    /* Get pin category information */
+                                    result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
+                                        endpointPinId,
+                                        &KSPROPSETID_Pin,
+                                        KSPROPERTY_PIN_CATEGORY,
+                                        &category,
+                                        sizeof(GUID),
+                                        NULL);
+
+                                    if (result == paNoError)
+                                    {
+                                        if (muxNodeIdTest == (unsigned)-1)
+                                        {
+                                            /* Ok, try pin name, and favor that if available */
+                                            result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
+                                                endpointPinId,
+                                                &KSPROPSETID_Pin,
+                                                KSPROPERTY_PIN_NAME,
+                                                pin->friendlyName,
+                                                MAX_PATH,
+                                                NULL);
+
+                                            if (result != paNoError)
+                                            {
+                                                result = GetNameFromCategory(&category, TRUE, pin->friendlyName, MAX_PATH);
+                                            }
+                                            break;
+                                        }
+                                        else
+                                        {
+                                            result = GetNameFromCategory(&category, TRUE, NULL, 0);
+
+                                            if (result == paNoError)
+                                            {
+                                                ++muxCount;
+                                            }
+                                        }
+                                    }
+                                    else
+                                    {
+                                        PA_DEBUG(("PinNew: Failed to get pin category"));
+                                    }
+                                }
+                            }
+
+                            if (muxCount == 0)
+                            {
+                                pin->endpointPinId = endpointPinId;
+                                /* Make sure we get a name for the pin */
+                                if (wcslen(pin->friendlyName) == 0)
+                                {
+                                    wcscpy(pin->friendlyName, kInputName);
+                                }
+#ifdef UNICODE
+                                PA_DEBUG(("PinNew: Input friendly name '%s'\n", pin->friendlyName));
+#else
+                                PA_DEBUG(("PinNew: Input friendly name '%S'\n", pin->friendlyName));
+#endif
+                            }
+                            else // muxCount > 0
+                            {
+                                PA_DEBUG(("PinNew: Setting up %u inputs\n", muxCount));
+
+                                /* Now we redo the operation once known how many multiplexer positions there are */
+                                pin->inputs = (PaWinWdmMuxedInput**)PaUtil_AllocateMemory(muxCount * sizeof(PaWinWdmMuxedInput*));
+                                if (pin->inputs == NULL)
+                                {
+                                    FilterRelease(pin->parentFilter->topologyFilter);
+                                    result = paInsufficientMemory;
+                                    goto error;
+                                }
+                                pin->inputCount = muxCount;
+
+                                for (i = 0; i < muxCount; ++muxPos)
+                                {
+                                    PA_DEBUG(("PinNew: Setting up input %u...\n", i));
+
+                                    if (pin->inputs[i] == NULL)
+                                    {
+                                        pin->inputs[i] = (PaWinWdmMuxedInput*)PaUtil_AllocateMemory(sizeof(PaWinWdmMuxedInput));
+                                        if (pin->inputs[i] == NULL)
+                                        {
+                                            FilterRelease(pin->parentFilter->topologyFilter);
+                                            result = paInsufficientMemory;
+                                            goto error;
+                                        }
+                                    }
+
+                                    endpointPinId = GetConnectedPin(pc->Pin,
+                                        FALSE,
+                                        pin->parentFilter->topologyFilter,
+                                        muxPos,
+                                        &pin->inputs[i]->muxPinId, 
+                                        &pin->inputs[i]->muxNodeId);
+
+                                    if (endpointPinId != KSFILTER_NODE)
+                                    {
+                                        /* The "endpointPinId" is what WASAPI looks at for pin names */
+                                        GUID category = {0};
+
+                                        /* Set input endpoint ID */
+                                        pin->inputs[i]->endpointPinId = endpointPinId;
+
+                                        /* Get pin category information */
+                                        result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
+                                            endpointPinId,
+                                            &KSPROPSETID_Pin,
+                                            KSPROPERTY_PIN_CATEGORY,
+                                            &category,
+                                            sizeof(GUID),
+                                            NULL);
+
+                                        if (result == paNoError)
+                                        {
+                                            /* Try pin name first, and if that is not defined, use category instead */
+                                            result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
+                                                endpointPinId,
+                                                &KSPROPSETID_Pin,
+                                                KSPROPERTY_PIN_NAME,
+                                                pin->inputs[i]->friendlyName,
+                                                MAX_PATH,
+                                                NULL);
+
+                                            if (result != paNoError)
+                                            {
+                                                result = GetNameFromCategory(&category, TRUE, pin->inputs[i]->friendlyName, MAX_PATH);
+                                                if (result != paNoError)
+                                                {
+                                                    /* Only specify name, let name hash in ScanDeviceInfos fix postfix enumerators */
+                                                    wcscpy(pin->inputs[i]->friendlyName, kInputName);
+                                                }
+                                            }
+#ifdef UNICODE
+                                            PA_DEBUG(("PinNew: Input (%u) friendly name '%s'\n", i, pin->inputs[i]->friendlyName));
+#else
+                                            PA_DEBUG(("PinNew: Input (%u) friendly name '%S'\n", i, pin->inputs[i]->friendlyName));
+#endif
+                                            ++i;
+                                        }
+                                    }
+                                    else
+                                    {
+                                        /* Should never come here! */
+                                        assert(FALSE);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                PaUtil_FreeMemory(pc);
+            }
+        }
+        else
+        {
+            PA_DEBUG(("PinNew: No topology pin id found. Bad...\n"));
+            /* No TOPO pin id ??? This is bad. Ok, so we just say it is an input or output... */
+            wcscpy(pin->friendlyName, (pin->dataFlow == KSPIN_DATAFLOW_IN) ? kOutputName : kInputName);
+        }
+    }
+
+    /* Release topology filter if it has been used */
+    if (pin->parentFilter->topologyFilter && pin->parentFilter->topologyFilter->handle != NULL)
+    {
+        PA_DEBUG(("PinNew: Releasing topology filter...\n"));
+        FilterRelease(pin->parentFilter->topologyFilter);
+    }
+
     /* Success */
     *error = paNoError;
     PA_DEBUG(("Pin created successfully\n"));
@@ -899,12 +1953,19 @@ static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, Pa
     return pin;
 
 error:
+    PA_DEBUG(("PinNew: Error %d\n", result));
     /*
     Error cleanup
     */
+
     PaUtil_FreeMemory( item );
     if( pin )
     {
+        if (pin->parentFilter->topologyFilter && pin->parentFilter->topologyFilter->handle != NULL)
+        {
+            FilterRelease(pin->parentFilter->topologyFilter);
+        }
+
         PaUtil_FreeMemory( pin->pinConnect );
         PaUtil_FreeMemory( pin->dataRangesItem );
         PaUtil_FreeMemory( pin );
@@ -919,6 +1980,7 @@ Safely free all resources associated with the pin
 */
 static void PinFree(PaWinWdmPin* pin)
 {
+    unsigned i;
     PA_LOGE_;
     if( pin )
     {
@@ -931,6 +1993,14 @@ static void PinFree(PaWinWdmPin* pin)
         {
             PaUtil_FreeMemory( pin->dataRangesItem );
         }
+        if( pin->inputs )
+        {
+            for (i = 0; i < pin->inputCount; ++i)
+            {
+                PaUtil_FreeMemory( pin->inputs[i] );
+            }
+            PaUtil_FreeMemory( pin->inputs );
+        }
         PaUtil_FreeMemory( pin );
     }
     PA_LOGL_;
@@ -964,22 +2034,21 @@ Set the state of this (instantiated) pin
 */
 static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state)
 {
-    PaError result;
+    PaError result = paNoError;
+    KSPROPERTY prop;
 
     PA_LOGE_;
+    prop.Set = KSPROPSETID_Connection;
+    prop.Id  = KSPROPERTY_CONNECTION_STATE;
+    prop.Flags = KSPROPERTY_TYPE_SET;
+
     if( pin == NULL )
         return paInternalError;
     if( pin->handle == NULL )
         return paInternalError;
 
-    result = WdmSetPropertySimple(
-        pin->handle,
-        &KSPROPSETID_Connection,
-        KSPROPERTY_CONNECTION_STATE,
-        &state,
-        sizeof(state),
-        NULL,
-        0);
+    result = WdmSyncIoctl(pin->handle, IOCTL_KS_PROPERTY, &prop, sizeof(KSPROPERTY), &state, sizeof(KSSTATE), NULL);
+
     PA_LOGL_;
     return result;
 }
@@ -1007,41 +2076,52 @@ static PaError PinInstantiate(PaWinWdmPin* pin)
         &pin->handle
         );
 
-    PA_DEBUG(("Pin create result = %x\n",createResult));
+    PA_DEBUG(("Pin create result = 0x%08x\n",createResult));
     if( createResult != ERROR_SUCCESS )
     {
         FilterRelease(pin->parentFilter);
         pin->handle = NULL;
-        return paInvalidDevice;
+        switch (createResult)
+        {
+        case ERROR_INVALID_PARAMETER:
+            /* First case when pin actually don't support the format */
+            return paSampleFormatNotSupported;
+        case ERROR_BAD_COMMAND:
+            /* Case when pin is occupied (by another application) */
+            return paDeviceUnavailable;
+        default:
+            /* All other cases */
+            return paInvalidDevice;
+        }
     }
 
-    result = WdmGetPropertySimple(
-        pin->handle,
-        &KSPROPSETID_Connection,
-        KSPROPERTY_CONNECTION_ALLOCATORFRAMING,
-        &ksaf,
-        sizeof(ksaf),
-        NULL,
-        0);
-
-    if( result != paNoError )
+    if (pin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
     {
+        /* Framing size query only valid for WaveCyclic devices */
         result = WdmGetPropertySimple(
             pin->handle,
             &KSPROPSETID_Connection,
-            KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX,
-            &ksafex,
-            sizeof(ksafex),
-            NULL,
-            0);
-        if( result == paNoError )
+            KSPROPERTY_CONNECTION_ALLOCATORFRAMING,
+            &ksaf,
+            sizeof(ksaf));
+
+        if( result != paNoError )
         {
-            pin->frameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize;
+            result = WdmGetPropertySimple(
+                pin->handle,
+                &KSPROPSETID_Connection,
+                KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX,
+                &ksafex,
+                sizeof(ksafex));
+            if( result == paNoError )
+            {
+                pin->frameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize;
+            }
+        }
+        else
+        {
+            pin->frameSize = ksaf.FrameSize;
         }
-    }
-    else
-    {
-        pin->frameSize = ksaf.FrameSize;
     }
 
     PA_LOGL_;
@@ -1049,30 +2129,6 @@ static PaError PinInstantiate(PaWinWdmPin* pin)
     return paNoError;
 }
 
-/* NOT USED
-static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state)
-{
-    PaError result;
-
-    if( state == NULL )
-        return paInternalError;
-    if( pin == NULL )
-        return paInternalError;
-    if( pin->handle == NULL )
-        return paInternalError;
-
-    result = WdmGetPropertySimple(
-        pin->handle,
-        KSPROPSETID_Connection,
-        KSPROPERTY_CONNECTION_STATE,
-        state,
-        sizeof(KSSTATE),
-        NULL,
-        0);
-
-    return result;
-}
-*/
 static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format)
 {
     unsigned long size;
@@ -1114,507 +2170,848 @@ static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format
     unsigned long count;
     GUID guid = DYNAMIC_GUID( DEFINE_WAVEFORMATEX_GUID(format->wFormatTag) );
     PaError result = paInvalidDevice;
+    const WAVEFORMATEXTENSIBLE* pFormatExt = (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) ? (const WAVEFORMATEXTENSIBLE*)format : 0;
 
     PA_LOGE_;
 
-    if( format->wFormatTag == WAVE_FORMAT_EXTENSIBLE )
+    if( pFormatExt != 0 )
     {
-        guid = ((WAVEFORMATEXTENSIBLE*)format)->SubFormat;
+        guid = pFormatExt->SubFormat;
     }
     dataRange = (KSDATARANGE_AUDIO*)pin->dataRanges;
-    for(count = 0; count<pin->dataRangesItem->Count; count++)
+    for(count = 0;
+        count<pin->dataRangesItem->Count;
+        count++, 
+        dataRange = (KSDATARANGE_AUDIO*)( ((char*)dataRange) + dataRange->DataRange.FormatSize)) /* Need to update dataRange here, due to 'continue' !! */
     {
-        if(( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_AUDIO,sizeof(GUID)) ) ||
-           ( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_WILDCARD,sizeof(GUID)) ))
+        /* Check major format*/
+        if (!(IsEqualGUID(&(dataRange->DataRange.MajorFormat), &KSDATAFORMAT_TYPE_AUDIO) ||
+            IsEqualGUID(&(dataRange->DataRange.MajorFormat), &KSDATAFORMAT_TYPE_WILDCARD)))
         {
-            /* This is an audio or wildcard datarange... */
-            if(( !memcmp(&(dataRange->DataRange.SubFormat),&KSDATAFORMAT_SUBTYPE_WILDCARD,sizeof(GUID)) ) ||
-                ( !memcmp(&(dataRange->DataRange.SubFormat),&guid,sizeof(GUID)) ))
-            {
-                if(( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WILDCARD,sizeof(GUID)) ) ||
-                  ( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,sizeof(GUID) )))
-                {
+            continue;
+        }
 
-                    PA_DEBUG(("Pin:%x, DataRange:%d\n",(void*)pin,count));
-                    PA_DEBUG(("\tFormatSize:%d, SampleSize:%d\n",dataRange->DataRange.FormatSize,dataRange->DataRange.SampleSize));
-                    PA_DEBUG(("\tMaxChannels:%d\n",dataRange->MaximumChannels));
-                    PA_DEBUG(("\tBits:%d-%d\n",dataRange->MinimumBitsPerSample,dataRange->MaximumBitsPerSample));
-                    PA_DEBUG(("\tSampleRate:%d-%d\n",dataRange->MinimumSampleFrequency,dataRange->MaximumSampleFrequency));
+        /* This is an audio or wildcard datarange... */
+        if (! (IsEqualGUID(&(dataRange->DataRange.SubFormat), &KSDATAFORMAT_SUBTYPE_WILDCARD) ||
+            IsEqualGUID(&(dataRange->DataRange.SubFormat), &KSDATAFORMAT_SUBTYPE_PCM) ||
+            IsEqualGUID(&(dataRange->DataRange.SubFormat), &guid) ))
+        {
+            continue;
+        }
 
-                    if( dataRange->MaximumChannels < format->nChannels )
-                    {
-                        result = paInvalidChannelCount;
-                        continue;
-                    }
-                    if( dataRange->MinimumBitsPerSample > format->wBitsPerSample )
-                    {
-                        result = paSampleFormatNotSupported;
-                        continue;
-                    }
-                    if( dataRange->MaximumBitsPerSample < format->wBitsPerSample )
-                    {
-                        result = paSampleFormatNotSupported;
-                        continue;
-                    }
-                    if( dataRange->MinimumSampleFrequency > format->nSamplesPerSec )
-                    {
-                        result = paInvalidSampleRate;
-                        continue;
-                    }
-                    if( dataRange->MaximumSampleFrequency < format->nSamplesPerSec )
-                    {
-                        result = paInvalidSampleRate;
-                        continue;
-                    }
-                    /* Success! */
-                    PA_LOGL_;
-                    return paNoError;
-                }
+        /* Check specifier... */
+        if (! (IsEqualGUID(&(dataRange->DataRange.Specifier), &KSDATAFORMAT_SPECIFIER_WILDCARD) ||
+            IsEqualGUID(&(dataRange->DataRange.Specifier), &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX)) )
+        {
+            continue;
+        }
+
+        PA_DEBUG(("Pin:%x, DataRange:%d\n",(void*)pin,count));
+        PA_DEBUG(("\tFormatSize:%d, SampleSize:%d\n",dataRange->DataRange.FormatSize,dataRange->DataRange.SampleSize));
+        PA_DEBUG(("\tMaxChannels:%d\n",dataRange->MaximumChannels));
+        PA_DEBUG(("\tBits:%d-%d\n",dataRange->MinimumBitsPerSample,dataRange->MaximumBitsPerSample));
+        PA_DEBUG(("\tSampleRate:%d-%d\n",dataRange->MinimumSampleFrequency,dataRange->MaximumSampleFrequency));
+
+        if( dataRange->MaximumChannels != (ULONG)-1 && 
+            dataRange->MaximumChannels < format->nChannels )
+        {
+            result = paInvalidChannelCount;
+            continue;
+        }
+
+        if (pFormatExt != 0)
+        {
+            if ( dataRange->MinimumBitsPerSample > pFormatExt->Samples.wValidBitsPerSample )
+            {
+                result = paSampleFormatNotSupported;
+                continue;
+            }
+            if ( dataRange->MaximumBitsPerSample < pFormatExt->Samples.wValidBitsPerSample )
+            {
+                result = paSampleFormatNotSupported;
+                continue;
             }
         }
-        dataRange = (KSDATARANGE_AUDIO*)( ((char*)dataRange) + dataRange->DataRange.FormatSize);
+        else
+        {
+            if( dataRange->MinimumBitsPerSample > format->wBitsPerSample )
+            {
+                result = paSampleFormatNotSupported;
+                continue;
+            }
+
+            if( dataRange->MaximumBitsPerSample < format->wBitsPerSample )
+            {
+                result = paSampleFormatNotSupported;
+                continue;
+            }
+        }
+
+        if( dataRange->MinimumSampleFrequency > format->nSamplesPerSec )
+        {
+            result = paInvalidSampleRate;
+            continue;
+        }
+
+        if( dataRange->MaximumSampleFrequency < format->nSamplesPerSec )
+        {
+            result = paInvalidSampleRate;
+            continue;
+        }
+
+        /* Success! */
+        result = paNoError;
+        break;
     }
 
     PA_LOGL_;
-
     return result;
 }
 
-/**
- * Create a new filter object
- */
-static PaWinWdmFilter* FilterNew(TCHAR* filterName, TCHAR* friendlyName, PaError* error)
+static PaError PinQueryNotificationSupport(PaWinWdmPin* pPin, BOOL* pbResult)
 {
-    PaWinWdmFilter* filter;
-    PaError result;
-    int pinId;
-    int valid;
+    PaError result = paNoError;
+    KSPROPERTY propIn;
 
+    propIn.Set = KSPROPSETID_RtAudio;
+    propIn.Id = 8; /* = KSPROPERTY_RTAUDIO_QUERY_NOTIFICATION_SUPPORT */
+    propIn.Flags = KSPROPERTY_TYPE_GET;
 
-    /* Allocate the new filter object */
-    filter = (PaWinWdmFilter*)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter) );
-    if( !filter )
+    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
+        &propIn,
+        sizeof(KSPROPERTY),
+        pbResult,
+        sizeof(BOOL),
+        NULL);
+
+    if (result != paNoError) 
     {
-        result = paInsufficientMemory;
-        goto error;
+        PA_DEBUG(("Failed PinQueryNotificationSupport\n"));
     }
 
-    /* Zero the filter object - done by AllocateMemory */
-    /* memset( (void*)filter, 0, sizeof(PaWinWdmFilter) ); */
-
-    /* Copy the filter name */
-    _tcsncpy(filter->filterName, filterName, MAX_PATH);
+    return result;
 
-    /* Copy the friendly name */
-    _tcsncpy(filter->friendlyName, friendlyName, MAX_PATH);
+}
 
-    /* Open the filter handle */
-    result = FilterUse(filter);
-    if( result != paNoError )
+static PaError PinGetBufferWithNotification(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier)
+{
+    PaError result = paNoError;
+    KSRTAUDIO_BUFFER_PROPERTY_WITH_NOTIFICATION propIn;
+    KSRTAUDIO_BUFFER propOut;
+
+    propIn.BaseAddress = 0;
+    propIn.NotificationCount = 2;
+    propIn.RequestedBufferSize = *pRequestedBufSize;
+    propIn.Property.Set = KSPROPSETID_RtAudio;
+    propIn.Property.Id = KSPROPERTY_RTAUDIO_BUFFER_WITH_NOTIFICATION;
+    propIn.Property.Flags = KSPROPERTY_TYPE_GET;
+
+    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
+        &propIn,
+        sizeof(KSRTAUDIO_BUFFER_PROPERTY_WITH_NOTIFICATION),
+        &propOut,
+        sizeof(KSRTAUDIO_BUFFER),
+        NULL);
+
+    if (result == paNoError) 
     {
-        goto error;
+        *pBuffer = propOut.BufferAddress;
+        *pRequestedBufSize = propOut.ActualBufferSize;
+        *pbCallMemBarrier = propOut.CallMemoryBarrier;
+    }
+    else 
+    {
+        PA_DEBUG(("Failed to get buffer with notification\n"));
     }
 
-    /* Get pin count */
-    result = WdmGetPinPropertySimple
-        (
-        filter->handle,
-        0,
-        &KSPROPSETID_Pin,
-        KSPROPERTY_PIN_CTYPES,
-        &filter->pinCount,
-        sizeof(filter->pinCount)
-        );
+    return result;
+}
 
-    if( result != paNoError)
+static PaError PinGetBufferWithoutNotification(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier)
+{
+    PaError result = paNoError;
+    KSRTAUDIO_BUFFER_PROPERTY propIn;
+    KSRTAUDIO_BUFFER propOut;
+
+    propIn.BaseAddress = NULL;
+    propIn.RequestedBufferSize = *pRequestedBufSize;
+    propIn.Property.Set = KSPROPSETID_RtAudio;
+    propIn.Property.Id = KSPROPERTY_RTAUDIO_BUFFER;
+    propIn.Property.Flags = KSPROPERTY_TYPE_GET;
+
+    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
+        &propIn,
+        sizeof(KSRTAUDIO_BUFFER_PROPERTY),
+        &propOut,
+        sizeof(KSRTAUDIO_BUFFER),
+        NULL);
+
+    if (result == paNoError)
     {
-        goto error;
+        *pBuffer = propOut.BufferAddress;
+        *pRequestedBufSize = propOut.ActualBufferSize;
+        *pbCallMemBarrier = propOut.CallMemoryBarrier;
     }
-
-    /* Allocate pointer array to hold the pins */
-    filter->pins = (PaWinWdmPin**)PaUtil_AllocateMemory( sizeof(PaWinWdmPin*) * filter->pinCount );
-    if( !filter->pins )
+    else 
     {
-        result = paInsufficientMemory;
-        goto error;
+        PA_DEBUG(("Failed to get buffer without notification\n"));
     }
 
-    /* Create all the pins we can */
-    filter->maxInputChannels = 0;
-    filter->maxOutputChannels = 0;
-    filter->bestSampleRate = 0;
+    return result;
+}
 
-    valid = 0;
-    for(pinId = 0; pinId < filter->pinCount; pinId++)
+/* greatest common divisor - PGCD in French */
+static unsigned long PaWinWDMGCD( unsigned long a, unsigned long b )
+{
+    return (b==0) ? a : PaWinWDMGCD( b, a%b);
+}
+
+
+/* This function will handle getting the cyclic buffer from a WaveRT driver. Certain WaveRT drivers needs to have
+requested buffer size on multiples of 128 bytes:
+
+*/
+static PaError PinGetBuffer(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier)
+{
+    PaError result = paNoError;
+
+    while (1)
     {
-        /* Create the pin with this Id */
-        PaWinWdmPin* newPin;
-        newPin = PinNew(filter, pinId, &result);
-        if( result == paInsufficientMemory )
-            goto error;
-        if( newPin != NULL )
+        if (pPin->pinKsSubType != SubType_kPolled)
         {
-            filter->pins[pinId] = newPin;
-            valid = 1;
-
-            /* Get the max output channel count */
-            if(( newPin->dataFlow == KSPIN_DATAFLOW_IN ) &&
-                (( newPin->communication == KSPIN_COMMUNICATION_SINK) ||
-                 ( newPin->communication == KSPIN_COMMUNICATION_BOTH)))
+            /* In case of unknown (or notification), we try both modes */
+            result = PinGetBufferWithNotification(pPin, pBuffer, pRequestedBufSize, pbCallMemBarrier);
+            if (result == paNoError)
             {
-                if(newPin->maxChannels > filter->maxOutputChannels)
-                    filter->maxOutputChannels = newPin->maxChannels;
-                filter->formats |= newPin->formats;
-            }
-            /* Get the max input channel count */
-            if(( newPin->dataFlow == KSPIN_DATAFLOW_OUT ) &&
-                (( newPin->communication == KSPIN_COMMUNICATION_SINK) ||
-                 ( newPin->communication == KSPIN_COMMUNICATION_BOTH)))
-            {
-                if(newPin->maxChannels > filter->maxInputChannels)
-                    filter->maxInputChannels = newPin->maxChannels;
-                filter->formats |= newPin->formats;
+                PA_DEBUG(("PinGetBuffer: SubType_kNotification\n"));
+                pPin->pinKsSubType = SubType_kNotification;
+                break;
             }
+        }
 
-            if(newPin->bestSampleRate > filter->bestSampleRate)
-            {
-                filter->bestSampleRate = newPin->bestSampleRate;
-            }
+        result = PinGetBufferWithoutNotification(pPin, pBuffer, pRequestedBufSize, pbCallMemBarrier);
+        if (result == paNoError)
+        {
+            PA_DEBUG(("PinGetBuffer: SubType_kPolled\n"));
+            pPin->pinKsSubType = SubType_kPolled;
+            break;
         }
-    }
 
-    if(( filter->maxInputChannels == 0) && ( filter->maxOutputChannels == 0))
-    {
-        /* No input or output... not valid */
-        valid = 0;
-    }
+        /* Check if requested size is on a 128 byte boundary */
+        if (((*pRequestedBufSize) % 128UL) == 0)
+        {
+            PA_DEBUG(("Buffer size on 128 byte boundary, still fails :(\n"));
+            /* Ok, can't do much more */
+            break;
+        }
+        else
+        {
+            /* Compute LCM so we know which sizes are on a 128 byte boundary */
+            const unsigned gcd = PaWinWDMGCD(128UL, pPin->ksDataFormatWfx->WaveFormatEx.nBlockAlign);
+            const unsigned lcm = (128UL * pPin->ksDataFormatWfx->WaveFormatEx.nBlockAlign) / gcd;
+            DWORD dwOldSize = *pRequestedBufSize;
 
-    if( !valid )
-    {
-        /* No valid pin was found on this filter so we destroy it */
-        result = paDeviceUnavailable;
-        goto error;
+            /* Align size to (next larger) LCM byte boundary, and then we try again. Note that LCM is not necessarily a
+            power of 2. */
+            *pRequestedBufSize = ((*pRequestedBufSize + lcm - 1) / lcm) * lcm;
+
+            PA_DEBUG(("Adjusting buffer size from %u to %u bytes (128 byte boundary, LCM=%u)\n", dwOldSize, *pRequestedBufSize, lcm));
+        }
     }
 
-    /* Close the filter handle for now
-     * It will be opened later when needed */
-    FilterRelease(filter);
+    return result;
+}
 
-    *error = paNoError;
-    return filter;
+static PaError PinRegisterPositionRegister(PaWinWdmPin* pPin) 
+{
+    PaError result = paNoError;
+    KSRTAUDIO_HWREGISTER_PROPERTY propIn;
+    KSRTAUDIO_HWREGISTER propOut;
 
-error:
-    /*
-    Error cleanup
-    */
-    if( filter )
+    PA_LOGE_;
+
+    propIn.BaseAddress = NULL;
+    propIn.Property.Set = KSPROPSETID_RtAudio;
+    propIn.Property.Id = KSPROPERTY_RTAUDIO_POSITIONREGISTER;
+    propIn.Property.Flags = KSPROPERTY_TYPE_GET;
+
+    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
+        &propIn,
+        sizeof(KSRTAUDIO_HWREGISTER_PROPERTY),
+        &propOut,
+        sizeof(KSRTAUDIO_HWREGISTER),
+        NULL);
+
+    if (result == paNoError) 
     {
-        for( pinId = 0; pinId < filter->pinCount; pinId++ )
-            PinFree(filter->pins[pinId]);
-        PaUtil_FreeMemory( filter->pins );
-        if( filter->handle )
-            CloseHandle( filter->handle );
-        PaUtil_FreeMemory( filter );
+        pPin->positionRegister = (ULONG*)propOut.Register;
     }
-    *error = result;
-    return NULL;
+    else
+    {
+        PA_DEBUG(("Failed to register position register\n"));
+    }
+
+    PA_LOGL_;
+
+    return result;
 }
 
-/**
- * Free a previously created filter
- */
-static void FilterFree(PaWinWdmFilter* filter)
+static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle) 
 {
-    int pinId;
-    PA_LOGL_;
-    if( filter )
-    {
-        for( pinId = 0; pinId < filter->pinCount; pinId++ )
-            PinFree(filter->pins[pinId]);
-        PaUtil_FreeMemory( filter->pins );
-        if( filter->handle )
-            CloseHandle( filter->handle );
-        PaUtil_FreeMemory( filter );
-    }
+    PaError result = paNoError;
+    KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY prop;
+
     PA_LOGE_;
+
+    prop.NotificationEvent = handle;
+    prop.Property.Set = KSPROPSETID_RtAudio;
+    prop.Property.Id = KSPROPERTY_RTAUDIO_REGISTER_NOTIFICATION_EVENT;
+    prop.Property.Flags = KSPROPERTY_TYPE_GET;
+
+    result = WdmSyncIoctl(pPin->handle,
+        IOCTL_KS_PROPERTY,
+        &prop,
+        sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
+        &prop,
+        sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
+        NULL);
+
+    if (result != paNoError) {
+        PA_DEBUG(("Failed to register notification handle 0x%08X\n", handle));
+    }
+
+    PA_LOGL_;
+
+    return result;
 }
 
-/**
- * Reopen the filter handle if necessary so it can be used
- **/
-static PaError FilterUse(PaWinWdmFilter* filter)
+static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle) 
 {
-    assert( filter );
+    PaError result = paNoError;
+    KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY prop;
 
     PA_LOGE_;
-    if( filter->handle == NULL )
+
+    if (handle != NULL)
     {
-        /* Open the filter */
-        filter->handle = CreateFile(
-            filter->filterName,
-            GENERIC_READ | GENERIC_WRITE,
-            0,
-            NULL,
-            OPEN_EXISTING,
-            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
+        prop.NotificationEvent = handle;
+        prop.Property.Set = KSPROPSETID_RtAudio;
+        prop.Property.Id = KSPROPERTY_RTAUDIO_UNREGISTER_NOTIFICATION_EVENT;
+        prop.Property.Flags = KSPROPERTY_TYPE_GET;
+
+        result = WdmSyncIoctl(pPin->handle,
+            IOCTL_KS_PROPERTY,
+            &prop,
+            sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
+            &prop,
+            sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
             NULL);
 
-        if( filter->handle == NULL )
-        {
-            return paDeviceUnavailable;
+        if (result != paNoError) {
+            PA_DEBUG(("Failed to unregister notification handle 0x%08X\n", handle));
         }
     }
-    filter->usageCount++;
     PA_LOGL_;
+
+    return result;
+}
+
+static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChipsetDelay, ULONG* pCodecDelay)
+{
+    PaError result = paNoError;
+    KSPROPERTY propIn;
+    KSRTAUDIO_HWLATENCY propOut;
+
+    PA_LOGE_;
+
+    propIn.Set = KSPROPSETID_RtAudio;
+    propIn.Id = KSPROPERTY_RTAUDIO_HWLATENCY;
+    propIn.Flags = KSPROPERTY_TYPE_GET;
+
+    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
+        &propIn,
+        sizeof(KSPROPERTY),
+        &propOut,
+        sizeof(KSRTAUDIO_HWLATENCY),
+        NULL);
+
+    if (result == paNoError)
+    {
+        *pFifoSize = propOut.FifoSize;
+        *pChipsetDelay = propOut.ChipsetDelay;
+        *pCodecDelay = propOut.CodecDelay;
+    }
+    else
+    {
+        PA_DEBUG(("Failed to retrieve hardware FIFO size!\n"));
+    }
+
+    PA_LOGL_;
+
+    return result;
+}
+
+/* This one is used for WaveRT */
+static PaError PinGetAudioPositionDirect(PaWinWdmPin* pPin, ULONG* pPosition)
+{
+    *pPosition = (*pPin->positionRegister);
     return paNoError;
 }
 
-/**
- * Release the filter handle if nobody is using it
- **/
-static void FilterRelease(PaWinWdmFilter* filter)
+/* This one also, but in case the driver hasn't implemented memory mapped access to the position register */
+static PaError PinGetAudioPositionViaIOCTL(PaWinWdmPin* pPin, ULONG* pPosition)
 {
-    assert( filter );
-    assert( filter->usageCount > 0 );
+    PaError result = paNoError;
+    KSPROPERTY propIn;
+    KSAUDIO_POSITION propOut;
 
     PA_LOGE_;
-    filter->usageCount--;
-    if( filter->usageCount == 0 )
+
+    propIn.Set = KSPROPSETID_Audio;
+    propIn.Id = KSPROPERTY_AUDIO_POSITION;
+    propIn.Flags = KSPROPERTY_TYPE_GET;
+
+    result = WdmSyncIoctl(pPin->handle,
+        IOCTL_KS_PROPERTY,
+        &propIn, sizeof(KSPROPERTY),
+        &propOut, sizeof(KSAUDIO_POSITION),
+        NULL);
+
+    if (result == paNoError)
     {
-        if( filter->handle != NULL )
-        {
-            CloseHandle( filter->handle );
-            filter->handle = NULL;
-        }
+        *pPosition = (ULONG)(propOut.PlayOffset);
+    }
+    else
+    {
+        PA_DEBUG(("Failed to get audio position!\n"));
     }
+
     PA_LOGL_;
+
+    return result;
+
 }
 
+/***********************************************************************************************/
+
 /**
- * Create a render (playback) Pin using the supplied format
- **/
-static PaWinWdmPin* FilterCreateRenderPin(PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error)
+* Create a new filter object. 
+*/
+static PaWinWdmFilter* FilterNew( PaWDMKSType type, DWORD devNode, const wchar_t* filterName, const wchar_t* friendlyName, PaError* error )
 {
+    PaWinWdmFilter* filter = 0;
     PaError result;
-    PaWinWdmPin* pin;
-
-    assert( filter );
 
-    pin = FilterFindViableRenderPin(filter,wfex,&result);
-    if(!pin)
+    /* Allocate the new filter object */
+    filter = (PaWinWdmFilter*)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter) );
+    if( !filter )
     {
+        result = paInsufficientMemory;
         goto error;
     }
-    result = PinSetFormat(pin,wfex);
+
+    PA_DEBUG(("FilterNew: Creating filter '%S'\n", friendlyName));
+
+    /* Set type flag */
+    filter->devInfo.streamingType = type;
+
+    /* Store device node */
+    filter->deviceNode = devNode;
+
+    /* Zero the filter object - done by AllocateMemory */
+    /* memset( (void*)filter, 0, sizeof(PaWinWdmFilter) ); */
+
+    /* Copy the filter name */
+    wcsncpy(filter->devInfo.filterPath, filterName, MAX_PATH);
+
+    /* Copy the friendly name */
+    wcsncpy(filter->friendlyName, friendlyName, MAX_PATH);
+
+    PA_DEBUG(("FilterNew: Opening filter...\n", friendlyName));
+
+    /* Open the filter handle */
+    result = FilterUse(filter);
     if( result != paNoError )
     {
         goto error;
     }
-    result = PinInstantiate(pin);
-    if( result != paNoError )
+
+    /* Get pin count */
+    result = WdmGetPinPropertySimple
+        (
+        filter->handle,
+        0,
+        &KSPROPSETID_Pin,
+        KSPROPERTY_PIN_CTYPES,
+        &filter->pinCount,
+        sizeof(filter->pinCount),
+        NULL);
+
+    if( result != paNoError)
     {
         goto error;
     }
 
-    *error = paNoError;
-    return pin;
+    /* Get connections & nodes for filter */
+    result = WdmGetPropertyMulti(
+        filter->handle,
+        &KSPROPSETID_Topology,
+        KSPROPERTY_TOPOLOGY_CONNECTIONS,
+        &filter->connections);
 
-error:
-    *error = result;
-    return NULL;
-}
+    if( result != paNoError)
+    {
+        goto error;
+    }
 
-/**
- * Find a pin that supports the given format
- **/
-static PaWinWdmPin* FilterFindViableRenderPin(PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error)
-{
-    int pinId;
-    PaWinWdmPin*  pin;
-    PaError result = paDeviceUnavailable;
-    *error = paNoError;
+    result = WdmGetPropertyMulti(
+        filter->handle,
+        &KSPROPSETID_Topology,
+        KSPROPERTY_TOPOLOGY_NODES,
+        &filter->nodes);
 
-    assert( filter );
+    if( result != paNoError)
+    {
+        goto error;
+    }
+
+    /* For debugging purposes */
+    DumpConnectionsAndNodes(filter);
 
-    for( pinId = 0; pinId<filter->pinCount; pinId++ )
+    /* Get product GUID (it might not be supported) */
     {
-        pin = filter->pins[pinId];
-        if( pin != NULL )
+        KSCOMPONENTID compId;
+        if (WdmGetPropertySimple(filter->handle, &KSPROPSETID_General, KSPROPERTY_GENERAL_COMPONENTID, &compId, sizeof(KSCOMPONENTID)) == paNoError)
         {
-            if(( pin->dataFlow == KSPIN_DATAFLOW_IN ) &&
-                (( pin->communication == KSPIN_COMMUNICATION_SINK) ||
-                 ( pin->communication == KSPIN_COMMUNICATION_BOTH)))
-            {
-                result = PinIsFormatSupported( pin, wfex );
-                if( result == paNoError )
-                {
-                    return pin;
-                }
-            }
+            filter->devInfo.deviceProductGuid = compId.Product;
         }
     }
 
+    /* This section is not executed for topology filters */
+    if (type != Type_kNotUsed)
+    {
+        /* Initialize the pins */
+        result = FilterInitializePins(filter);
+
+        if( result != paNoError)
+        {
+            goto error;
+        }
+    }
+
+    /* Close the filter handle for now
+    * It will be opened later when needed */
+    FilterRelease(filter);
+
+    *error = paNoError;
+    return filter;
+
+error:
+    PA_DEBUG(("FilterNew: Error %d\n", result));
+    /*
+    Error cleanup
+    */
+    FilterFree(filter);
+
     *error = result;
     return NULL;
 }
 
 /**
- * Check if there is a pin that should playback
- * with the supplied format
- **/
-static PaError FilterCanCreateRenderPin(PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex)
+* Add reference to filter
+*/
+static void FilterAddRef( PaWinWdmFilter* filter )
 {
-    PaWinWdmPin* pin;
-    PaError result;
-
-    assert ( filter );
-
-    pin = FilterFindViableRenderPin(filter,wfex,&result);
-    /* result will be paNoError if pin found
-     * or else an error code indicating what is wrong with the format
-     **/
-    return result;
+    if (filter != 0)
+    {
+        filter->filterRefCount++;
+    }
 }
 
+
 /**
- * Create a capture (record) Pin using the supplied format
- **/
-static PaWinWdmPin* FilterCreateCapturePin(PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error)
+* Initialize the pins of the filter. This is separated from FilterNew because this might fail if there is another
+* process using the pin(s).
+*/
+PaError FilterInitializePins( PaWinWdmFilter* filter )
 {
-    PaError result;
-    PaWinWdmPin* pin;
+    PaError result = paNoError;
+    int pinId;
 
-    assert( filter );
+    if (filter->devInfo.streamingType == Type_kNotUsed)
+        return paNoError;
+
+    if (filter->pins != NULL)
+        return paNoError;   
 
-    pin = FilterFindViableCapturePin(filter,wfex,&result);
-    if(!pin)
+    /* Allocate pointer array to hold the pins */
+    filter->pins = (PaWinWdmPin**)PaUtil_AllocateMemory( sizeof(PaWinWdmPin*) * filter->pinCount );
+    if( !filter->pins )
     {
+        result = paInsufficientMemory;
         goto error;
     }
 
-    result = PinSetFormat(pin,wfex);
-    if( result != paNoError )
+    /* Create all the pins we can */
+    for(pinId = 0; pinId < filter->pinCount; pinId++)
     {
-        goto error;
+        /* Create the pin with this Id */
+        PaWinWdmPin* newPin;
+        newPin = PinNew(filter, pinId, &result);
+        if( result == paInsufficientMemory )
+            goto error;
+        if( newPin != NULL )
+        {
+            filter->pins[pinId] = newPin;
+            ++filter->validPinCount;
+        }
     }
 
-    result = PinInstantiate(pin);
-    if( result != paNoError )
+    if (filter->validPinCount == 0)
     {
+        result = paDeviceUnavailable;
         goto error;
     }
 
-    *error = paNoError;
-    return pin;
+    return paNoError;
 
 error:
-    *error = result;
-    return NULL;
-}
-
-/**
- * Find a capture pin that supports the given format
- **/
-static PaWinWdmPin* FilterFindViableCapturePin(PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex,
-    PaError* error)
-{
-    int pinId;
-    PaWinWdmPin*  pin;
-    PaError result = paDeviceUnavailable;
-    *error = paNoError;
-
-    assert( filter );
 
-    for( pinId = 0; pinId<filter->pinCount; pinId++ )
+    if (filter->pins)
     {
-        pin = filter->pins[pinId];
-        if( pin != NULL )
+        for (pinId = 0; pinId < filter->pinCount; ++pinId)
         {
-            if(( pin->dataFlow == KSPIN_DATAFLOW_OUT ) &&
-                (( pin->communication == KSPIN_COMMUNICATION_SINK) ||
-                 ( pin->communication == KSPIN_COMMUNICATION_BOTH)))
+            if (filter->pins[pinId])
             {
-                result = PinIsFormatSupported( pin, wfex );
-                if( result == paNoError )
-                {
-                    return pin;
-                }
+                PaUtil_FreeMemory(filter->pins[pinId]);
+                filter->pins[pinId] = 0;
             }
         }
+        PaUtil_FreeMemory( filter->pins );
+        filter->pins = 0;
     }
 
-    *error = result;
-    return NULL;
-}
-
-/**
- * Check if there is a pin that should playback
- * with the supplied format
- **/
-static PaError FilterCanCreateCapturePin(PaWinWdmFilter* filter,
-    const WAVEFORMATEX* wfex)
-{
-    PaWinWdmPin* pin;
-    PaError result;
-
-    assert ( filter );
-
-    pin = FilterFindViableCapturePin(filter,wfex,&result);
-    /* result will be paNoError if pin found
-     * or else an error code indicating what is wrong with the format
-     **/
     return result;
 }
 
+
 /**
- * Build the list of available filters
- * Use the SetupDi API to enumerate all devices in the KSCATEGORY_AUDIO which 
- * have a KSCATEGORY_RENDER or KSCATEGORY_CAPTURE alias. For each of these 
- * devices initialise a PaWinWdmFilter structure by calling our NewFilter() 
- * function. We enumerate devices twice, once to count how many there are, 
- * and once to initialize the PaWinWdmFilter structures.
- */
-static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
+* Free a previously created filter
+*/
+static void FilterFree(PaWinWdmFilter* filter)
 {
-    PaError result = paNoError;
-    HDEVINFO handle = NULL;
-    int device;
-    int invalidDevices;
-    int slot;
+    int pinId;
+    PA_LOGL_;
+    if( filter )
+    {
+        if (--filter->filterRefCount > 0)
+        {
+            /* Ok, a stream has a ref count to this filter */
+            return;
+        }
+
+        if (filter->topologyFilter)
+        {
+            FilterFree(filter->topologyFilter);
+            filter->topologyFilter = 0;
+        }
+        if ( filter->pins )
+        {
+            for( pinId = 0; pinId < filter->pinCount; pinId++ )
+                PinFree(filter->pins[pinId]);
+            PaUtil_FreeMemory( filter->pins );
+            filter->pins = 0;
+        }
+        if( filter->connections )
+        {
+            PaUtil_FreeMemory(filter->connections);
+            filter->connections = 0;
+        }
+        if( filter->nodes )
+        {
+            PaUtil_FreeMemory(filter->nodes);
+            filter->nodes = 0;
+        }
+        if( filter->handle )
+            CloseHandle( filter->handle );
+        PaUtil_FreeMemory( filter );
+    }
+    PA_LOGE_;
+}
+
+/**
+* Reopen the filter handle if necessary so it can be used
+**/
+static PaError FilterUse(PaWinWdmFilter* filter)
+{
+    assert( filter );
+
+    PA_LOGE_;
+    if( filter->handle == NULL )
+    {
+        /* Open the filter */
+        filter->handle = CreateFileW(
+            filter->devInfo.filterPath,
+            GENERIC_READ | GENERIC_WRITE,
+            0,
+            NULL,
+            OPEN_EXISTING,
+            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
+            NULL);
+
+        if( filter->handle == NULL )
+        {
+            return paDeviceUnavailable;
+        }
+    }
+    filter->usageCount++;
+    PA_LOGL_;
+    return paNoError;
+}
+
+/**
+* Release the filter handle if nobody is using it
+**/
+static void FilterRelease(PaWinWdmFilter* filter)
+{
+    assert( filter );
+    assert( filter->usageCount > 0 );
+
+    PA_LOGE_;
+    /* Check first topology filter, if used */
+    if (filter->topologyFilter != NULL && filter->topologyFilter->handle != NULL)
+    {
+        FilterRelease(filter->topologyFilter);
+    }
+
+    filter->usageCount--;
+    if( filter->usageCount == 0 )
+    {
+        if( filter->handle != NULL )
+        {
+            CloseHandle( filter->handle );
+            filter->handle = NULL;
+        }
+    }
+    PA_LOGL_;
+}
+
+/**
+* Create a render or playback pin using the supplied format
+**/
+static PaWinWdmPin* FilterCreatePin(PaWinWdmFilter* filter,
+                                    int pinId,
+                                    const WAVEFORMATEX* wfex,
+                                    PaError* error)
+{
+    PaError result = paNoError;
+    PaWinWdmPin* pin = NULL;
+    assert( filter );
+    assert( pinId < filter->pinCount );
+    pin = filter->pins[pinId];
+    assert( pin );
+    result = PinSetFormat(pin,wfex);
+    if( result == paNoError )
+    {
+        result = PinInstantiate(pin);
+    }
+    *error = result;
+    return result == paNoError ? pin : 0;
+}
+
+static const wchar_t kUsbPrefix[] = L"\\\\?\\USB";
+
+static BOOL IsUSBDevice(const wchar_t* devicePath)
+{
+    /* Alex Lessard pointed out that different devices might present the device path with
+       lower case letters. */
+    return (_wcsnicmp(devicePath, kUsbPrefix, sizeof(kUsbPrefix)/sizeof(kUsbPrefix[0]) ) == 0);
+}
+
+/* This should make it more language tolerant, I hope... */
+static const wchar_t kUsbNamePrefix[] = L"USB Audio";
+
+static BOOL IsNameUSBAudioDevice(const wchar_t* friendlyName)
+{
+    return (_wcsnicmp(friendlyName, kUsbNamePrefix, sizeof(kUsbNamePrefix)/sizeof(kUsbNamePrefix[0])) == 0);
+}
+
+typedef enum _tag_EAlias
+{
+    Alias_kRender   = (1<<0),
+    Alias_kCapture  = (1<<1),
+    Alias_kRealtime = (1<<2),
+} EAlias;
+
+/* Trim whitespace from string */
+static void TrimString(wchar_t* str, size_t length)
+{
+    wchar_t* s = str;
+    wchar_t* e = 0;
+
+    /* Find start of string */
+    while (iswspace(*s)) ++s;
+    e=s+min(length,wcslen(s))-1;
+
+    /* Find end of string */
+    while(e>s && iswspace(*e)) --e;
+    ++e;
+
+    length = e - s;
+    memmove(str, s, length * sizeof(wchar_t));
+    str[length] = 0;
+}
+
+/**
+* Build the list of available filters
+* Use the SetupDi API to enumerate all devices in the KSCATEGORY_AUDIO which 
+* have a KSCATEGORY_RENDER or KSCATEGORY_CAPTURE alias. For each of these 
+* devices initialise a PaWinWdmFilter structure by calling our NewFilter() 
+* function. We enumerate devices twice, once to count how many there are, 
+* and once to initialize the PaWinWdmFilter structures.
+*
+* Vista and later: Also check KSCATEGORY_REALTIME for WaveRT devices.
+*/
+//PaError BuildFilterList( PaWinWdmHostApiRepresentation* wdmHostApi, int* noOfPaDevices )
+PaWinWdmFilter** BuildFilterList( int* pFilterCount, int* pNoOfPaDevices, PaError* pResult )
+{
+    PaWinWdmFilter** ppFilters = NULL;
+    HDEVINFO handle = NULL;
+    int device;
+    int invalidDevices;
+    int slot;
     SP_DEVICE_INTERFACE_DATA interfaceData;
     SP_DEVICE_INTERFACE_DATA aliasData;
     SP_DEVINFO_DATA devInfoData;
     int noError;
     const int sizeInterface = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR));
     unsigned char interfaceDetailsArray[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR))];
-    SP_DEVICE_INTERFACE_DETAIL_DATA* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA*)interfaceDetailsArray;
-    TCHAR friendlyName[MAX_PATH];
-    HKEY hkey;
-    DWORD sizeFriendlyName;
-    DWORD type;
-    PaWinWdmFilter* newFilter;
-    GUID* category = (GUID*)&KSCATEGORY_AUDIO;
-    GUID* alias_render = (GUID*)&KSCATEGORY_RENDER;
-    GUID* alias_capture = (GUID*)&KSCATEGORY_CAPTURE;
-    DWORD hasAlias;
+    SP_DEVICE_INTERFACE_DETAIL_DATA_W* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)interfaceDetailsArray;
+    const GUID* category = (const GUID*)&KSCATEGORY_AUDIO;
+    const GUID* alias_render = (const GUID*)&KSCATEGORY_RENDER;
+    const GUID* alias_capture = (const GUID*)&KSCATEGORY_CAPTURE;
+    const GUID* category_realtime = (const GUID*)&KSCATEGORY_REALTIME;
+    DWORD aliasFlags;
+    PaWDMKSType streamingType;
+    int filterCount = 0;
+    int noOfPaDevices = 0;
 
     PA_LOGE_;
 
-    devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
+    assert(pFilterCount != NULL);
+    assert(pNoOfPaDevices != NULL);
+    assert(pResult != NULL);
+
+    devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
+    *pFilterCount = 0;
+    *pNoOfPaDevices = 0;
 
     /* Open a handle to search for devices (filters) */
     handle = SetupDiGetClassDevs(category,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
-    if( handle == NULL )
+    if( handle == INVALID_HANDLE_VALUE )
     {
-        return paUnanticipatedHostError;
+        *pResult = paUnanticipatedHostError;
+        return NULL;
     }
     PA_DEBUG(("Setup called\n"));
 
@@ -1632,7 +3029,7 @@ static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
             break; /* No more devices */
 
         /* Check this one has the render or capture alias */
-        hasAlias = 0;
+        aliasFlags = 0;
         noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData);
         PA_DEBUG(("noError = %d\n",noError));
         if(noError)
@@ -1640,7 +3037,7 @@ static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
             if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
             {
                 PA_DEBUG(("Device %d has render alias\n",device));
-                hasAlias |= 1; /* Has render alias */
+                aliasFlags |= Alias_kRender; /* Has render alias */
             }
             else
             {
@@ -1653,29 +3050,29 @@ static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
             if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
             {
                 PA_DEBUG(("Device %d has capture alias\n",device));
-                hasAlias |= 2; /* Has capture alias */
+                aliasFlags |= Alias_kCapture; /* Has capture alias */
             }
             else
             {
                 PA_DEBUG(("Device %d has no capture alias\n",device));
             }
         }
-        if(!hasAlias)
+        if(!aliasFlags)
             invalidDevices++; /* This was not a valid capture or render audio device */
-
     }
     /* Remember how many there are */
-    wdmHostApi->filterCount = device-invalidDevices;
+    filterCount = device-invalidDevices;
 
     PA_DEBUG(("Interfaces found: %d\n",device-invalidDevices));
 
     /* Now allocate the list of pointers to devices */
-    wdmHostApi->filters  = (PaWinWdmFilter**)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter*) * device );
-    if( !wdmHostApi->filters )
+    ppFilters  = (PaWinWdmFilter**)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter*) * filterCount);
+    if( ppFilters == 0 )
     {
         if(handle != NULL)
             SetupDiDestroyDeviceInfoList(handle);
-        return paInsufficientMemory;
+        *pResult = paInsufficientMemory;
+        return NULL;
     }
 
     /* Now create filter objects for each interface found */
@@ -1688,20 +3085,21 @@ static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
         aliasData.Reserved = 0;
         devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
         devInfoData.Reserved = 0;
+        streamingType = Type_kWaveCyclic;
 
         noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData);
         if( !noError )
             break; /* No more devices */
 
         /* Check this one has the render or capture alias */
-        hasAlias = 0;
+        aliasFlags = 0;
         noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData);
         if(noError)
         {
             if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
             {
                 PA_DEBUG(("Device %d has render alias\n",device));
-                hasAlias |= 1; /* Has render alias */
+                aliasFlags |= Alias_kRender; /* Has render alias */
             }
         }
         noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData);
@@ -1710,48 +3108,114 @@ static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
             if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
             {
                 PA_DEBUG(("Device %d has capture alias\n",device));
-                hasAlias |= 2; /* Has capture alias */
+                aliasFlags |= Alias_kCapture; /* Has capture alias */
             }
         }
-        if(!hasAlias)
+        if(!aliasFlags)
+        {
             continue; /* This was not a valid capture or render audio device */
+        }
+        else
+        {
+            /* Check if filter is WaveRT, if not it is a WaveCyclic */
+            noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,category_realtime,&aliasData);
+            if (noError)
+            {
+                PA_DEBUG(("Device %d has realtime alias\n",device));
+                aliasFlags |= Alias_kRealtime;
+                streamingType = Type_kWaveRT;
+            }
+        }
 
-        noError = SetupDiGetDeviceInterfaceDetail(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData);
+        noError = SetupDiGetDeviceInterfaceDetailW(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData);
         if( noError )
         {
+            DWORD type;
+            WCHAR friendlyName[MAX_PATH] = {0};
+            DWORD sizeFriendlyName;
+            PaWinWdmFilter* newFilter = 0;
+
+            PaError result = paNoError;
             /* Try to get the "friendly name" for this interface */
             sizeFriendlyName = sizeof(friendlyName);
-            /* Fix contributed by Ben Allison
-             * Removed KEY_SET_VALUE from flags on following call
-             * as its causes failure when running without admin rights
-             * and it was not required */
-            hkey=SetupDiOpenDeviceInterfaceRegKey(handle,&interfaceData,0,KEY_QUERY_VALUE);
-            if(hkey!=INVALID_HANDLE_VALUE)
-            {
-                noError = RegQueryValueEx(hkey,TEXT("FriendlyName"),0,&type,(BYTE*)friendlyName,&sizeFriendlyName);
-                if( noError == ERROR_SUCCESS )
+
+            if (IsEarlierThanVista() && IsUSBDevice(devInterfaceDetails->DevicePath))
+            {
+                /* XP and USB audio device needs to look elsewhere, otherwise it'll only be a "USB Audio Device". Not
+                very literate. */
+                if (!SetupDiGetDeviceRegistryPropertyW(handle,
+                    &devInfoData,
+                    SPDRP_LOCATION_INFORMATION, 
+                    &type,
+                    (BYTE*)friendlyName,
+                    sizeof(friendlyName),
+                    NULL))
                 {
-                    PA_DEBUG(("Interface %d, Name: %s\n",device,friendlyName));
-                    RegCloseKey(hkey);
+                    friendlyName[0] = 0;
                 }
-                else
+            }
+
+            if (friendlyName[0] == 0 || IsNameUSBAudioDevice(friendlyName))
+            {
+                /* Fix contributed by Ben Allison
+                * Removed KEY_SET_VALUE from flags on following call
+                * as its causes failure when running without admin rights
+                * and it was not required */
+                HKEY hkey=SetupDiOpenDeviceInterfaceRegKey(handle,&interfaceData,0,KEY_QUERY_VALUE);
+                if(hkey!=INVALID_HANDLE_VALUE)
                 {
-                    friendlyName[0] = 0;
+                    noError = RegQueryValueExW(hkey,L"FriendlyName",0,&type,(BYTE*)friendlyName,&sizeFriendlyName);
+                    if( noError == ERROR_SUCCESS )
+                    {
+                        PA_DEBUG(("Interface %d, Name: %s\n",device,friendlyName));
+                        RegCloseKey(hkey);
+                    }
+                    else
+                    {
+                        friendlyName[0] = 0;
+                    }
                 }
             }
-            newFilter = FilterNew(devInterfaceDetails->DevicePath,friendlyName,&result);
+
+            TrimString(friendlyName, sizeFriendlyName);
+
+            newFilter = FilterNew(streamingType, 
+                devInfoData.DevInst,
+                devInterfaceDetails->DevicePath,
+                friendlyName,
+                &result);
+
             if( result == paNoError )
             {
-                PA_DEBUG(("Filter created\n"));
-                wdmHostApi->filters[slot] = newFilter;
+                int pin;
+                unsigned filterIOs = 0;
+
+                /* Increment number of "devices" */
+                for (pin = 0; pin < newFilter->pinCount; ++pin)
+                {
+                    PaWinWdmPin* pPin = newFilter->pins[pin];
+                    if (pPin == NULL)
+                        continue;
+
+                    filterIOs += max(1, pPin->inputCount);
+                }
+
+                noOfPaDevices += filterIOs;
+
+                PA_DEBUG(("Filter (%s) created with %d valid pins (total I/Os: %u)\n", ((newFilter->devInfo.streamingType==Type_kWaveRT)?"WaveRT":"WaveCyclic"), newFilter->validPinCount, filterIOs));
+
+                assert(slot < filterCount);
+
+                ppFilters[slot] = newFilter;
+
                 slot++;
             }
             else
             {
                 PA_DEBUG(("Filter NOT created\n"));
                 /* As there are now less filters than we initially thought
-                 * we must reduce the count by one */
-                wdmHostApi->filterCount--;
+                * we must reduce the count by one */
+                filterCount--;
             }
         }
     }
@@ -1760,69 +3224,184 @@ static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
     if(handle != NULL)
         SetupDiDestroyDeviceInfoList(handle);
 
-    return paNoError;
+    *pFilterCount = filterCount;
+    *pNoOfPaDevices = noOfPaDevices;
+
+    return ppFilters;
 }
 
-PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
+typedef struct PaNameHashIndex
 {
-    PaError result = paNoError;
-    int i, deviceCount;
-    PaWinWdmHostApiRepresentation *wdmHostApi;
-    PaWinWdmDeviceInfo *deviceInfoArray;
-    PaWinWdmFilter* pFilter;
-    PaWinWdmDeviceInfo *wdmDeviceInfo;
-    PaDeviceInfo *deviceInfo;
+    unsigned index;
+    unsigned count;
+    ULONG    hash;
+    struct PaNameHashIndex *next;
+} PaNameHashIndex;
 
-    PA_LOGE_;
+typedef struct PaNameHashObject
+{
+    PaNameHashIndex* list;
+    PaUtilAllocationGroup* allocGroup;
+} PaNameHashObject;
 
-    /*
-    Attempt to load the KSUSER.DLL without which we cannot create pins
-    We will unload this on termination
-    */
-    if(DllKsUser == NULL)
+static ULONG GetNameHash(const wchar_t* str, const BOOL input)
+{
+    /* This is to make sure that a name that exists as both input & output won't get the same hash value */
+    const ULONG fnv_prime = (input ? 0x811C9DD7 : 0x811FEB0B);
+    ULONG hash = 0;
+    for(; *str != 0; str++)
     {
-        DllKsUser = LoadLibrary(TEXT("ksuser.dll"));
-        if(DllKsUser == NULL)
-            goto error;
+        hash *= fnv_prime;
+        hash ^= (*str);
     }
+    assert(hash != 0);
+    return hash;
+}
 
-    FunctionKsCreatePin = (KSCREATEPIN*)GetProcAddress(DllKsUser, "KsCreatePin");
-    if(FunctionKsCreatePin == NULL)
-        goto error;
+static PaError CreateHashEntry(PaNameHashObject* obj, const wchar_t* name, const BOOL input)
+{
+    ULONG hash = GetNameHash(name, input); 
+    PaNameHashIndex * pLast = NULL;
+    PaNameHashIndex * p = obj->list;
+    while (p != 0)
+    {
+        if (p->hash == hash)
+        {
+            break;
+        }
+        pLast = p;
+        p = p->next;
+    }
+    if (p == NULL)
+    {
+        p = (PaNameHashIndex*)PaUtil_GroupAllocateMemory(obj->allocGroup, sizeof(PaNameHashIndex));
+        if (p == NULL)
+        {
+            return paInsufficientMemory;
+        }
+        p->hash = hash;
+        p->count = 1;
+        if (pLast != 0)
+        {
+            assert(pLast->next == 0);
+            pLast->next = p;
+        }
+        if (obj->list == 0)
+        {
+            obj->list = p;
+        }
+    }
+    else
+    {
+        ++p->count;
+    }
+    return paNoError;
+}
 
-    wdmHostApi = (PaWinWdmHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinWdmHostApiRepresentation) );
-    if( !wdmHostApi )
+static PaError InitNameHashObject(PaNameHashObject* obj, PaWinWdmFilter* pFilter)
+{
+    int i;
+
+    obj->allocGroup = PaUtil_CreateAllocationGroup();
+    if (obj->allocGroup == NULL)
     {
-        result = paInsufficientMemory;
-        goto error;
+        return paInsufficientMemory;
     }
 
-    wdmHostApi->allocations = PaUtil_CreateAllocationGroup();
-    if( !wdmHostApi->allocations )
+    for (i = 0; i < pFilter->pinCount; ++i)
     {
-        result = paInsufficientMemory;
-        goto error;
+        unsigned m;
+        PaWinWdmPin* pin = pFilter->pins[i];
+
+        if (pin == NULL)
+            continue;
+
+        for (m = 0; m < max(1, pin->inputCount); ++m)
+        {
+            const BOOL isInput = (pin->dataFlow == KSPIN_DATAFLOW_OUT);
+            const wchar_t* name = (pin->inputs == NULL) ? pin->friendlyName : pin->inputs[m]->friendlyName;
+
+            PaError result = CreateHashEntry(obj, name, isInput);
+
+            if (result != paNoError)
+            {
+                return result;
+            }
+        }
+    }
+    return paNoError;
+}
+
+static void DeinitNameHashObject(PaNameHashObject* obj)
+{
+    assert(obj != 0);
+    PaUtil_FreeAllAllocations(obj->allocGroup);
+    PaUtil_DestroyAllocationGroup(obj->allocGroup);
+    memset(obj, 0, sizeof(PaNameHashObject));
+}
+
+static unsigned GetNameIndex(PaNameHashObject* obj, const wchar_t* name, const BOOL input)
+{
+    ULONG hash = GetNameHash(name, input); 
+    PaNameHashIndex* p = obj->list;
+    while (p != NULL)
+    {
+        if (p->hash == hash)
+        {
+            if (p->count > 1)
+            {
+                return (++p->index);
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        p = p->next;
     }
+    // Should never get here!!
+    assert(FALSE);
+    return 0;
+}
+
+static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, void **scanResults, int *newDeviceCount )
+{
+    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
+    PaError result = paNoError;
+    PaWinWdmFilter** ppFilters = 0;
+    PaWinWDMScanDeviceInfosResults *outArgument = 0;
+    int filterCount = 0;
+    int totalDeviceCount = 0;
+    int idxDevice = 0;
 
-    result = BuildFilterList( wdmHostApi );
+    ppFilters = BuildFilterList( &filterCount, &totalDeviceCount, &result );
     if( result != paNoError )
     {
         goto error;
     }
-    deviceCount = wdmHostApi->filterCount;
-
-    *hostApi = &wdmHostApi->inheritedHostApiRep;
-    (*hostApi)->info.structVersion = 1;
-    (*hostApi)->info.type = paWDMKS;
-    (*hostApi)->info.name = "Windows WDM-KS";
-    (*hostApi)->info.defaultInputDevice = paNoDevice;
-    (*hostApi)->info.defaultOutputDevice = paNoDevice;
 
-    if( deviceCount > 0 )
+    if( totalDeviceCount > 0 )
     {
-        (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
-               wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo*) * deviceCount );
-        if( !(*hostApi)->deviceInfos )
+        PaWinWdmDeviceInfo *deviceInfoArray = 0;
+        int idxFilter;
+        int i;
+
+        /* Allocate the out param for all the info we need */
+        outArgument = (PaWinWDMScanDeviceInfosResults *) PaUtil_GroupAllocateMemory(
+            wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults) );
+        if( !outArgument )
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+        outArgument->defaultInputDevice  = paNoDevice;
+        outArgument->defaultOutputDevice = paNoDevice;
+
+        outArgument->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
+            wdmHostApi->allocations, sizeof(PaDeviceInfo*) * totalDeviceCount );
+        if( !outArgument->deviceInfos )
         {
             result = paInsufficientMemory;
             goto error;
@@ -1830,190 +3409,413 @@ PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
 
         /* allocate all device info structs in a contiguous block */
         deviceInfoArray = (PaWinWdmDeviceInfo*)PaUtil_GroupAllocateMemory(
-                wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo) * deviceCount );
+            wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo) * totalDeviceCount );
         if( !deviceInfoArray )
         {
             result = paInsufficientMemory;
             goto error;
         }
 
-        for( i=0; i < deviceCount; ++i )
+        /* Make sure all items in array */
+        for( i = 0 ; i < totalDeviceCount; ++i )
+        {
+            PaDeviceInfo *deviceInfo  = &deviceInfoArray[i].inheritedDeviceInfo;
+            deviceInfo->structVersion = 2;
+            deviceInfo->hostApi       = hostApiIndex;
+            deviceInfo->name          = 0;
+            outArgument->deviceInfos[ i ] = deviceInfo;
+        }
+
+        idxDevice = 0;
+        for (idxFilter = 0; idxFilter < filterCount; ++idxFilter)
         {
-            wdmDeviceInfo = &deviceInfoArray[i];
-            deviceInfo = &wdmDeviceInfo->inheritedDeviceInfo;
-            pFilter = wdmHostApi->filters[i];
+            PaNameHashObject nameHash = {0};
+            PaWinWdmFilter* pFilter = ppFilters[idxFilter];
             if( pFilter == NULL )
                 continue;
-            wdmDeviceInfo->filter = pFilter;
-            deviceInfo->structVersion = 2;
-            deviceInfo->hostApi = hostApiIndex;
-            deviceInfo->name = (char*)pFilter->friendlyName;
-            PA_DEBUG(("Device found name: %s\n",(char*)pFilter->friendlyName));
-            deviceInfo->maxInputChannels = pFilter->maxInputChannels;
-            if(deviceInfo->maxInputChannels > 0)
-            {
-                /* Set the default input device to the first device we find with
-                 * more than zero input channels
-                 **/
-                if((*hostApi)->info.defaultInputDevice == paNoDevice)
-                {
-                    (*hostApi)->info.defaultInputDevice = i;
-                }
-            }
 
-            deviceInfo->maxOutputChannels = pFilter->maxOutputChannels;
-            if(deviceInfo->maxOutputChannels > 0)
+            if (InitNameHashObject(&nameHash, pFilter) != paNoError)
             {
-                /* Set the default output device to the first device we find with
-                 * more than zero output channels
-                 **/
-                if((*hostApi)->info.defaultOutputDevice == paNoDevice)
-                {
-                    (*hostApi)->info.defaultOutputDevice = i;
-                }
+                DeinitNameHashObject(&nameHash);
+                continue;
             }
 
-            /* These low values are not very useful because
-             * a) The lowest latency we end up with can depend on many factors such
-             *    as the device buffer sizes/granularities, sample rate, channels and format
-             * b) We cannot know the device buffer sizes until we try to open/use it at
-             *    a particular setting
-             * So: we give 512x48000Hz frames as the default low input latency
-             **/
-            deviceInfo->defaultLowInputLatency = (512.0/48000.0);
-            deviceInfo->defaultLowOutputLatency = (512.0/48000.0);
-            deviceInfo->defaultHighInputLatency = (4096.0/48000.0);
-            deviceInfo->defaultHighOutputLatency = (4096.0/48000.0);
-            deviceInfo->defaultSampleRate = (double)(pFilter->bestSampleRate);
+            for (i = 0; i < pFilter->pinCount; ++i)
+            {
+                unsigned m;
+                ULONG nameIndex = 0;
+                ULONG nameIndexHash = 0;
+                PaWinWdmPin* pin = pFilter->pins[i];
 
-            (*hostApi)->deviceInfos[i] = deviceInfo;
-        }
-    }
+                if (pin == NULL)
+                    continue;
 
-    (*hostApi)->info.deviceCount = deviceCount;
+                for (m = 0; m < max(1, pin->inputCount); ++m)
+                {
+                    PaWinWdmDeviceInfo *wdmDeviceInfo = (PaWinWdmDeviceInfo *)outArgument->deviceInfos[idxDevice];
+                    PaDeviceInfo *deviceInfo = &wdmDeviceInfo->inheritedDeviceInfo;
+                    wchar_t localCompositeName[MAX_PATH];
+                    unsigned nameIndex = 0;
+                    const BOOL isInput = (pin->dataFlow == KSPIN_DATAFLOW_OUT);
 
-    (*hostApi)->Terminate = Terminate;
-    (*hostApi)->OpenStream = OpenStream;
-    (*hostApi)->IsFormatSupported = IsFormatSupported;
+                    wdmDeviceInfo->filter = pFilter;
 
-    PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream,
-                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
-                                      GetStreamTime, GetStreamCpuLoad,
-                                      PaUtil_DummyRead, PaUtil_DummyWrite,
-                                      PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
+                    deviceInfo->structVersion = 2;
+                    deviceInfo->hostApi = hostApiIndex;
+                    deviceInfo->name = wdmDeviceInfo->compositeName;
+                    /* deviceInfo->hostApiSpecificDeviceInfo = &pFilter->devInfo; */
 
-    PaUtil_InitializeStreamInterface( &wdmHostApi->blockingStreamInterface, CloseStream, StartStream,
-                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
-                                      GetStreamTime, PaUtil_DummyGetCpuLoad,
-                                      ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
+                    wdmDeviceInfo->pin = pin->pinId;
 
-    PA_LOGL_;
+                    /* Get the name of the "device" */
+                    if (pin->inputs == NULL)
+                    {
+                        wcsncpy(localCompositeName, pin->friendlyName, MAX_PATH);
+                        wdmDeviceInfo->muxPosition = -1;
+                        wdmDeviceInfo->endpointPinId = pin->endpointPinId;
+                    }
+                    else
+                    {
+                        PaWinWdmMuxedInput* input = pin->inputs[m];
+                        wcsncpy(localCompositeName, input->friendlyName, MAX_PATH);
+                        wdmDeviceInfo->muxPosition = (int)m;
+                        wdmDeviceInfo->endpointPinId = input->endpointPinId;
+                    }
+
+                    {
+                        /* Get base length */
+                        size_t n = wcslen(localCompositeName);
+
+                        /* Check if there are more entries with same name (which might very well be the case), if there
+                        are, the name will be postfixed with an index. */
+                        nameIndex = GetNameIndex(&nameHash, localCompositeName, isInput);
+                        if (nameIndex > 0)
+                        {
+                            /* This name has multiple instances, so we post fix with a number */
+                            n += _snwprintf(localCompositeName + n, MAX_PATH - n, L" %u", nameIndex);
+                        }
+                        /* Postfix with filter name */
+                        _snwprintf(localCompositeName + n, MAX_PATH - n, L" (%s)", pFilter->friendlyName);
+                    }
+
+                    /* Convert wide char string to utf-8 */
+                    WideCharToMultiByte(CP_UTF8, 0, localCompositeName, -1, wdmDeviceInfo->compositeName, MAX_PATH, NULL, NULL);
+
+                    /* NB! WDM/KS has no concept of a full-duplex device, each pin is either an input or and output */
+                    if (isInput)
+                    {
+                        /* INPUT ! */
+                        deviceInfo->maxInputChannels  = pin->maxChannels;
+                        deviceInfo->maxOutputChannels = 0;
+
+                        if (outArgument->defaultInputDevice == paNoDevice)
+                        {
+                            outArgument->defaultInputDevice = idxDevice;
+                        }
+                    }
+                    else
+                    {
+                        /* OUTPUT ! */
+                        deviceInfo->maxInputChannels  = 0;
+                        deviceInfo->maxOutputChannels = pin->maxChannels;
+
+                        if (outArgument->defaultOutputDevice == paNoDevice)
+                        {
+                            outArgument->defaultOutputDevice = idxDevice;
+                        }
+                    }
+
+                    /* These low values are not very useful because
+                    * a) The lowest latency we end up with can depend on many factors such
+                    *    as the device buffer sizes/granularities, sample rate, channels and format
+                    * b) We cannot know the device buffer sizes until we try to open/use it at
+                    *    a particular setting
+                    * So: we give 512x48000Hz frames as the default low input latency
+                    **/
+                    switch (pFilter->devInfo.streamingType)
+                    {
+                    case Type_kWaveCyclic:
+                        if (IsEarlierThanVista())
+                        {
+                            /* XP doesn't tolerate low latency, unless the Process Priority Class is set to REALTIME_PRIORITY_CLASS 
+                            through SetPriorityClass, then 10 ms is quite feasible. However, one should then bear in mind that ALL of
+                            the process is running in REALTIME_PRIORITY_CLASS, which might not be appropriate for an application with
+                            a GUI . In this case it is advisable to separate the audio engine in another process and use IPC to communicate
+                            with it. */
+                            deviceInfo->defaultLowInputLatency = 0.02;
+                            deviceInfo->defaultLowOutputLatency = 0.02;
+                        }
+                        else
+                        {
+                            /* This is a conservative estimate. Most WaveCyclic drivers will limit the available latency, but f.i. my Edirol
+                            PCR-A30 can reach 3 ms latency easily... */
+                            deviceInfo->defaultLowInputLatency = 0.01;
+                            deviceInfo->defaultLowOutputLatency = 0.01;
+                        }
+                        deviceInfo->defaultHighInputLatency = (4096.0/48000.0);
+                        deviceInfo->defaultHighOutputLatency = (4096.0/48000.0);
+                        deviceInfo->defaultSampleRate = (double)(pin->defaultSampleRate);
+                        break;
+                    case Type_kWaveRT:
+                        /* This is also a conservative estimate, based on WaveRT polled mode. In polled mode, the latency will be dictated
+                        by the buffer size given by the driver. */
+                        deviceInfo->defaultLowInputLatency = 0.01;
+                        deviceInfo->defaultLowOutputLatency = 0.01;
+                        deviceInfo->defaultHighInputLatency = 0.04;
+                        deviceInfo->defaultHighOutputLatency = 0.04;
+                        deviceInfo->defaultSampleRate = (double)(pin->defaultSampleRate);
+                        break;
+                    default:
+                        assert(0);
+                        break;
+                    }
+
+                    /* Add reference to filter */
+                    FilterAddRef(wdmDeviceInfo->filter);
+
+                    assert(idxDevice < totalDeviceCount);
+                    ++idxDevice;
+                }
+            }
+
+            /* If no one has add ref'd the filter, drop it */
+            if (pFilter->filterRefCount == 0)
+            {
+                FilterFree(pFilter);
+            }
+
+            /* Deinitialize name hash object */
+            DeinitNameHashObject(&nameHash);
+        }
+    }
+
+    *scanResults = outArgument;
+    *newDeviceCount = idxDevice;
     return result;
 
 error:
-    if( DllKsUser != NULL )
+    result = DisposeDeviceInfos(hostApi, outArgument, totalDeviceCount);
+
+    return result;
+}
+
+static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *scanResults, int deviceCount )
+{
+    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
+
+    hostApi->info.deviceCount = 0;
+    hostApi->info.defaultInputDevice = paNoDevice;
+    hostApi->info.defaultOutputDevice = paNoDevice;
+
+    /* Free any old memory which might be in the device info */
+    if( hostApi->deviceInfos )
     {
-        FreeLibrary( DllKsUser );
-        DllKsUser = NULL;
+        PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory(
+            wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults));
+        localScanResults->deviceInfos = hostApi->deviceInfos;
+
+        DisposeDeviceInfos(hostApi, &localScanResults, hostApi->info.deviceCount);
+
+        hostApi->deviceInfos = NULL;
     }
 
-    if( wdmHostApi )
+    if( scanResults != NULL )
     {
-        PaUtil_FreeMemory( wdmHostApi->filters );
-        if( wdmHostApi->allocations )
+        PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults;
+
+        if( deviceCount > 0 )
         {
-            PaUtil_FreeAllAllocations( wdmHostApi->allocations );
-            PaUtil_DestroyAllocationGroup( wdmHostApi->allocations );
+            /* use the array allocated in ScanDeviceInfos() as our deviceInfos */
+            hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos;
+
+            hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice;
+            hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice;
+
+            hostApi->info.deviceCount = deviceCount;
         }
-        PaUtil_FreeMemory( wdmHostApi );
+
+        PaUtil_GroupFreeMemory( wdmHostApi->allocations, scanDeviceInfosResults );
     }
-    PA_LOGL_;
-    return result;
-}
 
+    return paNoError;
 
-static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+}
+
+static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount )
 {
-    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
-    int i;
-    PA_LOGE_;
+    PaWinWdmHostApiRepresentation *winDsHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
 
-    if( wdmHostApi->filters )
+    if( scanResults != NULL )
     {
-        for( i=0; i<wdmHostApi->filterCount; i++)
+        PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults;
+
+        if( scanDeviceInfosResults->deviceInfos )
         {
-            if( wdmHostApi->filters[i] != NULL )
+            int i;
+            for (i = 0; i < deviceCount; ++i)
             {
-                FilterFree( wdmHostApi->filters[i] );
-                wdmHostApi->filters[i] = NULL;
+                PaWinWdmDeviceInfo* pDevice = (PaWinWdmDeviceInfo*)scanDeviceInfosResults->deviceInfos[i];
+                if (pDevice->filter != 0)
+                {
+                    FilterFree(pDevice->filter);
+                }
             }
+
+            PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos[0] ); /* all device info structs are allocated in a block so we can destroy them here */
+            PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos );
         }
+
+        PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults );
     }
-    PaUtil_FreeMemory( wdmHostApi->filters );
-    if( wdmHostApi->allocations )
-    {
-        PaUtil_FreeAllAllocations( wdmHostApi->allocations );
-        PaUtil_DestroyAllocationGroup( wdmHostApi->allocations );
-    }
-    PaUtil_FreeMemory( wdmHostApi );
-    PA_LOGL_;
+
+    return paNoError;
+
 }
 
-static void FillWFEXT( WAVEFORMATEXTENSIBLE* pwfext, PaSampleFormat sampleFormat, double sampleRate, int channelCount)
+PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
 {
+    PaError result = paNoError;
+    int deviceCount = 0;
+    void *scanResults = 0;
+    PaWinWdmHostApiRepresentation *wdmHostApi = NULL;
+
     PA_LOGE_;
-    PA_DEBUG(( "sampleFormat = %lx\n" , sampleFormat ));
-    PA_DEBUG(( "sampleRate = %f\n" , sampleRate ));
-    PA_DEBUG(( "chanelCount = %d\n", channelCount ));
-
-    pwfext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
-    pwfext->Format.nChannels = channelCount;
-    pwfext->Format.nSamplesPerSec = (int)sampleRate;
-    if(channelCount == 1)
-        pwfext->dwChannelMask = KSAUDIO_SPEAKER_DIRECTOUT;
-    else
-        pwfext->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
-    if(sampleFormat == paFloat32)
+
+#ifdef PA_WDMKS_SET_TREF
+    tRef = PaUtil_GetTime();
+#endif
+
+    /*
+    Attempt to load the KSUSER.DLL without which we cannot create pins
+    We will unload this on termination
+    */
+    if(DllKsUser == NULL)
+    {
+        DllKsUser = LoadLibrary(TEXT("ksuser.dll"));
+        if(DllKsUser == NULL)
+            goto error;
+    }
+    FunctionKsCreatePin = (KSCREATEPIN*)GetProcAddress(DllKsUser, "KsCreatePin");
+    if(FunctionKsCreatePin == NULL)
+        goto error;
+
+    /* Attempt to load AVRT.DLL, if we can't, then we'll just use time critical prio instead... */
+    if(paWinWDMKSAvRtEntryPoints.hInstance == NULL)
+    {
+        paWinWDMKSAvRtEntryPoints.hInstance = LoadLibrary(TEXT("avrt.dll"));
+        if (paWinWDMKSAvRtEntryPoints.hInstance != NULL)
+        {
+            paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics =
+                (HANDLE(WINAPI*)(LPCSTR,LPDWORD))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance,"AvSetMmThreadCharacteristicsA");
+            paWinWDMKSAvRtEntryPoints.AvRevertMmThreadCharacteristics =
+                (BOOL(WINAPI*)(HANDLE))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance, "AvRevertMmThreadCharacteristics");
+            paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority =
+                (BOOL(WINAPI*)(HANDLE,PA_AVRT_PRIORITY))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance, "AvSetMmThreadPriority");
+        }
+    }
+
+    wdmHostApi = (PaWinWdmHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinWdmHostApiRepresentation) );
+    if( !wdmHostApi )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    wdmHostApi->allocations = PaUtil_CreateAllocationGroup();
+    if( !wdmHostApi->allocations )
     {
-        pwfext->Format.nBlockAlign = channelCount * 4;
-        pwfext->Format.wBitsPerSample = 32;
-        pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
-        pwfext->Samples.wValidBitsPerSample = 32;
-        pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+        result = paInsufficientMemory;
+        goto error;
     }
-    else if(sampleFormat == paInt32)
+
+    *hostApi = &wdmHostApi->inheritedHostApiRep;
+    (*hostApi)->info.structVersion = 1;
+    (*hostApi)->info.type = paWDMKS;
+    (*hostApi)->info.name = "Windows WDM-KS";
+
+    /* these are all updated by CommitDeviceInfos() */
+    (*hostApi)->info.deviceCount = 0;
+    (*hostApi)->info.defaultInputDevice = paNoDevice;
+    (*hostApi)->info.defaultOutputDevice = paNoDevice;
+    (*hostApi)->deviceInfos = 0;
+
+    result = ScanDeviceInfos(&wdmHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount);
+    if (result != paNoError)
     {
-        pwfext->Format.nBlockAlign = channelCount * 4;
-        pwfext->Format.wBitsPerSample = 32;
-        pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
-        pwfext->Samples.wValidBitsPerSample = 32;
-        pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        goto error;
     }
-    else if(sampleFormat == paInt24)
+
+    CommitDeviceInfos(&wdmHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount);
+
+    (*hostApi)->Terminate = Terminate;
+    (*hostApi)->OpenStream = OpenStream;
+    (*hostApi)->IsFormatSupported = IsFormatSupported;
+    /* In preparation for hotplug
+    (*hostApi)->ScanDeviceInfos = ScanDeviceInfos;
+    (*hostApi)->CommitDeviceInfos = CommitDeviceInfos;
+    (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos;
+    */
+    PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream,
+        StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+        GetStreamTime, GetStreamCpuLoad,
+        PaUtil_DummyRead, PaUtil_DummyWrite,
+        PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
+
+    PaUtil_InitializeStreamInterface( &wdmHostApi->blockingStreamInterface, CloseStream, StartStream,
+        StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+        GetStreamTime, PaUtil_DummyGetCpuLoad,
+        ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
+
+    PA_LOGL_;
+    return result;
+
+error:
+    Terminate( (PaUtilHostApiRepresentation*)wdmHostApi );
+
+    PA_LOGL_;
+    return result;
+}
+
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
+    PA_LOGE_;
+
+    /* Do not unload the libraries */
+    if( DllKsUser != NULL )
     {
-        pwfext->Format.nBlockAlign = channelCount * 3;
-        pwfext->Format.wBitsPerSample = 24;
-        pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
-        pwfext->Samples.wValidBitsPerSample = 24;
-        pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        FreeLibrary( DllKsUser );
+        DllKsUser = NULL;
     }
-    else if(sampleFormat == paInt16)
+
+    if( paWinWDMKSAvRtEntryPoints.hInstance != NULL )
     {
-        pwfext->Format.nBlockAlign = channelCount * 2;
-        pwfext->Format.wBitsPerSample = 16;
-        pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
-        pwfext->Samples.wValidBitsPerSample = 16;
-        pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        FreeLibrary( paWinWDMKSAvRtEntryPoints.hInstance );
+        paWinWDMKSAvRtEntryPoints.hInstance = NULL;
     }
-    pwfext->Format.nAvgBytesPerSec = pwfext->Format.nSamplesPerSec * pwfext->Format.nBlockAlign;
 
+    if( wdmHostApi)
+    {
+        PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory(
+            wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults));
+        localScanResults->deviceInfos = hostApi->deviceInfos;
+        DisposeDeviceInfos(hostApi, localScanResults, hostApi->info.deviceCount);
+
+        if( wdmHostApi->allocations )
+        {
+            PaUtil_FreeAllAllocations( wdmHostApi->allocations );
+            PaUtil_DestroyAllocationGroup( wdmHostApi->allocations );
+        }
+        PaUtil_FreeMemory( wdmHostApi );
+    }
     PA_LOGL_;
 }
 
 static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
-                                  const PaStreamParameters *inputParameters,
-                                  const PaStreamParameters *outputParameters,
-                                  double sampleRate )
+                                 const PaStreamParameters *inputParameters,
+                                 const PaStreamParameters *outputParameters,
+                                 double sampleRate )
 {
     int inputChannelCount, outputChannelCount;
     PaSampleFormat inputSampleFormat, outputSampleFormat;
@@ -2021,49 +3823,114 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
     PaWinWdmFilter* pFilter;
     int result = paFormatIsSupported;
     WAVEFORMATEXTENSIBLE wfx;
+    PaWinWaveFormatChannelMask channelMask;
 
     PA_LOGE_;
 
     if( inputParameters )
     {
+        PaWinWdmDeviceInfo* pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device];
+        PaWinWdmPin* pin;
+        unsigned fmt;
+        unsigned long testFormat = 0;
+        unsigned validBits = 0;
+
         inputChannelCount = inputParameters->channelCount;
         inputSampleFormat = inputParameters->sampleFormat;
 
         /* all standard sample formats are supported by the buffer adapter,
-            this implementation doesn't support any custom sample formats */
+        this implementation doesn't support any custom sample formats */
         if( inputSampleFormat & paCustomFormat )
+        {
+            PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "IsFormatSupported: Custom input format not supported");
             return paSampleFormatNotSupported;
+        }
 
         /* unless alternate device specification is supported, reject the use of
-            paUseHostApiSpecificDeviceSpecification */
+        paUseHostApiSpecificDeviceSpecification */
 
         if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "IsFormatSupported: paUseHostApiSpecificDeviceSpecification not supported");
             return paInvalidDevice;
+        }
 
         /* check that input device can support inputChannelCount */
         if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "IsFormatSupported: Invalid input channel count");
             return paInvalidChannelCount;
+        }
 
         /* validate inputStreamInfo */
         if( inputParameters->hostApiSpecificStreamInfo )
+        {
+            PaWinWDM_SetLastErrorInfo(paIncompatibleHostApiSpecificStreamInfo, "Host API stream info not supported");
             return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+        }
+
+        pFilter = pDeviceInfo->filter;
+        pin = pFilter->pins[pDeviceInfo->pin];
+
+        /* Find out the testing format */
+        for (fmt = paFloat32; fmt <= paUInt8; fmt <<= 1)
+        {
+            if ((fmt & pin->formats) != 0)
+            {
+                /* Found a matching format! */
+                testFormat = fmt;
+                break;
+            }
+        }
+        if (testFormat == 0)
+        {
+            PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(capture) failed: no testformat found!");
+            return paUnanticipatedHostError;
+        }
+
+        /* Due to special considerations, WaveRT devices with paInt24 should be tested with paInt32 and
+        valid bits = 24 (instead of 24 bit samples) */
+        if (pFilter->devInfo.streamingType == Type_kWaveRT && testFormat == paInt24)
+        {
+            PA_DEBUG(("IsFormatSupported (capture): WaveRT overriding testFormat paInt24 with paInt32 (24 valid bits)"));
+            testFormat = paInt32;
+            validBits = 24;
+        }
 
         /* Check that the input format is supported */
-        FillWFEXT(&wfx,paInt16,sampleRate,inputChannelCount);
+        channelMask = PaWin_DefaultChannelMask(inputChannelCount);
+        PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
+            inputChannelCount, 
+            testFormat,
+            PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
+            sampleRate,
+            channelMask );
+        if (validBits != 0)
+        {
+            wfx.Samples.wValidBitsPerSample = validBits;
+        }
 
-        pFilter = wdmHostApi->filters[inputParameters->device];
-        result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx);
+        result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
         if( result != paNoError )
         {
             /* Try a WAVE_FORMAT_PCM instead */
-            wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
-            wfx.Format.cbSize = 0;
-            wfx.Samples.wValidBitsPerSample = 0;
-            wfx.dwChannelMask = 0;
-            wfx.SubFormat = GUID_NULL;
-            result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx);
+            PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
+                inputChannelCount, 
+                testFormat,
+                PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
+                sampleRate);
+
+            if (validBits != 0)
+            {
+                wfx.Samples.wValidBitsPerSample = validBits;
+            }
+
+            result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
             if( result != paNoError )
-                 return result;
+            {
+                PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(capture) failed: sr=%u,ch=%u,bits=%u", wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample);
+                return result;
+            }
         }
     }
     else
@@ -2073,44 +3940,109 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 
     if( outputParameters )
     {
+        PaWinWdmDeviceInfo* pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device];
+        PaWinWdmPin* pin;
+        unsigned fmt;
+        unsigned long testFormat = 0;
+        unsigned validBits = 0;
+
         outputChannelCount = outputParameters->channelCount;
         outputSampleFormat = outputParameters->sampleFormat;
 
         /* all standard sample formats are supported by the buffer adapter,
-            this implementation doesn't support any custom sample formats */
+        this implementation doesn't support any custom sample formats */
         if( outputSampleFormat & paCustomFormat )
+        {
+            PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "IsFormatSupported: Custom output format not supported");
             return paSampleFormatNotSupported;
+        }
 
         /* unless alternate device specification is supported, reject the use of
-            paUseHostApiSpecificDeviceSpecification */
+        paUseHostApiSpecificDeviceSpecification */
 
         if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "IsFormatSupported: paUseHostApiSpecificDeviceSpecification not supported");
             return paInvalidDevice;
+        }
 
         /* check that output device can support outputChannelCount */
         if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid output channel count");
             return paInvalidChannelCount;
+        }
 
         /* validate outputStreamInfo */
         if( outputParameters->hostApiSpecificStreamInfo )
+        {
+            PaWinWDM_SetLastErrorInfo(paIncompatibleHostApiSpecificStreamInfo, "Host API stream info not supported");
             return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+        }
+
+        pFilter = pDeviceInfo->filter;
+        pin = pFilter->pins[pDeviceInfo->pin];
+
+        /* Find out the testing format */
+        for (fmt = paFloat32; fmt <= paUInt8; fmt <<= 1)
+        {
+            if ((fmt & pin->formats) != 0)
+            {
+                /* Found a matching format! */
+                testFormat = fmt;
+                break;
+            }
+        }
+        if (testFormat == 0)
+        {
+            PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(render) failed: no testformat found!");
+            return paUnanticipatedHostError;
+        }
+
+        /* Due to special considerations, WaveRT devices with paInt24 should be tested with paInt32 and
+        valid bits = 24 (instead of 24 bit samples) */
+        if (pFilter->devInfo.streamingType == Type_kWaveRT && testFormat == paInt24)
+        {
+            PA_DEBUG(("IsFormatSupported (render): WaveRT overriding testFormat paInt24 with paInt32 (24 valid bits)"));
+            testFormat = paInt32;
+            validBits = 24;
+        }
 
         /* Check that the output format is supported */
-        FillWFEXT(&wfx,paInt16,sampleRate,outputChannelCount);
+        channelMask = PaWin_DefaultChannelMask(outputChannelCount);
+        PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
+            outputChannelCount, 
+            testFormat,
+            PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
+            sampleRate,
+            channelMask );
+
+        if (validBits != 0)
+        {
+            wfx.Samples.wValidBitsPerSample = validBits;
+        }
 
-        pFilter = wdmHostApi->filters[outputParameters->device];
-        result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx);
+        result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
         if( result != paNoError )
         {
             /* Try a WAVE_FORMAT_PCM instead */
-            wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
-            wfx.Format.cbSize = 0;
-            wfx.Samples.wValidBitsPerSample = 0;
-            wfx.dwChannelMask = 0;
-            wfx.SubFormat = GUID_NULL;
-            result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx);
+            PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
+                outputChannelCount, 
+                testFormat,
+                PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
+                sampleRate);
+
+            if (validBits != 0)
+            {
+                wfx.Samples.wValidBitsPerSample = validBits;
+            }
+
+            result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
             if( result != paNoError )
-                 return result;
+            {
+                PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(render) failed: %u,%u,%u", wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample);
+                return result;
+            }
         }
 
     }
@@ -2120,83 +4052,196 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
     }
 
     /*
-        IMPLEMENT ME:
+    IMPLEMENT ME:
 
-            - if a full duplex stream is requested, check that the combination
-                of input and output parameters is supported if necessary
+    - if a full duplex stream is requested, check that the combination
+    of input and output parameters is supported if necessary
 
-            - check that the device supports sampleRate
+    - check that the device supports sampleRate
 
-        Because the buffer adapter handles conversion between all standard
-        sample formats, the following checks are only required if paCustomFormat
-        is implemented, or under some other unusual conditions.
+    Because the buffer adapter handles conversion between all standard
+    sample formats, the following checks are only required if paCustomFormat
+    is implemented, or under some other unusual conditions.
 
-            - check that input device can support inputSampleFormat, or that
-                we have the capability to convert from inputSampleFormat to
-                a native format
+    - check that input device can support inputSampleFormat, or that
+    we have the capability to convert from inputSampleFormat to
+    a native format
 
-            - check that output device can support outputSampleFormat, or that
-                we have the capability to convert from outputSampleFormat to
-                a native format
+    - check that output device can support outputSampleFormat, or that
+    we have the capability to convert from outputSampleFormat to
+    a native format
     */
     if((inputChannelCount == 0)&&(outputChannelCount == 0))
-            result = paSampleFormatNotSupported; /* Not right error */
+    {
+        PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "No input or output channels defined");
+        result = paSampleFormatNotSupported; /* Not right error */
+    }
 
     PA_LOGL_;
     return result;
 }
 
-/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
-
-static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
-                           PaStream** s,
-                           const PaStreamParameters *inputParameters,
-                           const PaStreamParameters *outputParameters,
-                           double sampleRate,
-                           unsigned long framesPerBuffer,
-                           PaStreamFlags streamFlags,
-                           PaStreamCallback *streamCallback,
-                           void *userData )
+static void ResetStreamEvents(PaWinWdmStream* stream) 
 {
-    PaError result = paNoError;
-    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
-    PaWinWdmStream *stream = 0;
-    /* unsigned long framesPerHostBuffer; these may not be equivalent for all implementations */
-    PaSampleFormat inputSampleFormat, outputSampleFormat;
-    PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
-    int userInputChannels,userOutputChannels;
-    int size;
-    PaWinWdmFilter* pFilter;
-    WAVEFORMATEXTENSIBLE wfx;
-
-    PA_LOGE_;
-    PA_DEBUG(("OpenStream:sampleRate = %f\n",sampleRate));
-    PA_DEBUG(("OpenStream:framesPerBuffer = %lu\n",framesPerBuffer));
+    unsigned i;
+    ResetEvent(stream->eventAbort);
+    ResetEvent(stream->eventStreamStart[StreamStart_kOk]);
+    ResetEvent(stream->eventStreamStart[StreamStart_kFailed]);
 
-    if( inputParameters )
+    for (i=0; i<stream->capture.noOfPackets; ++i)
     {
-        userInputChannels = inputParameters->channelCount;
-        inputSampleFormat = inputParameters->sampleFormat;
-
-        /* unless alternate device specification is supported, reject the use of
-            paUseHostApiSpecificDeviceSpecification */
-
-        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
-            return paInvalidDevice;
+        if (stream->capture.events && stream->capture.events[i])
+        {
+            ResetEvent(stream->capture.events[i]);
+        }
+    }
 
-        /* check that input device can support stream->userInputChannels */
-        if( userInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
-            return paInvalidChannelCount;
+    for (i=0; i<stream->render.noOfPackets; ++i)
+    {
+        if (stream->render.events && stream->render.events[i])
+        {
+            ResetEvent(stream->render.events[i]);
+        }
+    }
+}
 
-        /* validate inputStreamInfo */
-        if( inputParameters->hostApiSpecificStreamInfo )
-            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+static void CloseStreamEvents(PaWinWdmStream* stream) 
+{
+    unsigned i;
+    PaWinWdmIOInfo* ios[2] = { &stream->capture, &stream->render };
 
+    if (stream->eventAbort)
+    {
+        CloseHandle(stream->eventAbort);
+        stream->eventAbort = 0;
+    }
+    if (stream->eventStreamStart[StreamStart_kOk])
+    {
+        CloseHandle(stream->eventStreamStart[StreamStart_kOk]);
+    }
+    if (stream->eventStreamStart[StreamStart_kFailed])
+    {
+        CloseHandle(stream->eventStreamStart[StreamStart_kFailed]);
+    }
+
+    for (i = 0; i < 2; ++i)
+    {
+        unsigned j;
+        /* Unregister notification handles for WaveRT */
+        if (ios[i]->pPin && ios[i]->pPin->parentFilter->devInfo.streamingType == Type_kWaveRT &&
+            ios[i]->pPin->pinKsSubType == SubType_kNotification &&
+            ios[i]->events != 0)
+        {
+            PinUnregisterNotificationHandle(ios[i]->pPin, ios[i]->events[0]);
+        }
+
+        for (j=0; j < ios[i]->noOfPackets; ++j)
+        {
+            if (ios[i]->events && ios[i]->events[j])
+            {
+                CloseHandle(ios[i]->events[j]);
+                ios[i]->events[j] = 0;
+            }
+        }
+    }
+}
+
+static unsigned NextPowerOf2(unsigned val)
+{
+    val--;
+    val = (val >> 1) | val;
+    val = (val >> 2) | val;
+    val = (val >> 4) | val;
+    val = (val >> 8) | val;
+    val = (val >> 16) | val;
+    return ++val;
+}
+
+static PaError ValidateSpecificStreamParameters(
+    const PaStreamParameters *streamParameters,
+    const PaWinWDMKSInfo *streamInfo)
+{
+    if( streamInfo )
+    {
+        if( streamInfo->size != sizeof( PaWinWDMKSInfo )
+            || streamInfo->version != 1 )
+        {
+            PA_DEBUG(("Stream parameters: size or version not correct"));
+            return paIncompatibleHostApiSpecificStreamInfo;
+        }
+
+        if (streamInfo->noOfPackets != 0 &&
+            (streamInfo->noOfPackets < 2 || streamInfo->noOfPackets > 8))
+        {
+            PA_DEBUG(("Stream parameters: noOfPackets %u out of range [2,8]", streamInfo->noOfPackets));
+            return paIncompatibleHostApiSpecificStreamInfo;
+        }
+
+    }
+
+    return paNoError;
+}
+
+
+
+/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
+
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                          PaStream** s,
+                          const PaStreamParameters *inputParameters,
+                          const PaStreamParameters *outputParameters,
+                          double sampleRate,
+                          unsigned long framesPerUserBuffer,
+                          PaStreamFlags streamFlags,
+                          PaStreamCallback *streamCallback,
+                          void *userData )
+{
+    PaError result = paNoError;
+    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
+    PaWinWdmStream *stream = 0;
+    /* unsigned long framesPerHostBuffer; these may not be equivalent for all implementations */
+    PaSampleFormat inputSampleFormat, outputSampleFormat;
+    PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+    int userInputChannels,userOutputChannels;
+    WAVEFORMATEXTENSIBLE wfx;
+
+    PA_LOGE_;
+    PA_DEBUG(("OpenStream:sampleRate = %f\n",sampleRate));
+    PA_DEBUG(("OpenStream:framesPerBuffer = %lu\n",framesPerUserBuffer));
+
+    if( inputParameters )
+    {
+        userInputChannels = inputParameters->channelCount;
+        inputSampleFormat = inputParameters->sampleFormat;
+
+        /* unless alternate device specification is supported, reject the use of
+        paUseHostApiSpecificDeviceSpecification */
+
+        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "paUseHostApiSpecificDeviceSpecification(in) not supported");
+            return paInvalidDevice;
+        }
+
+        /* check that input device can support stream->userInputChannels */
+        if( userInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid input channel count");
+            return paInvalidChannelCount;
+        }
+
+        /* validate inputStreamInfo */
+        result = ValidateSpecificStreamParameters(inputParameters, inputParameters->hostApiSpecificStreamInfo);
+        if(result != paNoError)
+        {
+            PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (in)");
+            return result; /* this implementation doesn't use custom stream info */
+        }
     }
     else
     {
         userInputChannels = 0;
-        inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */
+        inputSampleFormat = hostInputSampleFormat = paInt16; /* Supress 'uninitialised var' warnings. */
     }
 
     if( outputParameters )
@@ -2205,29 +4250,41 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         outputSampleFormat = outputParameters->sampleFormat;
 
         /* unless alternate device specification is supported, reject the use of
-            paUseHostApiSpecificDeviceSpecification */
+        paUseHostApiSpecificDeviceSpecification */
 
         if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "paUseHostApiSpecificDeviceSpecification(out) not supported");
             return paInvalidDevice;
+        }
 
         /* check that output device can support stream->userInputChannels */
         if( userOutputChannels > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
+        {
+            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid output channel count");
             return paInvalidChannelCount;
+        }
 
         /* validate outputStreamInfo */
-        if( outputParameters->hostApiSpecificStreamInfo )
-            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
-
+        result = ValidateSpecificStreamParameters( outputParameters, outputParameters->hostApiSpecificStreamInfo );
+        if (result != paNoError)
+        {
+            PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (out)");
+            return result; /* this implementation doesn't use custom stream info */
+        }
     }
     else
     {
         userOutputChannels = 0;
-        outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */
+        outputSampleFormat = hostOutputSampleFormat = paInt16; /* Supress 'uninitialized var' warnings. */
     }
 
     /* validate platform specific flags */
     if( (streamFlags & paPlatformSpecificFlags) != 0 )
+    {
+        PaWinWDM_SetLastErrorInfo(paInvalidFlag, "Invalid flag supplied");
         return paInvalidFlag; /* unexpected platform specific flag */
+    }
 
     stream = (PaWinWdmStream*)PaUtil_AllocateMemory( sizeof(PaWinWdmStream) );
     if( !stream )
@@ -2235,18 +4292,33 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         result = paInsufficientMemory;
         goto error;
     }
+
+    /* Create allocation group */
+    stream->allocGroup = PaUtil_CreateAllocationGroup();
+    if( !stream->allocGroup )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
     /* Zero the stream object */
     /* memset((void*)stream,0,sizeof(PaWinWdmStream)); */
 
     if( streamCallback )
     {
         PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
-                                               &wdmHostApi->callbackStreamInterface, streamCallback, userData );
+            &wdmHostApi->callbackStreamInterface, streamCallback, userData );
     }
     else
     {
-        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
-                                               &wdmHostApi->blockingStreamInterface, streamCallback, userData );
+        /* PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+        &wdmHostApi->blockingStreamInterface, streamCallback, userData ); */
+
+        /* We don't support the blocking API yet */
+        PA_DEBUG(("Blocking API not supported yet!\n"));
+        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Blocking API not supported yet");
+        result = paUnanticipatedHostError;
+        goto error;
     }
 
     PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
@@ -2254,363 +4326,883 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     /* Instantiate the input pin if necessary */
     if(userInputChannels > 0)
     {
+        PaWinWdmFilter* pFilter;
+        PaWinWdmDeviceInfo* pDeviceInfo;
+        PaWinWdmPin* pPin;
+        unsigned validBitsPerSample = 0;
+        PaWinWaveFormatChannelMask channelMask = PaWin_DefaultChannelMask( userInputChannels );
+
         result = paSampleFormatNotSupported;
-        pFilter = wdmHostApi->filters[inputParameters->device];
-        stream->userInputChannels = userInputChannels;
+        pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device];
+        pFilter = pDeviceInfo->filter;
+        pPin = pFilter->pins[pDeviceInfo->pin];
 
-        if(((inputSampleFormat & ~paNonInterleaved) & pFilter->formats) != 0)
-        {   /* inputSampleFormat is supported, so try to use it */
-            hostInputSampleFormat = inputSampleFormat;
-            FillWFEXT(&wfx, hostInputSampleFormat, sampleRate, stream->userInputChannels);
-            stream->bytesPerInputFrame = wfx.Format.nBlockAlign;
-            stream->recordingPin = FilterCreateCapturePin(pFilter, (const WAVEFORMATEX*)&wfx, &result);
-            stream->deviceInputChannels = stream->userInputChannels;
-        }
-        
-        if(result != paNoError)
-        {   /* Search through all PaSampleFormats to find one that works */
-            hostInputSampleFormat = paFloat32;
+        stream->userInputChannels = userInputChannels;
 
-            do {
-                FillWFEXT(&wfx, hostInputSampleFormat, sampleRate, stream->userInputChannels);
-                stream->bytesPerInputFrame = wfx.Format.nBlockAlign;
-                stream->recordingPin = FilterCreateCapturePin(pFilter, (const WAVEFORMATEX*)&wfx, &result);
-                stream->deviceInputChannels = stream->userInputChannels;
-                
-                if(stream->recordingPin == NULL) result = paSampleFormatNotSupported;
-                if(result != paNoError)    hostInputSampleFormat <<= 1;
-            }
-            while(result != paNoError && hostInputSampleFormat <= paUInt8);
+        hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat( pPin->formats, inputSampleFormat );
+        if (hostInputSampleFormat == paSampleFormatNotSupported)
+        {
+            result = paUnanticipatedHostError;
+            PaWinWDM_SetLastErrorInfo(result, "PU_SCAF(%X,%X) failed (input)", pPin->formats, inputSampleFormat);
+            goto error;
         }
-
-        if(result != paNoError)
-        {    /* None of the PaSampleFormats worked.  Set the hostInputSampleFormat to the best fit
-             * and try a PCM format.
-             **/
-            hostInputSampleFormat =
-                PaUtil_SelectClosestAvailableFormat( pFilter->formats, inputSampleFormat );
-
-            /* Try a WAVE_FORMAT_PCM instead */
-            wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
-            wfx.Format.cbSize = 0;
-            wfx.Samples.wValidBitsPerSample = 0;
-            wfx.dwChannelMask = 0;
-            wfx.SubFormat = GUID_NULL;
-            stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
-            if(stream->recordingPin == NULL) result = paSampleFormatNotSupported;
+        else if (pFilter->devInfo.streamingType == Type_kWaveRT && hostInputSampleFormat == paInt24)
+        {
+            /* For WaveRT, we choose 32 bit format instead of paInt24, since we MIGHT need to align buffer on a
+            128 byte boundary (see PinGetBuffer) */
+            hostInputSampleFormat = paInt32;
+            /* But we'll tell the driver that it's 24 bit in 32 bit container */
+            validBitsPerSample = 24;
         }
 
-        if( result != paNoError )
+        while (hostInputSampleFormat <= paUInt8)
         {
+            unsigned channelsToProbe = stream->userInputChannels;
             /* Some or all KS devices can only handle the exact number of channels
-             * they specify. But PortAudio clients expect to be able to
-             * at least specify mono I/O on a multi-channel device
-             * If this is the case, then we will do the channel mapping internally
-             **/
-            if( stream->userInputChannels < pFilter->maxInputChannels )
-            {
-                FillWFEXT(&wfx,hostInputSampleFormat,sampleRate,pFilter->maxInputChannels);
-                stream->bytesPerInputFrame = wfx.Format.nBlockAlign;
-                stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
-                stream->deviceInputChannels = pFilter->maxInputChannels;
-
-                if( result != paNoError )
+            * they specify. But PortAudio clients expect to be able to
+            * at least specify mono I/O on a multi-channel device
+            * If this is the case, then we will do the channel mapping internally
+            * The following loop tests this case
+            **/
+            while (1)
+            {
+                PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
+                    channelsToProbe, 
+                    hostInputSampleFormat,
+                    PaWin_SampleFormatToLinearWaveFormatTag(hostInputSampleFormat),
+                    sampleRate,
+                    channelMask );
+                stream->capture.bytesPerFrame = wfx.Format.nBlockAlign;
+                if (validBitsPerSample != 0)
+                {
+                    wfx.Samples.wValidBitsPerSample = validBitsPerSample;
+                }
+                stream->capture.pPin = FilterCreatePin(pFilter, pPin->pinId, (WAVEFORMATEX*)&wfx, &result);
+                stream->deviceInputChannels = channelsToProbe;
+
+                if( result != paNoError && result != paDeviceUnavailable )
                 {
                     /* Try a WAVE_FORMAT_PCM instead */
-                    wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
-                    wfx.Format.cbSize = 0;
-                    wfx.Samples.wValidBitsPerSample = 0;
-                    wfx.dwChannelMask = 0;
-                    wfx.SubFormat = GUID_NULL;
-                    stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
+                    PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
+                        channelsToProbe, 
+                        hostInputSampleFormat,
+                        PaWin_SampleFormatToLinearWaveFormatTag(hostInputSampleFormat),
+                        sampleRate);
+                    if (validBitsPerSample != 0)
+                    {
+                        wfx.Samples.wValidBitsPerSample = validBitsPerSample;
+                    }
+                    stream->capture.pPin = FilterCreatePin(pFilter, pPin->pinId, (const WAVEFORMATEX*)&wfx, &result);
+                }
+
+                if (result == paDeviceUnavailable) goto occupied;
+
+                if (result == paNoError)
+                {
+                    /* We're done */
+                    break;
+                }
+
+                if (channelsToProbe < (unsigned)pPin->maxChannels)
+                {
+                    /* Go to next multiple of 2 */
+                    channelsToProbe = min((((channelsToProbe>>1)+1)<<1), (unsigned)pPin->maxChannels);
+                    continue;
                 }
+
+                break;
             }
+
+            if (result == paNoError)
+            {
+                /* We're done */
+                break;
+            }
+
+            /* Go to next format in line with lower resolution */
+            hostInputSampleFormat <<= 1;
         }
 
-        if(stream->recordingPin == NULL)
+        if(stream->capture.pPin == NULL)
         {
+            PaWinWDM_SetLastErrorInfo(result, "Failed to create capture pin: sr=%u,ch=%u,bits=%u,align=%u",
+                wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample, wfx.Format.nBlockAlign);
             goto error;
         }
 
-        switch(hostInputSampleFormat)
+        /* Select correct mux input on MUX node of topology filter */
+        if (pDeviceInfo->muxPosition >= 0)
         {
-            case paInt16: stream->inputSampleSize = 2; break;
-            case paInt24: stream->inputSampleSize = 3; break;
-            case paInt32:
-            case paFloat32:    stream->inputSampleSize = 4; break;
+            assert(pPin->parentFilter->topologyFilter != NULL);
+
+            result = FilterUse(pPin->parentFilter->topologyFilter);
+            if (result != paNoError)
+            {
+                PaWinWDM_SetLastErrorInfo(result, "Failed to open topology filter");
+                goto error;
+            }
+
+            result = WdmSetMuxNodeProperty(pPin->parentFilter->topologyFilter->handle,
+                pPin->inputs[pDeviceInfo->muxPosition]->muxNodeId,
+                pPin->inputs[pDeviceInfo->muxPosition]->muxPinId);
+
+            FilterRelease(pPin->parentFilter->topologyFilter);
+
+            if(result != paNoError)
+            {
+                PaWinWDM_SetLastErrorInfo(result, "Failed to set topology mux node");
+                goto error;
+            }
         }
 
-        stream->recordingPin->frameSize /= stream->bytesPerInputFrame;
-        PA_DEBUG(("Pin output frames: %d\n",stream->recordingPin->frameSize));
+        stream->capture.bytesPerSample = stream->capture.bytesPerFrame / stream->deviceInputChannels;
+        stream->capture.pPin->frameSize /= stream->capture.bytesPerFrame;
+        PA_DEBUG(("Capture pin frames: %d\n",stream->capture.pPin->frameSize));
     }
     else
     {
-        stream->recordingPin = NULL;
-        stream->bytesPerInputFrame = 0;
+        stream->capture.pPin = NULL;
+        stream->capture.bytesPerFrame = 0;
     }
 
     /* Instantiate the output pin if necessary */
     if(userOutputChannels > 0)
     {
+        PaWinWdmFilter* pFilter;
+        PaWinWdmDeviceInfo* pDeviceInfo;
+        PaWinWdmPin* pPin;
+        unsigned validBitsPerSample = 0;
+        PaWinWaveFormatChannelMask channelMask = PaWin_DefaultChannelMask( userOutputChannels );
+
         result = paSampleFormatNotSupported;
-        pFilter = wdmHostApi->filters[outputParameters->device];
+        pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device];
+        pFilter = pDeviceInfo->filter;
+        pPin = pFilter->pins[pDeviceInfo->pin];
+
         stream->userOutputChannels = userOutputChannels;
 
-        if(((outputSampleFormat & ~paNonInterleaved) & pFilter->formats) != 0)
+        hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat( pPin->formats, outputSampleFormat );
+        if (hostOutputSampleFormat == paSampleFormatNotSupported)
+        {
+            result = paUnanticipatedHostError;
+            PaWinWDM_SetLastErrorInfo(result, "PU_SCAF(%X,%X) failed (output)", pPin->formats, hostOutputSampleFormat);
+            goto error;
+        }
+        else if (pFilter->devInfo.streamingType == Type_kWaveRT && hostOutputSampleFormat == paInt24)
         {
-            hostOutputSampleFormat = outputSampleFormat;
-            FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,stream->userOutputChannels);
-            stream->bytesPerOutputFrame = wfx.Format.nBlockAlign;
-            stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result);
-            stream->deviceOutputChannels = stream->userOutputChannels;
+            /* For WaveRT, we choose 32 bit format instead of paInt24, since we MIGHT need to align buffer on a
+            128 byte boundary (see PinGetBuffer) */
+            hostOutputSampleFormat = paInt32;
+            /* But we'll tell the driver that it's 24 bit in 32 bit container */
+            validBitsPerSample = 24;
         }
 
-        if(result != paNoError)
+        while (hostOutputSampleFormat <= paUInt8)
         {
-            hostOutputSampleFormat = paFloat32;
+            unsigned channelsToProbe = stream->userOutputChannels;
+            /* Some or all KS devices can only handle the exact number of channels
+            * they specify. But PortAudio clients expect to be able to
+            * at least specify mono I/O on a multi-channel device
+            * If this is the case, then we will do the channel mapping internally
+            * The following loop tests this case
+            **/
+            while (1)
+            {
+                PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
+                    channelsToProbe, 
+                    hostOutputSampleFormat,
+                    PaWin_SampleFormatToLinearWaveFormatTag(hostOutputSampleFormat),
+                    sampleRate,
+                    channelMask );
+                stream->render.bytesPerFrame = wfx.Format.nBlockAlign;
+                if (validBitsPerSample != 0)
+                {
+                    wfx.Samples.wValidBitsPerSample = validBitsPerSample;
+                }
+                stream->render.pPin = FilterCreatePin(pFilter, pPin->pinId, (WAVEFORMATEX*)&wfx, &result);
+                stream->deviceOutputChannels = channelsToProbe;
 
-            do {
-                FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,stream->userOutputChannels);
-                stream->bytesPerOutputFrame = wfx.Format.nBlockAlign;
-                stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result);
-                stream->deviceOutputChannels = stream->userOutputChannels;
+                if( result != paNoError && result != paDeviceUnavailable )
+                {
+                    PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
+                        channelsToProbe, 
+                        hostOutputSampleFormat,
+                        PaWin_SampleFormatToLinearWaveFormatTag(hostOutputSampleFormat),
+                        sampleRate);
+                    if (validBitsPerSample != 0)
+                    {
+                        wfx.Samples.wValidBitsPerSample = validBitsPerSample;
+                    }
+                    stream->render.pPin = FilterCreatePin(pFilter, pPin->pinId, (const WAVEFORMATEX*)&wfx, &result);
+                }
 
-                if(stream->playbackPin == NULL) result = paSampleFormatNotSupported;
-                if(result != paNoError)    hostOutputSampleFormat <<= 1;
+                if (result == paDeviceUnavailable) goto occupied;
+
+                if (result == paNoError)
+                {
+                    /* We're done */
+                    break;
+                }
+
+                if (channelsToProbe < (unsigned)pPin->maxChannels)
+                {
+                    /* Go to next multiple of 2 */
+                    channelsToProbe = min((((channelsToProbe>>1)+1)<<1), (unsigned)pPin->maxChannels);
+                    continue;
+                }
+
+                break;
+            };
+
+            if (result == paNoError)
+            {
+                /* We're done */
+                break;
             }
-            while(result != paNoError && hostOutputSampleFormat <= paUInt8);
+
+            /* Go to next format in line with lower resolution */
+            hostOutputSampleFormat <<= 1;
         }
 
-        if(result != paNoError)
+        if(stream->render.pPin == NULL)
         {
-            hostOutputSampleFormat =
-                PaUtil_SelectClosestAvailableFormat( pFilter->formats, outputSampleFormat );
-       
-            /* Try a WAVE_FORMAT_PCM instead */
-            wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
-            wfx.Format.cbSize = 0;
-            wfx.Samples.wValidBitsPerSample = 0;
-            wfx.dwChannelMask = 0;
-            wfx.SubFormat = GUID_NULL;
-            stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result);
-            if(stream->playbackPin == NULL) result = paSampleFormatNotSupported;
-        }
-            
-        if( result != paNoError )
+            PaWinWDM_SetLastErrorInfo(result, "Failed to create render pin: sr=%u,ch=%u,bits=%u,align=%u",
+                wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample, wfx.Format.nBlockAlign);
+            goto error;
+        }
+
+        stream->render.bytesPerSample = stream->render.bytesPerFrame / stream->deviceOutputChannels;
+        stream->render.pPin->frameSize /= stream->render.bytesPerFrame;
+        PA_DEBUG(("Render pin frames: %d\n",stream->render.pPin->frameSize));
+    }
+    else
+    {
+        stream->render.pPin = NULL;
+        stream->render.bytesPerFrame = 0;
+    }
+
+    /* Calculate the framesPerHostXxxxBuffer size based upon the suggested latency values */
+    /* Record the buffer length */
+    if(inputParameters)
+    {
+        /* Calculate the frames from the user's value - add a bit to round up */
+        stream->capture.framesPerBuffer = (unsigned long)((inputParameters->suggestedLatency*sampleRate)+0.0001);
+        if(stream->capture.framesPerBuffer > (unsigned long)sampleRate)
+        { /* Upper limit is 1 second */
+            stream->capture.framesPerBuffer = (unsigned long)sampleRate;
+        }
+        else if(stream->capture.framesPerBuffer < stream->capture.pPin->frameSize)
         {
-            /* Some or all KS devices can only handle the exact number of channels
-             * they specify. But PortAudio clients expect to be able to
-             * at least specify mono I/O on a multi-channel device
-             * If this is the case, then we will do the channel mapping internally
-             **/
-            if( stream->userOutputChannels < pFilter->maxOutputChannels )
-            {
-                FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,pFilter->maxOutputChannels);
-                stream->bytesPerOutputFrame = wfx.Format.nBlockAlign;
-                stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
-                stream->deviceOutputChannels = pFilter->maxOutputChannels;
-                if( result != paNoError )
+            stream->capture.framesPerBuffer = stream->capture.pPin->frameSize;
+        }
+        PA_DEBUG(("Input frames chosen:%ld\n",stream->capture.framesPerBuffer));
+
+        /* Setup number of packets to use */
+        stream->capture.noOfPackets = 2;
+
+        if (inputParameters->hostApiSpecificStreamInfo)
+        {
+            PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)inputParameters->hostApiSpecificStreamInfo;
+
+            if (stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic &&
+                pInfo->noOfPackets != 0)
+            {
+                stream->capture.noOfPackets = pInfo->noOfPackets;
+            }
+        }
+    }
+
+    if(outputParameters)
+    {
+        /* Calculate the frames from the user's value - add a bit to round up */
+        stream->render.framesPerBuffer = (unsigned long)((outputParameters->suggestedLatency*sampleRate)+0.0001);
+        if(stream->render.framesPerBuffer > (unsigned long)sampleRate)
+        { /* Upper limit is 1 second */
+            stream->render.framesPerBuffer = (unsigned long)sampleRate;
+        }
+        else if(stream->render.framesPerBuffer < stream->render.pPin->frameSize)
+        {
+            stream->render.framesPerBuffer = stream->render.pPin->frameSize;
+        }
+        PA_DEBUG(("Output frames chosen:%ld\n",stream->render.framesPerBuffer));
+
+        /* Setup number of packets to use */
+        stream->render.noOfPackets = 2;
+
+        if (outputParameters->hostApiSpecificStreamInfo)
+        {
+            PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)outputParameters->hostApiSpecificStreamInfo;
+
+            if (stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic &&
+                pInfo->noOfPackets != 0)
+            {
+                stream->render.noOfPackets = pInfo->noOfPackets;
+            }
+        }
+    }
+
+    /* Host buffer size is bound to the largest of the input and output frame sizes */
+    result =  PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+        stream->userInputChannels, inputSampleFormat, hostInputSampleFormat,
+        stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat,
+        sampleRate, streamFlags, framesPerUserBuffer,
+        max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer), 
+        paUtilBoundedHostBufferSize,
+        streamCallback, userData );
+    if( result != paNoError )
+    {
+        PaWinWDM_SetLastErrorInfo(result, "PaUtil_InitializeBufferProcessor failed: ich=%u, isf=%u, hisf=%u, och=%u, osf=%u, hosf=%u, sr=%lf, flags=0x%X, fpub=%u, fphb=%u",
+            stream->userInputChannels, inputSampleFormat, hostInputSampleFormat,
+            stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat,
+            sampleRate, streamFlags, framesPerUserBuffer,
+            max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer));
+        goto error;
+    }
+
+    /* Allocate/get all the buffers for host I/O */
+    if (stream->userInputChannels > 0)
+    {
+        stream->streamRepresentation.streamInfo.inputLatency = stream->capture.framesPerBuffer / sampleRate;
+
+        switch (stream->capture.pPin->parentFilter->devInfo.streamingType)
+        {
+        case Type_kWaveCyclic:
+            {
+                unsigned size = stream->capture.noOfPackets * stream->capture.framesPerBuffer * stream->capture.bytesPerFrame;
+                /* Allocate input host buffer */
+                stream->capture.hostBuffer = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, size);
+                PA_DEBUG(("Input buffer allocated (size = %u)\n", size));
+                if( !stream->capture.hostBuffer )
                 {
-                    /* Try a WAVE_FORMAT_PCM instead */
-                    wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
-                    wfx.Format.cbSize = 0;
-                    wfx.Samples.wValidBitsPerSample = 0;
-                    wfx.dwChannelMask = 0;
-                    wfx.SubFormat = GUID_NULL;
-                    stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
+                    PA_DEBUG(("Cannot allocate host input buffer!\n"));
+                    PaWinWDM_SetLastErrorInfo(paInsufficientMemory, "Failed to allocate input buffer");
+                    result = paInsufficientMemory;
+                    goto error;
+                }
+                stream->capture.hostBufferSize = size;
+                PA_DEBUG(("Input buffer start = %p (size=%u)\n",stream->capture.hostBuffer, stream->capture.hostBufferSize));
+                stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveCyclic;
+                stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveCyclic;
+            }
+            break;
+        case Type_kWaveRT:
+            {
+                const DWORD dwTotalSize = 2 * stream->capture.framesPerBuffer * stream->capture.bytesPerFrame;
+                DWORD dwRequestedSize = dwTotalSize;
+                BOOL bCallMemoryBarrier = FALSE;
+                ULONG hwFifoLatency = 0;
+                ULONG dummy;
+                result = PinGetBuffer(stream->capture.pPin, (void**)&stream->capture.hostBuffer, &dwRequestedSize, &bCallMemoryBarrier);
+                if (!result) 
+                {
+                    PA_DEBUG(("Input buffer start = %p, size = %u\n", stream->capture.hostBuffer, dwRequestedSize));
+                    if (dwRequestedSize != dwTotalSize)
+                    {
+                        PA_DEBUG(("Buffer length changed by driver from %u to %u !\n", dwTotalSize, dwRequestedSize));
+                        /* Recalculate to what the driver has given us */
+                        stream->capture.framesPerBuffer = dwRequestedSize / (2 * stream->capture.bytesPerFrame);
+                    }
+                    stream->capture.hostBufferSize = dwRequestedSize;
+
+                    if (stream->capture.pPin->pinKsSubType == SubType_kPolled)
+                    {
+                        stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveRTPolled;
+                        stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveRTPolled;
+                    }
+                    else
+                    {
+                        stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveRTEvent;
+                        stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveRTEvent;
+                    }
+
+                    stream->capture.pPin->fnMemBarrier = bCallMemoryBarrier ? MemoryBarrierRead : MemoryBarrierDummy;
+                }
+                else 
+                {
+                    PA_DEBUG(("Failed to get input buffer (WaveRT)\n"));
+                    PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to get input buffer (WaveRT)");
+                    result = paUnanticipatedHostError;
+                    goto error;
+                }
+
+                /* Get latency */
+                result = PinGetHwLatency(stream->capture.pPin, &hwFifoLatency, &dummy, &dummy);
+                if (result == paNoError)
+                {
+                    stream->capture.pPin->hwLatency = hwFifoLatency;
+
+                    /* Add HW latency into total input latency */
+                    stream->streamRepresentation.streamInfo.inputLatency += ((hwFifoLatency / stream->capture.bytesPerFrame) / sampleRate);
+                }
+                else
+                {
+                    PA_DEBUG(("Failed to get size of FIFO hardware buffer (is set to zero)\n"));
+                    stream->capture.pPin->hwLatency = 0;
+                }
+            }
+            break;
+        default:
+            /* Undefined wave type!! */
+            assert(0);
+            result = paInternalError;
+            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
+            goto error;
+        }
+    }
+    else 
+    {
+        stream->capture.hostBuffer = 0;
+    }
+
+    if (stream->userOutputChannels > 0)
+    {
+        stream->streamRepresentation.streamInfo.outputLatency = stream->render.framesPerBuffer / sampleRate;
+
+        switch (stream->render.pPin->parentFilter->devInfo.streamingType)
+        {
+        case Type_kWaveCyclic:
+            {
+                unsigned size = stream->render.noOfPackets * stream->render.framesPerBuffer * stream->render.bytesPerFrame;
+                /* Allocate output device buffer */
+                stream->render.hostBuffer = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, size);
+                PA_DEBUG(("Output buffer allocated (size = %u)\n", size));
+                if( !stream->render.hostBuffer )
+                {
+                    PA_DEBUG(("Cannot allocate host output buffer!\n"));
+                    PaWinWDM_SetLastErrorInfo(paInsufficientMemory, "Failed to allocate output buffer");
+                    result = paInsufficientMemory;
+                    goto error;
+                }
+                stream->render.hostBufferSize = size;
+                PA_DEBUG(("Output buffer start = %p (size=%u)\n",stream->render.hostBuffer, stream->render.hostBufferSize));
+
+                stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveCyclic;
+                stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveCyclic;
+            }
+            break;
+        case Type_kWaveRT:
+            {
+                const DWORD dwTotalSize = 2 * stream->render.framesPerBuffer * stream->render.bytesPerFrame;
+                DWORD dwRequestedSize = dwTotalSize;
+                BOOL bCallMemoryBarrier = FALSE;
+                ULONG hwFifoLatency = 0;
+                ULONG dummy;
+                result = PinGetBuffer(stream->render.pPin, (void**)&stream->render.hostBuffer, &dwRequestedSize, &bCallMemoryBarrier);
+                if (!result) 
+                {
+                    PA_DEBUG(("Output buffer start = %p, size = %u, membarrier = %u\n", stream->render.hostBuffer, dwRequestedSize, bCallMemoryBarrier));
+                    if (dwRequestedSize != dwTotalSize)
+                    {
+                        PA_DEBUG(("Buffer length changed by driver from %u to %u !\n", dwTotalSize, dwRequestedSize));
+                        /* Recalculate to what the driver has given us */
+                        stream->render.framesPerBuffer = dwRequestedSize / (2 * stream->render.bytesPerFrame);
+                    }
+                    stream->render.hostBufferSize = dwRequestedSize;
+
+                    if (stream->render.pPin->pinKsSubType == SubType_kPolled)
+                    {
+                        stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveRTPolled;
+                        stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveRTPolled;
+                    }
+                    else
+                    {
+                        stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveRTEvent;
+                        stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveRTEvent;
+                    }
+
+                    stream->render.pPin->fnMemBarrier = bCallMemoryBarrier ? MemoryBarrierWrite : MemoryBarrierDummy;
+                }
+                else 
+                {
+                    PA_DEBUG(("Failed to get output buffer (with notification)\n"));
+                    PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to get output buffer (with notification)");
+                    result = paUnanticipatedHostError;
+                    goto error;
+                }
+
+                /* Get latency */
+                result = PinGetHwLatency(stream->render.pPin, &hwFifoLatency, &dummy, &dummy);
+                if (result == paNoError)
+                {
+                    stream->render.pPin->hwLatency = hwFifoLatency;
+
+                    /* Add HW latency into total output latency */
+                    stream->streamRepresentation.streamInfo.outputLatency += ((hwFifoLatency / stream->render.bytesPerFrame) / sampleRate);
+                }
+                else
+                {
+                    PA_DEBUG(("Failed to get size of FIFO hardware buffer (is set to zero)\n"));
+                    stream->render.pPin->hwLatency = 0;
                 }
             }
+            break;
+        default:
+            /* Undefined wave type!! */
+            assert(0);
+            result = paInternalError;
+            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
+            goto error;
+        }
+    }
+    else 
+    {
+        stream->render.hostBuffer = 0;
+    }
+
+    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+    PA_DEBUG(("BytesPerInputFrame = %d\n",stream->capture.bytesPerFrame));
+    PA_DEBUG(("BytesPerOutputFrame = %d\n",stream->render.bytesPerFrame));
+
+    /* memset(stream->hostBuffer,0,size); */
+
+    /* Abort */
+    stream->eventAbort          = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (stream->eventAbort == 0)
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+    stream->eventStreamStart[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (stream->eventStreamStart[0] == 0)
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+    stream->eventStreamStart[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (stream->eventStreamStart[1] == 0)
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    if(stream->userInputChannels > 0)
+    {
+        const unsigned bufferSizeInBytes = stream->capture.framesPerBuffer * stream->capture.bytesPerFrame;
+        const unsigned ringBufferFrameSize = NextPowerOf2( 1024 + 2 * max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer) );
+
+        stream->capture.events = (HANDLE*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->capture.noOfPackets * sizeof(HANDLE));
+        if (stream->capture.events == NULL)
+        {
+            result = paInsufficientMemory;
+            goto error;
         }
 
-        if(stream->playbackPin == NULL)
+        stream->capture.packets = (DATAPACKET*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->capture.noOfPackets * sizeof(DATAPACKET));
+        if (stream->capture.packets == NULL)
         {
+            result = paInsufficientMemory;
             goto error;
         }
 
-        switch(hostOutputSampleFormat)
+        switch(stream->capture.pPin->parentFilter->devInfo.streamingType)
         {
-            case paInt16: stream->outputSampleSize = 2; break;
-            case paInt24: stream->outputSampleSize = 3; break;
-            case paInt32:
-            case paFloat32: stream->outputSampleSize = 4; break;
+        case Type_kWaveCyclic:
+            {
+                /* WaveCyclic case */
+                unsigned i;
+                for (i = 0; i < stream->capture.noOfPackets; ++i)
+                {
+                    /* Set up the packets */
+                    DATAPACKET *p = stream->capture.packets + i;
+
+                    /* Record event */
+                    stream->capture.events[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+                    p->Signal.hEvent = stream->capture.events[i];
+                    p->Header.Data = stream->capture.hostBuffer + (i*bufferSizeInBytes);
+                    p->Header.FrameExtent = bufferSizeInBytes;
+                    p->Header.DataUsed = 0;
+                    p->Header.Size = sizeof(p->Header);
+                    p->Header.PresentationTime.Numerator = 1;
+                    p->Header.PresentationTime.Denominator = 1;
+                }
+            }
+            break;
+        case Type_kWaveRT:
+            {
+                /* Set up the "packets" */
+                DATAPACKET *p = stream->capture.packets + 0;
+
+                /* Record event: WaveRT has a single event for 2 notification per buffer */
+                stream->capture.events[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+                p->Header.Data = stream->capture.hostBuffer;
+                p->Header.FrameExtent = bufferSizeInBytes;
+                p->Header.DataUsed = 0;
+                p->Header.Size = sizeof(p->Header);
+                p->Header.PresentationTime.Numerator = 1;
+                p->Header.PresentationTime.Denominator = 1;
+
+                ++p;
+                p->Header.Data = stream->capture.hostBuffer + bufferSizeInBytes;
+                p->Header.FrameExtent = bufferSizeInBytes;
+                p->Header.DataUsed = 0;
+                p->Header.Size = sizeof(p->Header);
+                p->Header.PresentationTime.Numerator = 1;
+                p->Header.PresentationTime.Denominator = 1;
+
+                if (stream->capture.pPin->pinKsSubType == SubType_kNotification)
+                {
+                    result = PinRegisterNotificationHandle(stream->capture.pPin, stream->capture.events[0]);
+
+                    if (result != paNoError)
+                    {
+                        PA_DEBUG(("Failed to register capture notification handle\n"));
+                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to register capture notification handle");
+                        result = paUnanticipatedHostError;
+                        goto error;
+                    }
+                }
+
+                result = PinRegisterPositionRegister(stream->capture.pPin);
+
+                if (result != paNoError)
+                {
+                    unsigned long pos = 0xdeadc0de;
+                    PA_DEBUG(("Failed to register capture position register, using PinGetAudioPositionViaIOCTL\n"));
+                    stream->capture.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTL;
+                    /* Test position function */
+                    result = (stream->capture.pPin->fnAudioPosition)(stream->capture.pPin, &pos);
+                    if (result != paNoError || pos != 0x0)
+                    {
+                        PA_DEBUG(("Failed to read capture position register (IOCTL)\n"));
+                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to read capture position register (IOCTL)");
+                        result = paUnanticipatedHostError;
+                        goto error;
+                    }                
+                }
+                else
+                {
+                    stream->capture.pPin->fnAudioPosition = PinGetAudioPositionDirect;
+                }
+            }
+            break;
+        default:
+            /* Undefined wave type!! */
+            assert(0);
+            result = paInternalError;
+            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
+            goto error;
         }
 
-        stream->playbackPin->frameSize /= stream->bytesPerOutputFrame;
-        PA_DEBUG(("Pin output frames: %d\n",stream->playbackPin->frameSize));
+        /* Setup the input ring buffer here */
+        stream->ringBufferData = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, ringBufferFrameSize * stream->capture.bytesPerFrame);
+        if (stream->ringBufferData == NULL)
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+        PaUtil_InitializeRingBuffer(&stream->ringBuffer, stream->capture.bytesPerFrame, ringBufferFrameSize, stream->ringBufferData);
     }
-    else
+    if(stream->userOutputChannels > 0)
     {
-        stream->playbackPin = NULL;
-        stream->bytesPerOutputFrame = 0;
+        const unsigned bufferSizeInBytes = stream->render.framesPerBuffer * stream->render.bytesPerFrame;
+
+        stream->render.events = (HANDLE*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->render.noOfPackets * sizeof(HANDLE));
+        if (stream->render.events == NULL)
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+        stream->render.packets = (DATAPACKET*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->render.noOfPackets * sizeof(DATAPACKET));
+        if (stream->render.packets == NULL)
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+        switch(stream->render.pPin->parentFilter->devInfo.streamingType)
+        {
+        case Type_kWaveCyclic:
+            {
+                /* WaveCyclic case */
+                unsigned i;
+                for (i = 0; i < stream->render.noOfPackets; ++i)
+                {
+                    /* Set up the packets */
+                    DATAPACKET *p = stream->render.packets + i;
+
+                    /* Playback event */
+                    stream->render.events[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+                    /* In this case, we just use the packets as ptr to the device buffer */
+                    p->Signal.hEvent = stream->render.events[i];
+                    p->Header.Data = stream->render.hostBuffer + (i*bufferSizeInBytes);
+                    p->Header.FrameExtent = bufferSizeInBytes;
+                    p->Header.DataUsed = bufferSizeInBytes;
+                    p->Header.Size = sizeof(p->Header);
+                    p->Header.PresentationTime.Numerator = 1;
+                    p->Header.PresentationTime.Denominator = 1;
+                }
+            }
+            break;
+        case Type_kWaveRT:
+            {
+                /* WaveRT case */
+
+                /* Set up the "packets" */
+                DATAPACKET *p = stream->render.packets;
+
+                /* The only playback event */
+                stream->render.events[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+                /* In this case, we just use the packets as ptr to the device buffer */
+                p->Header.Data = stream->render.hostBuffer;
+                p->Header.FrameExtent = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
+                p->Header.DataUsed = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
+                p->Header.Size = sizeof(p->Header);
+                p->Header.PresentationTime.Numerator = 1;
+                p->Header.PresentationTime.Denominator = 1;
+
+                ++p;
+                p->Header.Data = stream->render.hostBuffer + stream->render.framesPerBuffer*stream->render.bytesPerFrame;
+                p->Header.FrameExtent = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
+                p->Header.DataUsed = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
+                p->Header.Size = sizeof(p->Header);
+                p->Header.PresentationTime.Numerator = 1;
+                p->Header.PresentationTime.Denominator = 1;
+
+                if (stream->render.pPin->pinKsSubType == SubType_kNotification)
+                {
+                    result = PinRegisterNotificationHandle(stream->render.pPin, stream->render.events[0]);
+
+                    if (result != paNoError)
+                    {
+                        PA_DEBUG(("Failed to register rendering notification handle\n"));
+                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to register rendering notification handle");
+                        result = paUnanticipatedHostError;
+                        goto error;
+                    }
+                }
+
+                result = PinRegisterPositionRegister(stream->render.pPin);
+
+                if (result != paNoError)
+                {
+                    unsigned long pos = 0xdeadc0de;
+                    PA_DEBUG(("Failed to register rendering position register, using PinGetAudioPositionViaIOCTL\n"));
+                    stream->render.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTL;
+                    /* Test position function */
+                    result = (stream->render.pPin->fnAudioPosition)(stream->render.pPin, &pos);
+                    if (result != paNoError || pos != 0x0)
+                    {
+                        PA_DEBUG(("Failed to read render position register (IOCTL)\n"));
+                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to read render position register (IOCTL)");
+                        result = paUnanticipatedHostError;
+                        goto error;
+                    }
+                }
+                else
+                {
+                    stream->render.pPin->fnAudioPosition = PinGetAudioPositionDirect;
+                }
+            }
+            break;
+        default:
+            /* Undefined wave type!! */
+            assert(0);
+            result = paInternalError;
+            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
+            goto error;
+        }
     }
 
-    /* Calculate the framesPerHostXxxxBuffer size based upon the suggested latency values */
+    stream->streamStarted = 0;
+    stream->streamActive = 0;
+    stream->streamStop = 0;
+    stream->streamAbort = 0;
+    stream->streamFlags = streamFlags;
+    stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
 
-    /* Record the buffer length */
-    if(inputParameters)
+    /* Increase ref count on filters in use, so that a CommitDeviceInfos won't delete them */
+    if (stream->capture.pPin != 0)
     {
-        /* Calculate the frames from the user's value - add a bit to round up */
-            stream->framesPerHostIBuffer = (unsigned long)((inputParameters->suggestedLatency*sampleRate)+0.0001);
-        if(stream->framesPerHostIBuffer > (unsigned long)sampleRate)
-        { /* Upper limit is 1 second */
-              stream->framesPerHostIBuffer = (unsigned long)sampleRate;
-        }
-        else if(stream->framesPerHostIBuffer < stream->recordingPin->frameSize)
-        {
-              stream->framesPerHostIBuffer = stream->recordingPin->frameSize;
-        }
-        PA_DEBUG(("Input frames chosen:%ld\n",stream->framesPerHostIBuffer));
+        FilterAddRef(stream->capture.pPin->parentFilter);
+    }
+    if (stream->render.pPin != 0)
+    {
+        FilterAddRef(stream->render.pPin->parentFilter);
     }
 
-    if(outputParameters)
+    /* Ok, now update our host API specific stream info */
+    if (stream->userInputChannels)
     {
-        /* Calculate the frames from the user's value - add a bit to round up */
-        stream->framesPerHostOBuffer = (unsigned long)((outputParameters->suggestedLatency*sampleRate)+0.0001);
-        if(stream->framesPerHostOBuffer > (unsigned long)sampleRate)
-        { /* Upper limit is 1 second */
-                  stream->framesPerHostOBuffer = (unsigned long)sampleRate;
-        }
-        else if(stream->framesPerHostOBuffer < stream->playbackPin->frameSize)
+        PaWinWdmDeviceInfo *pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device];
+
+        stream->hostApiStreamInfo.input.device = Pa_HostApiDeviceIndexToDeviceIndex(Pa_HostApiTypeIdToHostApiIndex(paWDMKS), inputParameters->device);
+        stream->hostApiStreamInfo.input.channels = stream->deviceInputChannels;
+        stream->hostApiStreamInfo.input.muxNodeId = -1;
+        if (stream->capture.pPin->inputs)
         {
-              stream->framesPerHostOBuffer = stream->playbackPin->frameSize;
+            stream->hostApiStreamInfo.input.muxNodeId = stream->capture.pPin->inputs[pDeviceInfo->muxPosition]->muxNodeId;
         }
-        PA_DEBUG(("Output frames chosen:%ld\n",stream->framesPerHostOBuffer));
+        stream->hostApiStreamInfo.input.endpointPinId = pDeviceInfo->endpointPinId;
+        stream->hostApiStreamInfo.input.framesPerHostBuffer = stream->capture.framesPerBuffer;
+        stream->hostApiStreamInfo.input.streamingSubType = stream->capture.pPin->pinKsSubType;
     }
-
-    /* Host buffer size is bounded to the largest of the input and output
-    frame sizes */
-
-    result =  PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
-              stream->userInputChannels, inputSampleFormat, hostInputSampleFormat,
-              stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat,
-              sampleRate, streamFlags, framesPerBuffer,
-              max(stream->framesPerHostOBuffer,stream->framesPerHostIBuffer),
-              paUtilBoundedHostBufferSize,
-              streamCallback, userData );
-    if( result != paNoError )
-        goto error;
-
-    stream->streamRepresentation.streamInfo.inputLatency =
-            ((double)stream->framesPerHostIBuffer) / sampleRate;
-    stream->streamRepresentation.streamInfo.outputLatency =
-            ((double)stream->framesPerHostOBuffer) / sampleRate;
-    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
-
-      PA_DEBUG(("BytesPerInputFrame = %d\n",stream->bytesPerInputFrame));
-      PA_DEBUG(("BytesPerOutputFrame = %d\n",stream->bytesPerOutputFrame));
-
-    /* Allocate all the buffers for host I/O */
-    size = 2 * (stream->framesPerHostIBuffer*stream->bytesPerInputFrame +  stream->framesPerHostOBuffer*stream->bytesPerOutputFrame);
-    PA_DEBUG(("Buffer size = %d\n",size));
-    stream->hostBuffer = (char*)PaUtil_AllocateMemory(size);
-    PA_DEBUG(("Buffer allocated\n"));
-    if( !stream->hostBuffer )
+    else
     {
-        PA_DEBUG(("Cannot allocate host buffer!\n"));
-        result = paInsufficientMemory;
-        goto error;
+        stream->hostApiStreamInfo.input.device = paNoDevice;
     }
-    PA_DEBUG(("Buffer start = %p\n",stream->hostBuffer));
-    /* memset(stream->hostBuffer,0,size); */
-
-    /* Set up the packets */
-    stream->events[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
-    ResetEvent(stream->events[0]); /* Record buffer 1 */
-    stream->events[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
-    ResetEvent(stream->events[1]); /* Record buffer 2 */
-    stream->events[2] = CreateEvent(NULL, FALSE, FALSE, NULL);
-    ResetEvent(stream->events[2]); /* Play buffer 1 */
-    stream->events[3] = CreateEvent(NULL, FALSE, FALSE, NULL);
-    ResetEvent(stream->events[3]); /* Play buffer 2 */
-    stream->events[4] = CreateEvent(NULL, FALSE, FALSE, NULL);
-    ResetEvent(stream->events[4]); /* Abort event */
-    if(stream->userInputChannels > 0)
+    if (stream->userOutputChannels)
     {
-        DATAPACKET *p = &(stream->packets[0]);
-        p->Signal.hEvent = stream->events[0];
-        p->Header.Data = stream->hostBuffer;
-        p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
-        p->Header.DataUsed = 0;
-        p->Header.Size = sizeof(p->Header);
-        p->Header.PresentationTime.Numerator = 1;
-        p->Header.PresentationTime.Denominator = 1;
-
-        p = &(stream->packets[1]);
-        p->Signal.hEvent = stream->events[1];
-        p->Header.Data = stream->hostBuffer + stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
-        p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
-        p->Header.DataUsed = 0;
-        p->Header.Size = sizeof(p->Header);
-        p->Header.PresentationTime.Numerator = 1;
-        p->Header.PresentationTime.Denominator = 1;
+        stream->hostApiStreamInfo.output.device = Pa_HostApiDeviceIndexToDeviceIndex(Pa_HostApiTypeIdToHostApiIndex(paWDMKS), outputParameters->device);
+        stream->hostApiStreamInfo.output.channels = stream->deviceOutputChannels;
+        stream->hostApiStreamInfo.output.framesPerHostBuffer = stream->render.framesPerBuffer;
+        stream->hostApiStreamInfo.output.endpointPinId = stream->render.pPin->endpointPinId;
+        stream->hostApiStreamInfo.output.streamingSubType = stream->render.pPin->pinKsSubType;
     }
-    if(stream->userOutputChannels > 0)
+    else
     {
-        DATAPACKET *p = &(stream->packets[2]);
-        p->Signal.hEvent = stream->events[2];
-        p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
-        p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
-        p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
-        p->Header.Size = sizeof(p->Header);
-        p->Header.PresentationTime.Numerator = 1;
-        p->Header.PresentationTime.Denominator = 1;
-    
-        p = &(stream->packets[3]);
-        p->Signal.hEvent = stream->events[3];
-        p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame + stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
-        p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
-        p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
-        p->Header.Size = sizeof(p->Header);
-        p->Header.PresentationTime.Numerator = 1;
-        p->Header.PresentationTime.Denominator = 1;
+        stream->hostApiStreamInfo.output.device = paNoDevice;
     }
-
-    stream->streamStarted = 0;
-    stream->streamActive = 0;
-    stream->streamStop = 0;
-    stream->streamAbort = 0;
-    stream->streamFlags = streamFlags;
-    stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
+    /*stream->streamRepresentation.streamInfo.hostApiTypeId = paWDMKS;
+    stream->streamRepresentation.streamInfo.hostApiSpecificStreamInfo = &stream->hostApiStreamInfo;*/
+    stream->streamRepresentation.streamInfo.structVersion = 2;
 
     *s = (PaStream*)stream;
 
     PA_LOGL_;
     return result;
 
+occupied:
+    /* Ok, someone else is hogging the pin, bail out */
+    assert (result == paDeviceUnavailable);
+    PaWinWDM_SetLastErrorInfo(result, "Device is occupied");
+
 error:
-    size = 5;
-    while(size--)
+    PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+
+    CloseStreamEvents(stream);
+
+    if (stream->allocGroup)
     {
-        if(stream->events[size] != NULL)
-        {
-            CloseHandle(stream->events[size]);
-            stream->events[size] = NULL;
-        }
+        PaUtil_FreeAllAllocations(stream->allocGroup);
+        PaUtil_DestroyAllocationGroup(stream->allocGroup);
+        stream->allocGroup = 0;
     }
-    if(stream->hostBuffer)
-        PaUtil_FreeMemory( stream->hostBuffer );
 
-    if(stream->playbackPin)
-        PinClose(stream->playbackPin);
-    if(stream->recordingPin)
-        PinClose(stream->recordingPin);
+    if(stream->render.pPin)
+        PinClose(stream->render.pPin);
+    if(stream->capture.pPin)
+        PinClose(stream->capture.pPin);
 
-    if( stream )
-        PaUtil_FreeMemory( stream );
+    PaUtil_FreeMemory( stream );
 
     PA_LOGL_;
     return result;
 }
 
 /*
-    When CloseStream() is called, the multi-api layer ensures that
-    the stream has already been stopped or aborted.
+When CloseStream() is called, the multi-api layer ensures that
+the stream has already been stopped or aborted.
 */
 static PaError CloseStream( PaStream* s )
 {
     PaError result = paNoError;
     PaWinWdmStream *stream = (PaWinWdmStream*)s;
-    int size;
 
     PA_LOGE_;
 
@@ -2619,22 +5211,33 @@ static PaError CloseStream( PaStream* s )
 
     PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
     PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
-    size = 5;
-    while(size--)
+
+    CloseStreamEvents(stream);
+
+    if (stream->allocGroup)
     {
-        if(stream->events[size] != NULL)
-        {
-            CloseHandle(stream->events[size]);
-            stream->events[size] = NULL;
-        }
+        PaUtil_FreeAllAllocations(stream->allocGroup);
+        PaUtil_DestroyAllocationGroup(stream->allocGroup);
+        stream->allocGroup = 0;
+    }
+
+    if(stream->render.pPin)
+    {
+        PinClose(stream->render.pPin);
+    }
+    if(stream->capture.pPin)
+    {
+        PinClose(stream->capture.pPin);
     }
-    if(stream->hostBuffer)
-        PaUtil_FreeMemory( stream->hostBuffer );
 
-    if(stream->playbackPin)
-        PinClose(stream->playbackPin);
-    if(stream->recordingPin)
-        PinClose(stream->recordingPin);
+    if (stream->render.pPin)
+    {
+        FilterFree(stream->render.pPin->parentFilter);
+    }
+    if (stream->capture.pPin)
+    {
+        FilterFree(stream->capture.pPin->parentFilter);
+    }
 
     PaUtil_FreeMemory( stream );
 
@@ -2645,25 +5248,57 @@ static PaError CloseStream( PaStream* s )
 /*
 Write the supplied packet to the pin
 Asynchronous
-Should return false on success
+Should return paNoError on success
 */
-static BOOL PinWrite(HANDLE h, DATAPACKET* p)
+static PaError PinWrite(HANDLE h, DATAPACKET* p)
 {
+    PaError result = paNoError;
     unsigned long cbReturned = 0;
-    return DeviceIoControl(h,IOCTL_KS_WRITE_STREAM,NULL,0,
-                            &p->Header,p->Header.Size,&cbReturned,&p->Signal);
+    BOOL fRes = DeviceIoControl(h,
+        IOCTL_KS_WRITE_STREAM,
+        NULL,
+        0,
+        &p->Header,
+        p->Header.Size,
+        &cbReturned,
+        &p->Signal);
+    if (!fRes)
+    {
+        unsigned long error = GetLastError();
+        if (error != ERROR_IO_PENDING)
+        {
+            result = paInternalError;
+        }
+    }
+    return result;
 }
 
 /*
 Read to the supplied packet from the pin
 Asynchronous
-Should return false on success
+Should return paNoError on success
 */
-static BOOL PinRead(HANDLE h, DATAPACKET* p)
+static PaError PinRead(HANDLE h, DATAPACKET* p)
 {
+    PaError result = paNoError;
     unsigned long cbReturned = 0;
-    return DeviceIoControl(h,IOCTL_KS_READ_STREAM,NULL,0,
-                            &p->Header,p->Header.Size,&cbReturned,&p->Signal);
+    BOOL fRes = DeviceIoControl(h,
+        IOCTL_KS_READ_STREAM,
+        NULL,
+        0,
+        &p->Header,
+        p->Header.Size,
+        &cbReturned,
+        &p->Signal);
+    if (!fRes)
+    {
+        unsigned long error = GetLastError();
+        if (error != ERROR_IO_PENDING)
+        {
+            result = paInternalError;
+        }
+    }
+    return result;
 }
 
 /*
@@ -2729,350 +5364,767 @@ static void DuplicateFirstChannelInt32(void* buffer, int channels, int samples)
     }
 }
 
-static DWORD WINAPI ProcessingThread(LPVOID pParam)
+/*
+Increase the priority of the calling thread to RT 
+*/
+static HANDLE BumpThreadPriority() 
+{
+    HANDLE hThread = GetCurrentThread();
+    DWORD dwTask = 0;
+    HANDLE hAVRT = NULL;
+
+    /* If we have access to AVRT.DLL (Vista and later), use it */
+    if (paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics != NULL) 
+    {
+        hAVRT = paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics("Pro Audio", &dwTask);
+        if (hAVRT != NULL && hAVRT != INVALID_HANDLE_VALUE) 
+        {
+            BOOL bret = paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority(hAVRT, PA_AVRT_PRIORITY_CRITICAL);
+            if (!bret)
+            {
+                PA_DEBUG(("Set mm thread prio to critical failed!\n"));
+            }
+            else
+            {
+                return hAVRT;
+            }
+        }
+        else
+        {
+            PA_DEBUG(("Set mm thread characteristic to 'Pro Audio' failed, reverting to SetThreadPriority\n"));
+        }
+    }
+
+    /* For XP and earlier, or if AvSetMmThreadCharacteristics fails (MMCSS disabled ?) */
+    if (timeBeginPeriod(1) != TIMERR_NOERROR) {
+        PA_DEBUG(("timeBeginPeriod(1) failed!\n"));
+    }
+
+    if (!SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL)) {
+        PA_DEBUG(("SetThreadPriority failed!\n"));
+    }
+
+    return hAVRT;
+}
+
+/*
+Decrease the priority of the calling thread to normal
+*/
+static void DropThreadPriority(HANDLE hAVRT)
+{
+    HANDLE hThread = GetCurrentThread();
+
+    if (hAVRT != NULL) 
+    {
+        paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority(hAVRT, PA_AVRT_PRIORITY_NORMAL);
+        paWinWDMKSAvRtEntryPoints.AvRevertMmThreadCharacteristics(hAVRT);
+        return;
+    }
+
+    SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL);
+    timeEndPeriod(1);
+}
+
+static PaError PreparePinForStart(PaWinWdmPin* pin)
+{
+    PaError result;
+    result = PinSetState(pin, KSSTATE_ACQUIRE);
+    if (result != paNoError)
+    {
+        goto error;
+    }
+    result = PinSetState(pin, KSSTATE_PAUSE);
+    if (result != paNoError)
+    {
+        goto error;
+    }
+    return result;
+
+error:
+    PinSetState(pin, KSSTATE_STOP);
+    return result;
+}
+
+static PaError PreparePinsForStart(PaProcessThreadInfo* pInfo)
+{
+    PaError result = paNoError;
+    /* Submit buffers */
+    if (pInfo->stream->capture.pPin)
+    {
+        if ((result = PreparePinForStart(pInfo->stream->capture.pPin)) != paNoError)
+        {
+            goto error;
+        }
+
+        if (pInfo->stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
+        {
+            unsigned i;
+            for(i=0; i < pInfo->stream->capture.noOfPackets; ++i)
+            {
+                if ((result = PinRead(pInfo->stream->capture.pPin->handle, pInfo->stream->capture.packets + i)) != paNoError)
+                {
+                    goto error;
+                }
+                ++pInfo->pending;
+            }
+        }
+        else
+        {
+            pInfo->pending = 2;
+        }
+    }
+
+    if(pInfo->stream->render.pPin)
+    {
+        if ((result = PreparePinForStart(pInfo->stream->render.pPin)) != paNoError)
+        {
+            goto error;
+        }
+
+        pInfo->priming += pInfo->stream->render.noOfPackets;
+        ++pInfo->pending;
+        SetEvent(pInfo->stream->render.events[0]);
+        if (pInfo->stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic) 
+        {
+            unsigned i;
+            for(i=1; i < pInfo->stream->render.noOfPackets; ++i)
+            {
+                SetEvent(pInfo->stream->render.events[i]);
+                ++pInfo->pending;
+            }
+        }
+    }
+
+error:
+    PA_DEBUG(("PreparePinsForStart = %d\n", result));
+    return result;
+}
+
+static PaError StartPin(PaWinWdmPin* pin)
+{
+    return PinSetState(pin, KSSTATE_RUN);
+}
+
+static PaError StartPins(PaProcessThreadInfo* pInfo)
+{
+    PaError result = paNoError;
+    /* Start the pins as synced as possible */
+    if (pInfo->stream->capture.pPin)
+    {
+        result = StartPin(pInfo->stream->capture.pPin);
+    }
+    if(pInfo->stream->render.pPin)
+    {
+        result = StartPin(pInfo->stream->render.pPin);
+    }
+    PA_DEBUG(("StartPins = %d\n", result));
+    return result;
+}
+
+
+static PaError StopPin(PaWinWdmPin* pin)
+{
+    PinSetState(pin, KSSTATE_PAUSE);
+    PinSetState(pin, KSSTATE_STOP);
+    return paNoError;
+}
+
+
+static PaError StopPins(PaProcessThreadInfo* pInfo)
+{
+    PaError result = paNoError;
+    if(pInfo->stream->render.pPin)
+    {
+        StopPin(pInfo->stream->render.pPin);
+    }
+    if(pInfo->stream->capture.pPin)
+    {
+        StopPin(pInfo->stream->capture.pPin);
+    }
+    return result;
+}
+
+typedef void (*TSetInputFrameCount)(PaUtilBufferProcessor*, unsigned long);
+typedef void (*TSetInputChannel)(PaUtilBufferProcessor*, unsigned int, void *, unsigned int);
+static const TSetInputFrameCount fnSetInputFrameCount[2] = { PaUtil_SetInputFrameCount, PaUtil_Set2ndInputFrameCount };
+static const TSetInputChannel fnSetInputChannel[2] = { PaUtil_SetInputChannel, PaUtil_Set2ndInputChannel };
+
+static PaError PaDoProcessing(PaProcessThreadInfo* pInfo)
+{
+    PaError result = paNoError;
+    int i, framesProcessed = 0, doChannelCopy = 0;
+    ring_buffer_size_t inputFramesAvailable = PaUtil_GetRingBufferReadAvailable(&pInfo->stream->ringBuffer);
+
+    /* Do necessary buffer processing (which will invoke user callback if necessary) */
+    if (pInfo->cbResult == paContinue &&
+        (pInfo->renderHead != pInfo->renderTail || inputFramesAvailable))
+    {
+        unsigned processFullDuplex = pInfo->stream->capture.pPin && pInfo->stream->render.pPin && (!pInfo->priming);
+
+        PA_HP_TRACE((pInfo->stream->hLog, "DoProcessing: InputFrames=%u", inputFramesAvailable));
+
+        PaUtil_BeginCpuLoadMeasurement( &pInfo->stream->cpuLoadMeasurer );
+
+        pInfo->ti.currentTime = PaUtil_GetTime();
+
+        PaUtil_BeginBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->ti, pInfo->underover);
+        pInfo->underover = 0; /* Reset the (under|over)flow status */
+
+        if (pInfo->renderTail != pInfo->renderHead)
+        {
+            DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet;
+
+            assert(packet != 0);
+            assert(packet->Header.Data != 0);
+
+            PaUtil_SetOutputFrameCount(&pInfo->stream->bufferProcessor, pInfo->stream->render.framesPerBuffer);
+
+            for(i=0;i<pInfo->stream->userOutputChannels;i++)
+            {
+                /* Only write the user output channels. Leave the rest blank */
+                PaUtil_SetOutputChannel(&pInfo->stream->bufferProcessor,
+                    i,
+                    ((unsigned char*)(packet->Header.Data))+(i*pInfo->stream->render.bytesPerSample),
+                    pInfo->stream->deviceOutputChannels);
+            }
+
+            /* We will do a copy to the other channels after the data has been written */
+            doChannelCopy = ( pInfo->stream->userOutputChannels == 1 );
+        }
+
+        if (inputFramesAvailable && (!pInfo->stream->userOutputChannels || inputFramesAvailable >= (int)pInfo->stream->render.framesPerBuffer))
+        {
+            unsigned wrapCntr = 0;
+            void* data[2] = {0};
+            ring_buffer_size_t size[2] = {0};
+
+            /* If full-duplex, we just extract output buffer number of frames */
+            if (pInfo->stream->userOutputChannels)
+            {
+                inputFramesAvailable = min(inputFramesAvailable, (int)pInfo->stream->render.framesPerBuffer);
+            }
+
+            inputFramesAvailable = PaUtil_GetRingBufferReadRegions(&pInfo->stream->ringBuffer,
+                inputFramesAvailable,
+                &data[0],
+                &size[0],
+                &data[1],
+                &size[1]);
+
+            for (wrapCntr = 0; wrapCntr < 2; ++wrapCntr)
+            {
+                if (size[wrapCntr] == 0)
+                    break;
+
+                fnSetInputFrameCount[wrapCntr](&pInfo->stream->bufferProcessor, size[wrapCntr]);
+                for(i=0;i<pInfo->stream->userInputChannels;i++)
+                {
+                    /* Only read as many channels as the user wants */
+                    fnSetInputChannel[wrapCntr](&pInfo->stream->bufferProcessor,
+                        i,
+                        ((unsigned char*)(data[wrapCntr]))+(i*pInfo->stream->capture.bytesPerSample),
+                        pInfo->stream->deviceInputChannels);
+                }
+            }
+        }
+        else
+        {
+            /* We haven't consumed anything from the ring buffer... */
+            inputFramesAvailable = 0;
+            /* If we have full-duplex, this is at startup, so mark no-input! */
+            if (pInfo->stream->userOutputChannels>0 && pInfo->stream->userInputChannels>0)
+            {
+                PA_HP_TRACE((pInfo->stream->hLog, "Input startup, marking no input."));
+                PaUtil_SetNoInput(&pInfo->stream->bufferProcessor);
+            }
+        }
+
+        if (processFullDuplex) /* full duplex */
+        {
+            /* Only call the EndBufferProcessing function when the total input frames == total output frames */
+            const unsigned long totalInputFrameCount = pInfo->stream->bufferProcessor.hostInputFrameCount[0] + pInfo->stream->bufferProcessor.hostInputFrameCount[1];
+            const unsigned long totalOutputFrameCount = pInfo->stream->bufferProcessor.hostOutputFrameCount[0] + pInfo->stream->bufferProcessor.hostOutputFrameCount[1];
+
+            if(totalInputFrameCount == totalOutputFrameCount && totalOutputFrameCount != 0)
+            {
+                framesProcessed = PaUtil_EndBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->cbResult);
+            }
+            else
+            {
+                framesProcessed = 0;
+            }
+        }
+        else 
+        {
+            framesProcessed = PaUtil_EndBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->cbResult);
+        }
+
+        PA_HP_TRACE((pInfo->stream->hLog, "Frames processed: %u %s", framesProcessed, (pInfo->priming ? "(priming)":"")));
+
+        if( doChannelCopy )
+        {
+            DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet;
+            /* Copy the first output channel to the other channels */
+            switch (pInfo->stream->render.bytesPerSample)
+            {
+            case 2:
+                DuplicateFirstChannelInt16(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer);
+                break;
+            case 3:
+                DuplicateFirstChannelInt24(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer);
+                break;
+            case 4:
+                DuplicateFirstChannelInt32(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer);
+                break;
+            default:
+                assert(0); /* Unsupported format! */
+                break;
+            }
+        }
+        PaUtil_EndCpuLoadMeasurement( &pInfo->stream->cpuLoadMeasurer, framesProcessed );
+
+        if (inputFramesAvailable)
+        {
+            PaUtil_AdvanceRingBufferReadIndex(&pInfo->stream->ringBuffer, inputFramesAvailable);
+        }
+
+        if (pInfo->renderTail != pInfo->renderHead)
+        {
+            if (!pInfo->stream->streamStop)
+            {
+                result = pInfo->stream->render.pPin->fnSubmitHandler(pInfo, pInfo->renderTail);
+                if (result != paNoError)
+                {
+                    PA_HP_TRACE((pInfo->stream->hLog, "Capture submit handler failed with result %d", result));
+                    return result;
+                }
+            }
+            pInfo->renderTail++;
+            if (!pInfo->pinsStarted && pInfo->priming == 0)
+            {
+                /* We start the pins here to allow "prime time" */
+                if ((result = StartPins(pInfo)) == paNoError)
+                {
+                    PA_HP_TRACE((pInfo->stream->hLog, "Starting pins!"));
+                    pInfo->pinsStarted = 1;
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+static VOID CALLBACK TimerAPCWaveRTPolledMode(
+    LPVOID lpArgToCompletionRoutine,
+    DWORD dwTimerLowValue,
+    DWORD dwTimerHighValue)
 {
-    PaWinWdmStream *stream = (PaWinWdmStream*)pParam;
-    PaStreamCallbackTimeInfo ti;
-    int cbResult = paContinue;
-    int inbuf = 0;
-    int outbuf = 0;
-    int pending = 0;
-    PaError result;
-    unsigned long wait;
-    unsigned long eventSignaled;
-    int fillPlaybuf = 0;
-    int emptyRecordbuf = 0;
-    int framesProcessed;
-    unsigned long timeout;
-    int i;
-    int doChannelCopy;
-    int priming = 0;
-    PaStreamCallbackFlags underover = 0;
+    HANDLE* pHandles = (HANDLE*)lpArgToCompletionRoutine;
+    if (pHandles[0]) SetEvent(pHandles[0]);
+    if (pHandles[1]) SetEvent(pHandles[1]);
+}
+
+static DWORD GetCurrentTimeInMillisecs()
+{
+    return timeGetTime();
+}
+
+PA_THREAD_FUNC ProcessingThread(void* pParam)
+{
+    PaError result = paNoError;
+    HANDLE hAVRT = NULL;
+    HANDLE hTimer = NULL;
+    HANDLE *handleArray = NULL;
+    HANDLE timerEventHandles[2] = {0};
+    unsigned noOfHandles = 0;
+    unsigned captureEvents = 0;
+    unsigned renderEvents = 0;
+    unsigned timerPeriod = 0;
+    DWORD timeStamp[2] = {0};
+
+    PaProcessThreadInfo info;
+    memset(&info, 0, sizeof(PaProcessThreadInfo));
+    info.stream = (PaWinWdmStream*)pParam;
+
+    info.stream->threadResult = paNoError;
 
     PA_LOGE_;
 
-    ti.inputBufferAdcTime = 0.0;
-    ti.currentTime = 0.0;
-    ti.outputBufferDacTime = 0.0;
+    info.ti.inputBufferAdcTime = 0.0;
+    info.ti.currentTime = 0.0;
+    info.ti.outputBufferDacTime = 0.0;
 
-    /* Get double buffering going */
+    PA_DEBUG(("In  buffer len: %.3f ms\n",(2000*info.stream->capture.framesPerBuffer) / info.stream->streamRepresentation.streamInfo.sampleRate));
+    PA_DEBUG(("Out buffer len: %.3f ms\n",(2000*info.stream->render.framesPerBuffer) / info.stream->streamRepresentation.streamInfo.sampleRate));
+    info.timeout = (DWORD)max(
+        (2000*info.stream->render.framesPerBuffer/info.stream->streamRepresentation.streamInfo.sampleRate + 0.5),
+        (2000*info.stream->capture.framesPerBuffer/info.stream->streamRepresentation.streamInfo.sampleRate + 0.5));
+    info.timeout = max(info.timeout*8, 100);
+    timerPeriod = info.timeout;
+    PA_DEBUG(("Timeout = %ld ms\n",info.timeout));
 
-    /* Submit buffers */
-    if(stream->playbackPin)
-    {
-        result = PinSetState(stream->playbackPin, KSSTATE_RUN);
+    /* Allocate handle array */
+    handleArray = (HANDLE*)PaUtil_AllocateMemory((info.stream->capture.noOfPackets + info.stream->render.noOfPackets + 1) * sizeof(HANDLE));
 
-        PA_DEBUG(("play state run = %d;",(int)result));
-        SetEvent(stream->events[outbuf+2]);
-        outbuf = (outbuf+1)&1;
-        SetEvent(stream->events[outbuf+2]);
-        outbuf = (outbuf+1)&1;
-        pending += 2;
-        priming += 4;
+    /* Setup handle array for WFMO */
+    if (info.stream->capture.pPin != 0)
+    {
+        handleArray[noOfHandles++] = info.stream->capture.events[0];
+        if (info.stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
+        {
+            unsigned i;
+            for(i=1; i < info.stream->capture.noOfPackets; ++i)
+            {
+                handleArray[noOfHandles++] = info.stream->capture.events[i];
+            }
+        }
+        captureEvents = noOfHandles;
+        renderEvents = noOfHandles;
     }
-    if(stream->recordingPin)
+
+    if (info.stream->render.pPin != 0)
     {
-        result = PinSetState(stream->recordingPin, KSSTATE_RUN);
+        handleArray[noOfHandles++] = info.stream->render.events[0];
+        if (info.stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
+        {
+            unsigned i;
+            for(i=1; i < info.stream->render.noOfPackets; ++i)
+            {
+                handleArray[noOfHandles++] = info.stream->render.events[i];
+            }
+        }
+        renderEvents = noOfHandles;
+    }
+    handleArray[noOfHandles++] = info.stream->eventAbort;
+    assert(noOfHandles <= (info.stream->capture.noOfPackets + info.stream->render.noOfPackets + 1));
 
-        PA_DEBUG(("recording state run = %d;",(int)result));
-        PinRead(stream->recordingPin->handle,&stream->packets[inbuf]);
-        inbuf = (inbuf+1)&1; /* Increment and wrap */
-        PinRead(stream->recordingPin->handle,&stream->packets[inbuf]);
-        inbuf = (inbuf+1)&1; /* Increment and wrap */
-        /* FIXME - do error checking */
-        pending += 2;
+    /* Prepare render and capture pins */
+    if ((result = PreparePinsForStart(&info)) != paNoError) 
+    {
+        PA_DEBUG(("Failed to prepare device(s)!\n"));
+        goto error;
     }
-    PA_DEBUG(("Out buffer len:%f\n",(2000*stream->framesPerHostOBuffer) / stream->streamRepresentation.streamInfo.sampleRate));
-    PA_DEBUG(("In buffer len:%f\n",(2000*stream->framesPerHostIBuffer) / stream->streamRepresentation.streamInfo.sampleRate));
-    timeout = max(
-       ((2000*(DWORD)stream->framesPerHostOBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate),
-       ((2000*(DWORD)stream->framesPerHostIBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate));
-    timeout = max(timeout,1);
-    PA_DEBUG(("Timeout = %ld\n",timeout));
 
-    while(!stream->streamAbort)
+    /* Init high speed logger */
+    if (PaUtil_InitializeHighSpeedLog(&info.stream->hLog, 1000000) != paNoError)
     {
-        fillPlaybuf = 0;
-        emptyRecordbuf = 0;
+        PA_DEBUG(("Failed to init high speed logger!\n"));
+        goto error;
+    }
+
+    /* Heighten priority here */
+    hAVRT = BumpThreadPriority();
 
-        /* Wait for next input or output buffer to be finished with*/
-        assert(pending>0);
+    /* If input only, we start the pins immediately */
+    if (info.stream->render.pPin == 0)
+    {
+        if ((result = StartPins(&info)) != paNoError)
+        {
+            PA_DEBUG(("Failed to start device(s)!\n"));
+            goto error;
+        }
+        info.pinsStarted = 1;
+    }
 
-        if(stream->streamStop)
+    /* Handle WaveRT polled mode */
+    {
+        const unsigned fs = (unsigned)info.stream->streamRepresentation.streamInfo.sampleRate;
+        if (info.stream->capture.pPin != 0 && info.stream->capture.pPin->pinKsSubType == SubType_kPolled)
         {
-            PA_DEBUG(("ss1:pending=%d ",pending));
+            timerEventHandles[0] = info.stream->capture.events[0];
+            timerPeriod = min(timerPeriod, (1000*info.stream->capture.framesPerBuffer)/fs);
         }
-        wait = WaitForMultipleObjects(5, stream->events, FALSE, 0);
-        if( wait == WAIT_TIMEOUT )
+
+        if (info.stream->render.pPin != 0 && info.stream->render.pPin->pinKsSubType == SubType_kPolled)
         {
-            /* No (under|over)flow has ocurred */
-            wait = WaitForMultipleObjects(5, stream->events, FALSE, timeout);
-            eventSignaled = wait - WAIT_OBJECT_0;
+            timerEventHandles[1] = info.stream->render.events[0];
+            timerPeriod = min(timerPeriod, (1000*info.stream->render.framesPerBuffer)/fs);
         }
-        else
+
+        if (timerEventHandles[0] || timerEventHandles[1])
         {
-            eventSignaled = wait - WAIT_OBJECT_0;
-            if( eventSignaled < 2 )
+            LARGE_INTEGER dueTime = {0};
+
+            timerPeriod=max(timerPeriod/5,1);
+            PA_DEBUG(("Timer event handles=0x%04X,0x%04X period=%u ms", timerEventHandles[0], timerEventHandles[1], timerPeriod));
+            hTimer = CreateWaitableTimer(0, FALSE, NULL);
+            if (hTimer == NULL)
             {
-                underover |= paInputOverflow;
-                PA_DEBUG(("Input overflow\n"));
+                result = paUnanticipatedHostError;
+                goto error;
             }
-            else if(( eventSignaled < 4 )&&(!priming))
+            /* invoke first timeout immediately */
+            if (!SetWaitableTimer(hTimer, &dueTime, timerPeriod, TimerAPCWaveRTPolledMode, timerEventHandles, FALSE))
             {
-                underover |= paOutputUnderflow;
-                PA_DEBUG(("Output underflow\n"));
+                result = paUnanticipatedHostError;
+                goto error;
             }
+            PA_DEBUG(("Waitable timer started, period = %u ms\n", timerPeriod));
         }
+    }
 
-        if(stream->streamStop)
-        {
-            PA_DEBUG(("ss2:wait=%ld",wait));
-        }
-        if(wait == WAIT_FAILED)
+    /* Mark stream as active */
+    info.stream->streamActive = 1;
+    info.stream->threadResult = paNoError;
+
+    /* Up and running... */
+    SetEvent(info.stream->eventStreamStart[StreamStart_kOk]);
+
+    /* Take timestamp here */
+    timeStamp[0] = timeStamp[1] = GetCurrentTimeInMillisecs();
+
+    while(!info.stream->streamAbort)
+    {
+        unsigned doProcessing = 1;
+        unsigned wait = WaitForMultipleObjects(noOfHandles, handleArray, FALSE, 0);
+        unsigned eventSignalled = wait - WAIT_OBJECT_0;
+        DWORD dwCurrentTime = 0;
+
+        if (wait == WAIT_FAILED) 
         {
-            PA_DEBUG(("Wait fail = %ld! ",wait));
+            PA_DEBUG(("Wait failed = %ld! \n",wait));
             break;
         }
-        if(wait == WAIT_TIMEOUT)
+        if (wait == WAIT_TIMEOUT)
         {
-            continue;
+            wait = WaitForMultipleObjectsEx(noOfHandles, handleArray, FALSE, 50, TRUE);
+            eventSignalled = wait - WAIT_OBJECT_0;
         }
-
-        if(eventSignaled < 2)
-        { /* Recording input buffer has been filled */
-            if(stream->playbackPin)
+        else
+        {
+            if (eventSignalled < captureEvents)
             {
-                /* First check if also the next playback buffer has been signaled */
-                wait = WaitForSingleObject(stream->events[outbuf+2],0);
-                if(wait == WAIT_OBJECT_0)
+                if (PaUtil_GetRingBufferWriteAvailable(&info.stream->ringBuffer) == 0)
                 {
-                    /* Yes, so do both buffers at same time */
-                    fillPlaybuf = 1;
-                    pending--;
-                    /* Was this an underflow situation? */
-                    if( underover )
-                        underover |= paOutputUnderflow; /* Yes! */
+                    PA_HP_TRACE((info.stream->hLog, "!!!!! Input overflow !!!!!"));
+                    info.underover |= paInputOverflow;
                 }
             }
-            emptyRecordbuf = 1;
-            pending--;
-        }
-        else if(eventSignaled < 4)
-        { /* Playback output buffer has been emptied */
-            if(stream->recordingPin)
-            {
-                /* First check if also the next recording buffer has been signaled */
-                wait = WaitForSingleObject(stream->events[inbuf],0);
-                if(wait == WAIT_OBJECT_0)
-                { /* Yes, so do both buffers at same time */
-                    emptyRecordbuf = 1;
-                    pending--;
-                    /* Was this an overflow situation? */
-                    if( underover )
-                        underover |= paInputOverflow; /* Yes! */
+            else if (eventSignalled < renderEvents)
+            {
+                if (!info.priming && info.renderHead - info.renderTail > 1)
+                {
+                    PA_HP_TRACE((info.stream->hLog, "!!!!! Output underflow !!!!!"));
+                    info.underover |= paOutputUnderflow;
                 }
             }
-            fillPlaybuf = 1;
-            pending--;
         }
-        else
+
+        /* Get event time */
+        dwCurrentTime = GetCurrentTimeInMillisecs();
+
+        /* Since we can mix capture/render devices between WaveCyclic, WaveRT polled and WaveRT notification (3x3 combinations), 
+        we can't rely on the timeout of WFMO to check for device timeouts, we need to keep tally. */
+        if (info.stream->capture.pPin && (dwCurrentTime - timeStamp[0]) >= info.timeout)
+        {
+            PA_DEBUG(("Timeout for capture device (%u ms)!", info.timeout, (dwCurrentTime - timeStamp[0])));
+            result = paTimedOut;
+            break;
+        }
+        if (info.stream->render.pPin && (dwCurrentTime - timeStamp[1]) >= info.timeout)
         {
-            /* Abort event! */
-            assert(stream->streamAbort); /* Should have been set */
-            PA_DEBUG(("ABORTING "));
+            PA_DEBUG(("Timeout for render device (%u ms)!", info.timeout, (dwCurrentTime - timeStamp[1])));
+            result = paTimedOut;
             break;
         }
-        ResetEvent(stream->events[eventSignaled]);
 
-        if(stream->streamStop)
+        if (wait == WAIT_IO_COMPLETION)
         {
-            PA_DEBUG(("Stream stop! pending=%d",pending));
-            cbResult = paComplete; /* Stop, but play remaining buffers */
+            /* Waitable timer has fired! */
+            PA_HP_TRACE((info.stream->hLog, "WAIT_IO_COMPLETION"));
+            continue;
         }
 
-        /* Do necessary buffer processing (which will invoke user callback if necessary */
-        doChannelCopy = 0;
-        if(cbResult==paContinue)
+        if (wait == WAIT_TIMEOUT)
+        {
+            continue;
+        }
+        else
         {
-            PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
-            if((stream->bufferProcessor.hostInputFrameCount[0] + stream->bufferProcessor.hostInputFrameCount[1]) ==
-                (stream->bufferProcessor.hostOutputFrameCount[0] + stream->bufferProcessor.hostOutputFrameCount[1]) )
-                PaUtil_BeginBufferProcessing(&stream->bufferProcessor,&ti,underover);
-            underover = 0; /* Reset the (under|over)flow status */
-            if(fillPlaybuf)
+            if (eventSignalled < captureEvents)
             {
-                PaUtil_SetOutputFrameCount(&stream->bufferProcessor,0);
-                if( stream->userOutputChannels == 1 )
-                {
-                    /* Write the single user channel to the first interleaved block */
-                    PaUtil_SetOutputChannel(&stream->bufferProcessor,0,stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels);
-                    /* We will do a copy to the other channels after the data has been written */
-                    doChannelCopy = 1;
-                }
-                else
+                if (info.stream->capture.pPin->fnEventHandler(&info, eventSignalled) == paNoError)
                 {
-                    for(i=0;i<stream->userOutputChannels;i++)
+                    timeStamp[0] = dwCurrentTime;
+
+                    /* Since we use the ring buffer, we can submit the buffers directly */
+                    if (!info.stream->streamStop)
+                    {
+                        result = info.stream->capture.pPin->fnSubmitHandler(&info, info.captureTail);
+                        if (result != paNoError)
+                        {
+                            PA_HP_TRACE((info.stream->hLog, "Capture submit handler failed with result %d", result));
+                            break;
+                        }
+                    }
+                    ++info.captureTail;
+                    /* If full-duplex, let _only_ render event trigger processing. We still need the stream stop
+                    handling working, so let that be processed anyways... */
+                    if (info.stream->userOutputChannels > 0)
                     {
-                        /* Only write the user output channels. Leave the rest blank */
-                        PaUtil_SetOutputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[outbuf+2].Header.Data))+(i*stream->outputSampleSize),stream->deviceOutputChannels);
+                        doProcessing = 0;
                     }
                 }
             }
-            if(emptyRecordbuf)
+            else if (eventSignalled < renderEvents)
             {
-                PaUtil_SetInputFrameCount(&stream->bufferProcessor,stream->packets[inbuf].Header.DataUsed/stream->bytesPerInputFrame);
-                for(i=0;i<stream->userInputChannels;i++)
-                {
-                    /* Only read as many channels as the user wants */
-                    PaUtil_SetInputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[inbuf].Header.Data))+(i*stream->inputSampleSize),stream->deviceInputChannels);
-                }
+                timeStamp[1] = dwCurrentTime;
+                eventSignalled -= captureEvents;
+                info.stream->render.pPin->fnEventHandler(&info, eventSignalled);
             }
-            /* Only call the EndBufferProcessing function is the total input frames == total output frames */
-            if((stream->bufferProcessor.hostInputFrameCount[0] + stream->bufferProcessor.hostInputFrameCount[1]) ==
-                (stream->bufferProcessor.hostOutputFrameCount[0] + stream->bufferProcessor.hostOutputFrameCount[1]) )
-                framesProcessed = PaUtil_EndBufferProcessing(&stream->bufferProcessor,&cbResult);
-            else framesProcessed = 0;
-            if( doChannelCopy )
+            else
             {
-                /* Copy the first output channel to the other channels */
-                switch(stream->outputSampleSize)
-                {
-                    case 2:
-                        DuplicateFirstChannelInt16(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer);
-                        break;
-                    case 3:
-                        DuplicateFirstChannelInt24(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer);
-                        break;
-                    case 4:
-                        DuplicateFirstChannelInt32(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer);
-                        break;
-                    default:
-                        assert(0); /* Unsupported format! */
-                        break;
-                }
+                assert(info.stream->streamAbort);
+                PA_HP_TRACE((info.stream->hLog, "Stream abort!"));
+                continue;
             }
-            PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
-        }
-        else
-        {
-            fillPlaybuf = 0;
-            emptyRecordbuf = 0;
         }
-            
-        /*
-        if(cbResult != paContinue)
-        {
-            PA_DEBUG(("cbResult=%d, pending=%d:",cbResult,pending));
-        }
-        */
-        /* Submit buffers */
-        if((fillPlaybuf)&&(cbResult!=paAbort))
+
+        /* Handle processing */
+        if (doProcessing)
         {
-            if(!PinWrite(stream->playbackPin->handle,&stream->packets[outbuf+2]))
-                outbuf = (outbuf+1)&1; /* Increment and wrap */
-            pending++;
-            if( priming )
-                priming--; /* Have to prime twice */
+            result = PaDoProcessing(&info);
+            if (result != paNoError)
+            {
+                PA_HP_TRACE((info.stream->hLog, "PaDoProcessing failed!"));
+                break;
+            }
         }
-        if((emptyRecordbuf)&&(cbResult==paContinue))
+
+        if(info.stream->streamStop && info.cbResult != paComplete)
         {
-            stream->packets[inbuf].Header.DataUsed = 0; /* Reset for reuse */
-            PinRead(stream->recordingPin->handle,&stream->packets[inbuf]);
-            inbuf = (inbuf+1)&1; /* Increment and wrap */
-            pending++;
+            PA_HP_TRACE((info.stream->hLog, "Stream stop! pending=%d",info.pending));
+            info.cbResult = paComplete; /* Stop, but play remaining buffers */
         }
-        if(pending==0)
+
+        if(info.pending<=0)
         {
-            PA_DEBUG(("pending==0 finished...;"));
+            PA_HP_TRACE((info.stream->hLog, "pending==0 finished..."));
             break;
         }
-        if((!stream->playbackPin)&&(cbResult!=paContinue))
+        if((!info.stream->render.pPin)&&(info.cbResult!=paContinue))
         {
-            PA_DEBUG(("record only cbResult=%d...;",cbResult));
+            PA_HP_TRACE((info.stream->hLog, "record only cbResult=%d...",info.cbResult));
             break;
         }
     }
 
-    PA_DEBUG(("Finished thread"));
+    PA_DEBUG(("Finished processing loop\n"));
+
+    info.stream->threadResult = result;
+    goto bailout;
 
-    /* Finished, either normally or aborted */
-    if(stream->playbackPin)
+error:
+    PA_DEBUG(("Error starting processing thread\n"));
+    /* Set the "error" event together with result */
+    info.stream->threadResult = result;
+    SetEvent(info.stream->eventStreamStart[StreamStart_kFailed]);
+
+bailout:
+    if (hTimer)
     {
-        result = PinSetState(stream->playbackPin, KSSTATE_PAUSE);
-        result = PinSetState(stream->playbackPin, KSSTATE_STOP);
+        PA_DEBUG(("Waitable timer stopped\n", timerPeriod));
+        CancelWaitableTimer(hTimer);
+        CloseHandle(hTimer);
+        hTimer = 0;
     }
-    if(stream->recordingPin)
+
+    if (info.pinsStarted)
     {
-        result = PinSetState(stream->recordingPin, KSSTATE_PAUSE);
-        result = PinSetState(stream->recordingPin, KSSTATE_STOP);
+        StopPins(&info);
     }
 
-    stream->streamActive = 0;
+    /* Lower prio here */
+    DropThreadPriority(hAVRT);
 
-    if((!stream->streamStop)&&(!stream->streamAbort))
+    if (handleArray != NULL)
     {
-          /* Invoke the user stream finished callback */
-          /* Only do it from here if not being stopped/aborted by user */
-          if( stream->streamRepresentation.streamFinishedCallback != 0 )
-              stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+        PaUtil_FreeMemory(handleArray);
     }
-    stream->streamStop = 0;
-    stream->streamAbort = 0;
 
-    /* Reset process priority if necessary */
-    if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS)
+#if PA_TRACE_REALTIME_EVENTS
+    if (info.stream->hLog)
+    {
+        PA_DEBUG(("Dumping highspeed trace...\n"));
+        PaUtil_DumpHighSpeedLog(info.stream->hLog, "hp_trace.log");
+        PaUtil_DiscardHighSpeedLog(info.stream->hLog);
+        info.stream->hLog = 0;
+    }
+#endif
+    info.stream->streamActive = 0;
+
+    if((!info.stream->streamStop)&&(!info.stream->streamAbort))
     {
-        SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority);
-        stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
+        /* Invoke the user stream finished callback */
+        /* Only do it from here if not being stopped/aborted by user */
+        if( info.stream->streamRepresentation.streamFinishedCallback != 0 )
+            info.stream->streamRepresentation.streamFinishedCallback( info.stream->streamRepresentation.userData );
     }
+    info.stream->streamStop = 0;
+    info.stream->streamAbort = 0;
 
     PA_LOGL_;
-    ExitThread(0);
     return 0;
 }
 
+
 static PaError StartStream( PaStream *s )
 {
     PaError result = paNoError;
     PaWinWdmStream *stream = (PaWinWdmStream*)s;
-    DWORD dwID;
-    BOOL ret;
-    int size;
 
     PA_LOGE_;
 
-    stream->streamStop = 0;
-    stream->streamAbort = 0;
-    size = 5;
-    while(size--)
+    if (stream->streamThread != NULL)
     {
-        if(stream->events[size] != NULL)
-        {
-            ResetEvent(stream->events[size]);
-        }
+        return paStreamIsNotStopped;
     }
 
+    stream->streamStop = 0;
+    stream->streamAbort = 0;
+
+    ResetStreamEvents(stream);
+
     PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
 
     stream->oldProcessPriority = GetPriorityClass(GetCurrentProcess());
     /* Uncomment the following line to enable dynamic boosting of the process
-     * priority to real time for best low latency support
-     * Disabled by default because RT processes can easily block the OS */
+    * priority to real time for best low latency support
+    * Disabled by default because RT processes can easily block the OS */
     /*ret = SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
-      PA_DEBUG(("Class ret = %d;",ret));*/
+    PA_DEBUG(("Class ret = %d;",ret));*/
 
-    stream->streamStarted = 1;
-    stream->streamThread = CreateThread(NULL, 0, ProcessingThread, stream, 0, &dwID);
+    stream->streamThread = CREATE_THREAD_FUNCTION (NULL, 0, ProcessingThread, stream, CREATE_SUSPENDED, NULL);
     if(stream->streamThread == NULL)
     {
-        stream->streamStarted = 0;
         result = paInsufficientMemory;
         goto end;
     }
-    ret = SetThreadPriority(stream->streamThread,THREAD_PRIORITY_TIME_CRITICAL);
-    PA_DEBUG(("Priority ret = %d;",ret));
-    /* Make the stream active */
-    stream->streamActive = 1;
+    ResumeThread(stream->streamThread);
+
+    switch (WaitForMultipleObjects(2, stream->eventStreamStart, FALSE, 5000))
+    {
+    case WAIT_OBJECT_0 + StreamStart_kOk:
+        PA_DEBUG(("Processing thread started!\n"));
+        result = paNoError;
+        /* streamActive is set in processing thread */
+        stream->streamStarted = 1;
+        break;
+    case WAIT_OBJECT_0 + StreamStart_kFailed:
+        PA_DEBUG(("Processing thread start failed! (result=%d)\n", stream->threadResult));
+        result = stream->threadResult;
+        /* Wait for the stream to really exit */
+        WaitForSingleObject(stream->streamThread, 200);
+        CloseHandle(stream->streamThread);
+        stream->streamThread = 0;
+        break;
+    case WAIT_TIMEOUT:
+    default:
+        result = paTimedOut;
+        PaWinWDM_SetLastErrorInfo(result, "Failed to start processing thread (timeout)!");
+        break;
+    }
 
 end:
     PA_LOGL_;
@@ -3084,35 +6136,48 @@ static PaError StopStream( PaStream *s )
 {
     PaError result = paNoError;
     PaWinWdmStream *stream = (PaWinWdmStream*)s;
-    int doCb = 0;
+    BOOL doCb = FALSE;
 
     PA_LOGE_;
 
     if(stream->streamActive)
     {
-        doCb = 1;
+        DWORD dwExitCode;
+        doCb = TRUE;
         stream->streamStop = 1;
-        while(stream->streamActive)
+        if (GetExitCodeThread(stream->streamThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
         {
-            PA_DEBUG(("W."));
-            Sleep(10); /* Let thread sleep for 10 msec */
+            if (WaitForSingleObject(stream->streamThread, INFINITE) != WAIT_OBJECT_0)
+            {
+                PA_DEBUG(("StopStream: stream thread terminated\n"));
+                TerminateThread(stream->streamThread, -1);
+                result = paTimedOut;
+            }
+        }
+        else
+        {
+            PA_DEBUG(("StopStream: GECT says not active, but streamActive is not false ??"));
+            result = paUnanticipatedHostError;
+            PaWinWDM_SetLastErrorInfo(result, "StopStream: GECT says not active, but streamActive = %d", stream->streamActive);
         }
     }
-
-    PA_DEBUG(("Terminating thread"));
-    if(stream->streamStarted && stream->streamThread)
+    else
     {
-        TerminateThread(stream->streamThread,0);
-        stream->streamThread = NULL;
+        if (stream->threadResult != paNoError)
+        {
+            PA_DEBUG(("StopStream: Stream not active (%d)\n", stream->threadResult));
+            result = stream->threadResult;
+            stream->threadResult = paNoError;
+        }
     }
 
-    stream->streamStarted = 0;
-
-    if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS)
+    if (stream->streamThread != NULL)
     {
-        SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority);
-        stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
+        CloseHandle(stream->streamThread);
+        stream->streamThread = 0;
     }
+    stream->streamStarted = 0;
+    stream->streamActive = 0;
 
     if(doCb)
     {
@@ -3120,7 +6185,7 @@ static PaError StopStream( PaStream *s )
         /* This means it should be safe for the called function */
         /* to invoke e.g. StartStream */
         if( stream->streamRepresentation.streamFinishedCallback != 0 )
-             stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+            stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
     }
 
     PA_LOGL_;
@@ -3139,27 +6204,20 @@ static PaError AbortStream( PaStream *s )
     {
         doCb = 1;
         stream->streamAbort = 1;
-        SetEvent(stream->events[4]); /* Signal immediately */
-        while(stream->streamActive)
+        SetEvent(stream->eventAbort); /* Signal immediately */
+        if (WaitForSingleObject(stream->streamThread, 10000) != WAIT_OBJECT_0)
         {
-            Sleep(10);
-        }
-    }
+            TerminateThread(stream->streamThread, -1);
+            result = paTimedOut;
 
-    if(stream->streamStarted && stream->streamThread)
-    {
-        TerminateThread(stream->streamThread,0);
-        stream->streamThread = NULL;
+            PA_DEBUG(("AbortStream: stream thread terminated\n"));
+        }
+        assert(!stream->streamActive);
     }
-
+    CloseHandle(stream->streamThread);
+    stream->streamThread = NULL;
     stream->streamStarted = 0;
 
-    if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS)
-    {
-        SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority);
-        stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
-    }
-
     if(doCb)
     {
         /* Do user callback now after all state has been reset */
@@ -3228,14 +6286,14 @@ static double GetStreamCpuLoad( PaStream* s )
 
 
 /*
-    As separate stream interfaces are used for blocking and callback
-    streams, the following functions can be guaranteed to only be called
-    for blocking streams.
+As separate stream interfaces are used for blocking and callback
+streams, the following functions can be guaranteed to only be called
+for blocking streams.
 */
 
 static PaError ReadStream( PaStream* s,
-                           void *buffer,
-                           unsigned long frames )
+                          void *buffer,
+                          unsigned long frames )
 {
     PaWinWdmStream *stream = (PaWinWdmStream*)s;
 
@@ -3248,13 +6306,13 @@ static PaError ReadStream( PaStream* s,
 
     /* IMPLEMENT ME, see portaudio.h for required behavior*/
     PA_LOGL_;
-    return paNoError;
+    return paInternalError;
 }
 
 
 static PaError WriteStream( PaStream* s,
-                            const void *buffer,
-                            unsigned long frames )
+                           const void *buffer,
+                           unsigned long frames )
 {
     PaWinWdmStream *stream = (PaWinWdmStream*)s;
 
@@ -3267,7 +6325,7 @@ static PaError WriteStream( PaStream* s,
 
     /* IMPLEMENT ME, see portaudio.h for required behavior*/
     PA_LOGL_;
-    return paNoError;
+    return paInternalError;
 }
 
 
@@ -3297,4 +6355,301 @@ static signed long GetStreamWriteAvailable( PaStream* s )
     /* IMPLEMENT ME, see portaudio.h for required behavior*/
     PA_LOGL_;
     return 0;
-}
\ No newline at end of file
+}
+
+/***************************************************************************************/
+/* Event and submit handlers for WaveCyclic                                            */
+/***************************************************************************************/
+
+static PaError PaPinCaptureEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    PaError result = paNoError;
+    ring_buffer_size_t frameCount;
+    DATAPACKET* packet = pInfo->stream->capture.packets + eventIndex;
+
+    assert( eventIndex < pInfo->stream->capture.noOfPackets );
+
+    if (packet->Header.DataUsed == 0)
+    {
+        PA_HP_TRACE((pInfo->stream->hLog, ">>> Capture bogus event (no data): idx=%u", eventIndex));
+
+        /* Bogus event, reset! This is to handle the behavior of this USB mic: http://shop.xtz.se/measurement-system/microphone-to-dirac-live-room-correction-suite 
+           on startup of streaming, where it erroneously sets the event without the corresponding buffer being filled (DataUsed == 0) */
+        ResetEvent(packet->Signal.hEvent);
+
+        result = -1;    /* Only need this to be NOT paNoError */
+    }
+    else
+    {
+        pInfo->capturePackets[pInfo->captureHead & cPacketsArrayMask].packet = packet;
+
+        frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer, packet->Header.Data, pInfo->stream->capture.framesPerBuffer);
+
+        PA_HP_TRACE((pInfo->stream->hLog, ">>> Capture event: idx=%u (frames=%u)", eventIndex, frameCount));
+        ++pInfo->captureHead;
+    }
+
+    --pInfo->pending; /* This needs to be done in either case */
+    return result;
+}
+
+static PaError PaPinCaptureSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    PaError result = paNoError;
+    DATAPACKET* packet = pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet;
+    pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0;
+    assert(packet != 0);
+    PA_HP_TRACE((pInfo->stream->hLog, "Capture submit: %u", eventIndex));
+    packet->Header.DataUsed = 0; /* Reset for reuse */
+    ResetEvent(packet->Signal.hEvent);
+    result = PinRead(pInfo->stream->capture.pPin->handle, packet);
+    ++pInfo->pending;
+    return result;
+}
+
+static PaError PaPinRenderEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    assert( eventIndex < pInfo->stream->render.noOfPackets );
+
+    pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask].packet = pInfo->stream->render.packets + eventIndex;
+    PA_HP_TRACE((pInfo->stream->hLog, "<<< Render event : idx=%u head=%u", eventIndex, pInfo->renderHead));
+    ++pInfo->renderHead;
+    --pInfo->pending;
+    return paNoError;
+}
+
+static PaError PaPinRenderSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    PaError result = paNoError;
+    DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet;
+    pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0;
+    assert(packet != 0);
+
+    PA_HP_TRACE((pInfo->stream->hLog, "Render submit : %u idx=%u", pInfo->renderTail, (unsigned)(packet - pInfo->stream->render.packets)));
+    ResetEvent(packet->Signal.hEvent);
+    result = PinWrite(pInfo->stream->render.pPin->handle, packet);
+    /* Reset event, just in case we have an analogous situation to capture (see PaPinCaptureSubmitHandler_WaveCyclic) */
+    ++pInfo->pending;
+    if (pInfo->priming)
+    {
+        --pInfo->priming;
+    }
+    return result;
+}
+
+/***************************************************************************************/
+/* Event and submit handlers for WaveRT                                                */
+/***************************************************************************************/
+
+static PaError PaPinCaptureEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    unsigned long pos;
+    unsigned realInBuf;
+    unsigned frameCount;
+    PaWinWdmIOInfo* pCapture = &pInfo->stream->capture;
+    const unsigned halfInputBuffer = pCapture->hostBufferSize >> 1;
+    PaWinWdmPin* pin = pCapture->pPin;
+    DATAPACKET* packet = 0;
+
+    /* Get hold of current ADC position */
+    pin->fnAudioPosition(pin, &pos);
+    /* Wrap it (robi: why not use hw latency compensation here ?? because pos then gets _way_ off from
+    where it should be, i.e. at beginning or half buffer position. Why? No idea.)  */
+
+    pos %= pCapture->hostBufferSize;
+    /* Then realInBuf will point to "other" half of double buffer */
+    realInBuf = pos < halfInputBuffer ? 1U : 0U;
+
+    packet = pInfo->stream->capture.packets + realInBuf;
+
+    /* Call barrier (or dummy) */
+    pin->fnMemBarrier();
+
+    /* Put it in queue */
+    frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer, packet->Header.Data, pCapture->framesPerBuffer);
+
+    pInfo->capturePackets[pInfo->captureHead & cPacketsArrayMask].packet = packet;
+
+    PA_HP_TRACE((pInfo->stream->hLog, "Capture event (WaveRT): idx=%u head=%u (pos = %4.1lf%%, frames=%u)", realInBuf, pInfo->captureHead, (pos * 100.0 / pCapture->hostBufferSize), frameCount));
+
+    ++pInfo->captureHead;
+    --pInfo->pending;
+
+    return paNoError;
+}
+
+static PaError PaPinCaptureEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    unsigned long pos;
+    unsigned bytesToRead;
+    PaWinWdmIOInfo* pCapture = &pInfo->stream->capture;
+    const unsigned halfInputBuffer = pCapture->hostBufferSize>>1;
+    PaWinWdmPin* pin = pInfo->stream->capture.pPin;
+
+    /* Get hold of current ADC position */
+    pin->fnAudioPosition(pin, &pos);
+    /* Wrap it (robi: why not use hw latency compensation here ?? because pos then gets _way_ off from
+    where it should be, i.e. at beginning or half buffer position. Why? No idea.)  */
+    /* Compensate for HW FIFO to get to last read buffer position */
+    pos += pin->hwLatency;
+    pos %= pCapture->hostBufferSize;
+    /* Need to align position on frame boundary */
+    pos &= ~(pCapture->bytesPerFrame - 1);
+
+    /* Call barrier (or dummy) */
+    pin->fnMemBarrier();
+
+    /* Put it in "queue" */
+    bytesToRead = (pCapture->hostBufferSize + pos - pCapture->lastPosition) % pCapture->hostBufferSize;
+    if (bytesToRead > 0)
+    {
+        unsigned frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer,
+            pCapture->hostBuffer + pCapture->lastPosition,
+            bytesToRead / pCapture->bytesPerFrame);
+
+        pCapture->lastPosition = (pCapture->lastPosition + frameCount * pCapture->bytesPerFrame) % pCapture->hostBufferSize;
+
+        PA_HP_TRACE((pInfo->stream->hLog, "Capture event (WaveRTPolled): pos = %4.1lf%%, framesRead=%u", (pos * 100.0 / pCapture->hostBufferSize), frameCount));
+        ++pInfo->captureHead;
+        --pInfo->pending;
+    }
+    return paNoError;
+}
+
+static PaError PaPinCaptureSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0;
+    ++pInfo->pending;
+    return paNoError;
+}
+
+static PaError PaPinCaptureSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0;
+    ++pInfo->pending;
+    return paNoError;
+}
+
+static PaError PaPinRenderEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    unsigned long pos;
+    unsigned realOutBuf;
+    PaWinWdmIOInfo* pRender = &pInfo->stream->render;
+    const unsigned halfOutputBuffer = pRender->hostBufferSize >> 1;
+    PaWinWdmPin* pin = pInfo->stream->render.pPin;
+    PaIOPacket* ioPacket = &pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask];
+
+    /* Get hold of current DAC position */
+    pin->fnAudioPosition(pin, &pos);
+    /* Compensate for HW FIFO to get to last read buffer position */
+    pos += pin->hwLatency;
+    /* Wrap it */
+    pos %= pRender->hostBufferSize;
+    /* And align it, not sure its really needed though */
+    pos &= ~(pRender->bytesPerFrame - 1);
+    /* Then realOutBuf will point to "other" half of double buffer */
+    realOutBuf = pos < halfOutputBuffer ? 1U : 0U;
+
+    if (pInfo->priming)
+    {
+        realOutBuf = pInfo->renderHead & 0x1;
+    }
+    ioPacket->packet = pInfo->stream->render.packets + realOutBuf;
+    ioPacket->startByte = realOutBuf * halfOutputBuffer;
+    ioPacket->lengthBytes = halfOutputBuffer;
+
+    PA_HP_TRACE((pInfo->stream->hLog, "Render event (WaveRT) : idx=%u head=%u (pos = %4.1lf%%)", realOutBuf, pInfo->renderHead, (pos * 100.0 / pRender->hostBufferSize) ));
+
+    ++pInfo->renderHead;
+    --pInfo->pending;
+    return paNoError;
+}
+
+static PaError PaPinRenderEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    unsigned long pos;
+    unsigned realOutBuf;
+    unsigned bytesToWrite;
+
+    PaWinWdmIOInfo* pRender = &pInfo->stream->render;
+    const unsigned halfOutputBuffer = pRender->hostBufferSize >> 1;
+    PaWinWdmPin* pin = pInfo->stream->render.pPin;
+    PaIOPacket* ioPacket = &pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask];
+
+    /* Get hold of current DAC position */
+    pin->fnAudioPosition(pin, &pos);
+    /* Compensate for HW FIFO to get to last read buffer position */
+    pos += pin->hwLatency;
+    /* Wrap it */
+    pos %= pRender->hostBufferSize;
+    /* And align it, not sure its really needed though */
+    pos &= ~(pRender->bytesPerFrame - 1);
+
+    if (pInfo->priming)
+    {
+        realOutBuf = pInfo->renderHead & 0x1;
+        ioPacket->packet = pInfo->stream->render.packets + realOutBuf;
+        ioPacket->startByte = realOutBuf * halfOutputBuffer;
+        ioPacket->lengthBytes = halfOutputBuffer;
+        ++pInfo->renderHead;
+        --pInfo->pending;
+    }
+    else
+    {
+        bytesToWrite = (pRender->hostBufferSize + pos - pRender->lastPosition) % pRender->hostBufferSize;
+        ++pRender->pollCntr;
+        if (bytesToWrite >= halfOutputBuffer)
+        {
+            realOutBuf = (pos < halfOutputBuffer) ? 1U : 0U;
+            ioPacket->packet = pInfo->stream->render.packets + realOutBuf;
+            pRender->lastPosition = realOutBuf ? 0U : halfOutputBuffer;
+            ioPacket->startByte = realOutBuf * halfOutputBuffer;
+            ioPacket->lengthBytes = halfOutputBuffer;
+            ++pInfo->renderHead;
+            --pInfo->pending;
+            PA_HP_TRACE((pInfo->stream->hLog, "Render event (WaveRTPolled) : idx=%u head=%u (pos = %4.1lf%%, cnt=%u)", realOutBuf, pInfo->renderHead, (pos * 100.0 / pRender->hostBufferSize), pRender->pollCntr));
+            pRender->pollCntr = 0;
+        }
+    }
+    return paNoError;
+}
+
+static PaError PaPinRenderSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    PaWinWdmPin* pin = pInfo->stream->render.pPin;
+    pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0;
+    /* Call barrier (if needed) */
+    pin->fnMemBarrier();
+    PA_HP_TRACE((pInfo->stream->hLog, "Render submit (WaveRT) : submit=%u", pInfo->renderTail));
+    ++pInfo->pending;
+    if (pInfo->priming)
+    {
+        --pInfo->priming;
+        if (pInfo->priming)
+        {
+            PA_HP_TRACE((pInfo->stream->hLog, "Setting WaveRT event for priming (2)"));
+            SetEvent(pInfo->stream->render.events[0]);
+        }
+    }
+    return paNoError;
+}
+
+static PaError PaPinRenderSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
+{
+    PaWinWdmPin* pin = pInfo->stream->render.pPin;
+    pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0;
+    /* Call barrier (if needed) */
+    pin->fnMemBarrier();
+    PA_HP_TRACE((pInfo->stream->hLog, "Render submit (WaveRTPolled) : submit=%u", pInfo->renderTail));
+    ++pInfo->pending;
+    if (pInfo->priming)
+    {
+        --pInfo->priming;
+        if (pInfo->priming)
+        {
+            PA_HP_TRACE((pInfo->stream->hLog, "Setting WaveRT event for priming (2)"));
+            SetEvent(pInfo->stream->render.events[0]);
+        }
+    }
+    return paNoError;
+}
diff --git a/external/portaudio/pa_win_wdmks.h b/external/portaudio/pa_win_wdmks.h
new file mode 100644
index 0000000..9fe9284
--- /dev/null
+++ b/external/portaudio/pa_win_wdmks.h
@@ -0,0 +1,106 @@
+#ifndef PA_WIN_WDMKS_H
+#define PA_WIN_WDMKS_H
+/*
+ * $Id: pa_win_wdmks.h 1812 2012-02-14 09:32:57Z robiwan $
+ * PortAudio Portable Real-Time Audio Library
+ * WDM/KS specific extensions
+ *
+ * Copyright (c) 1999-2007 Ross Bencina and Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+/** @file
+ @ingroup public_header
+ @brief WDM Kernel Streaming-specific PortAudio API extension header file.
+*/
+
+
+#include "portaudio.h"
+
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+    typedef struct PaWinWDMKSInfo{
+        unsigned long size;             /**< sizeof(PaWinWDMKSInfo) */
+        PaHostApiTypeId hostApiType;    /**< paWDMKS */
+        unsigned long version;          /**< 1 */
+
+        /* The number of packets to use for WaveCyclic devices, range is [2, 8]. Set to zero for default value of 2. */
+        unsigned noOfPackets;
+    } PaWinWDMKSInfo;
+
+    typedef enum PaWDMKSType
+    {
+        Type_kNotUsed,
+        Type_kWaveCyclic,
+        Type_kWaveRT,
+        Type_kCnt,
+    } PaWDMKSType;
+
+    typedef enum PaWDMKSSubType
+    {
+        SubType_kUnknown,
+        SubType_kNotification,
+        SubType_kPolled,
+        SubType_kCnt,
+    } PaWDMKSSubType;
+
+    typedef struct PaWinWDMKSDeviceInfo {
+        wchar_t filterPath[MAX_PATH];     /**< KS filter path in Unicode! */
+        wchar_t topologyPath[MAX_PATH];   /**< Topology filter path in Unicode! */
+        PaWDMKSType streamingType;
+        GUID deviceProductGuid;           /**< The product GUID of the device (if supported) */
+    } PaWinWDMKSDeviceInfo;
+
+    typedef struct PaWDMKSDirectionSpecificStreamInfo
+    {
+        PaDeviceIndex device;
+        unsigned channels;                  /**< No of channels the device is opened with */
+        unsigned framesPerHostBuffer;       /**< No of frames of the device buffer */
+        int endpointPinId;                  /**< Endpoint pin ID (on topology filter if topologyName is not empty) */
+        int muxNodeId;                      /**< Only valid for input */
+        PaWDMKSSubType streamingSubType;       /**< Not known until device is opened for streaming */
+    } PaWDMKSDirectionSpecificStreamInfo;
+
+    typedef struct PaWDMKSSpecificStreamInfo {
+        PaWDMKSDirectionSpecificStreamInfo input;
+        PaWDMKSDirectionSpecificStreamInfo output;
+    } PaWDMKSSpecificStreamInfo;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PA_WIN_DS_H */                                  
diff --git a/external/portaudio/pa_win_wdmks_utils.c b/external/portaudio/pa_win_wdmks_utils.c
new file mode 100644
index 0000000..47e4f10
--- /dev/null
+++ b/external/portaudio/pa_win_wdmks_utils.c
@@ -0,0 +1,308 @@
+/*
+ * PortAudio Portable Real-Time Audio Library
+ * Windows WDM KS utilities
+ *
+ * Copyright (c) 1999 - 2007 Andrew Baldwin, Ross Bencina
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+#include <windows.h>
+#include <mmreg.h>
+#ifndef WAVE_FORMAT_IEEE_FLOAT
+    #define WAVE_FORMAT_IEEE_FLOAT 0x0003   // MinGW32 does not define this
+#endif    
+#ifndef _WAVEFORMATEXTENSIBLE_
+    #define _WAVEFORMATEXTENSIBLE_          // MinGW32 does not define this
+#endif
+#ifndef _INC_MMREG
+    #define _INC_MMREG                      // for STATIC_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
+#endif
+#include <winioctl.h>						// MinGW32 does not define this automatically
+
+#if defined(__GNUC__)
+
+#include "../../hostapi/wasapi/mingw-include/ks.h"
+#include "../../hostapi/wasapi/mingw-include/ksmedia.h"
+
+#else
+
+#include <ks.h>
+#include <ksmedia.h>
+
+#endif
+
+#include <stdio.h>                          // just for some development printfs
+
+#include "portaudio.h"
+#include "pa_util.h"
+#include "pa_win_wdmks_utils.h"
+
+#if !defined(PA_WDMKS_NO_KSGUID_LIB) && !defined(PAWIN_WDMKS_NO_KSGUID_LIB) && !defined(__GNUC__)
+    #if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
+        #pragma comment( lib, "ksguid.lib" )
+    #endif
+    #define pa_KSDATAFORMAT_TYPE_AUDIO            KSDATAFORMAT_TYPE_AUDIO
+    #define pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT    KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
+    #define pa_KSDATAFORMAT_SUBTYPE_PCM           KSDATAFORMAT_SUBTYPE_PCM
+    #define pa_KSDATAFORMAT_SUBTYPE_WAVEFORMATEX  KSDATAFORMAT_SUBTYPE_WAVEFORMATEX
+    #define pa_KSMEDIUMSETID_Standard             KSMEDIUMSETID_Standard
+    #define pa_KSINTERFACESETID_Standard          KSINTERFACESETID_Standard
+    #define pa_KSPROPSETID_Pin                    KSPROPSETID_Pin
+#else
+    static const GUID pa_KSDATAFORMAT_TYPE_AUDIO            = { STATIC_KSDATAFORMAT_TYPE_AUDIO };
+    static const GUID pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT    = { STATIC_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT };
+    static const GUID pa_KSDATAFORMAT_SUBTYPE_PCM           = { STATIC_KSDATAFORMAT_SUBTYPE_PCM };
+    static const GUID pa_KSDATAFORMAT_SUBTYPE_WAVEFORMATEX  = { STATIC_KSDATAFORMAT_SUBTYPE_WAVEFORMATEX };
+    static const GUID pa_KSMEDIUMSETID_Standard             = { STATIC_KSMEDIUMSETID_Standard };
+    static const GUID pa_KSINTERFACESETID_Standard          = { STATIC_KSINTERFACESETID_Standard };
+    static const GUID pa_KSPROPSETID_Pin                    = { STATIC_KSPROPSETID_Pin };
+#endif
+
+
+#define pa_IS_VALID_WAVEFORMATEX_GUID(Guid)\
+    (!memcmp(((PUSHORT)&pa_KSDATAFORMAT_SUBTYPE_WAVEFORMATEX) + 1, ((PUSHORT)(Guid)) + 1, sizeof(GUID) - sizeof(USHORT)))
+
+
+
+static PaError WdmGetPinPropertySimple(
+    HANDLE  handle,
+    unsigned long pinId,
+    unsigned long property,
+    void* value,
+    unsigned long valueSize )
+{
+    DWORD bytesReturned;
+    KSP_PIN ksPProp;
+    ksPProp.Property.Set = pa_KSPROPSETID_Pin;
+    ksPProp.Property.Id = property;
+    ksPProp.Property.Flags = KSPROPERTY_TYPE_GET;
+    ksPProp.PinId = pinId;
+    ksPProp.Reserved = 0;
+
+    if( DeviceIoControl( handle, IOCTL_KS_PROPERTY, &ksPProp, sizeof(KSP_PIN),
+            value, valueSize, &bytesReturned, NULL ) == 0 || bytesReturned != valueSize )
+    {
+        return paUnanticipatedHostError;
+    }
+    else
+    {
+        return paNoError;
+    }
+}
+
+
+static PaError WdmGetPinPropertyMulti(
+    HANDLE handle,
+    unsigned long pinId,
+    unsigned long property,
+    KSMULTIPLE_ITEM** ksMultipleItem)
+{
+    unsigned long multipleItemSize = 0;
+    KSP_PIN ksPProp;
+    DWORD bytesReturned;
+
+    *ksMultipleItem = 0;
+
+    ksPProp.Property.Set = pa_KSPROPSETID_Pin;
+    ksPProp.Property.Id = property;
+    ksPProp.Property.Flags = KSPROPERTY_TYPE_GET;
+    ksPProp.PinId = pinId;
+    ksPProp.Reserved = 0;
+
+    if( DeviceIoControl( handle, IOCTL_KS_PROPERTY, &ksPProp.Property,
+            sizeof(KSP_PIN), NULL, 0, &multipleItemSize, NULL ) == 0 && GetLastError() != ERROR_MORE_DATA )
+    {
+        return paUnanticipatedHostError;
+    }
+
+    *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize );
+    if( !*ksMultipleItem )
+    {
+        return paInsufficientMemory;
+    }
+
+    if( DeviceIoControl( handle, IOCTL_KS_PROPERTY, &ksPProp, sizeof(KSP_PIN),
+            (void*)*ksMultipleItem,  multipleItemSize, &bytesReturned, NULL ) == 0 || bytesReturned != multipleItemSize )
+    {
+        PaUtil_FreeMemory( ksMultipleItem );
+        return paUnanticipatedHostError;
+    }
+
+    return paNoError;
+}
+
+
+static int GetKSFilterPinCount( HANDLE deviceHandle )
+{
+    DWORD result;
+
+    if( WdmGetPinPropertySimple( deviceHandle, 0, KSPROPERTY_PIN_CTYPES, &result, sizeof(result) ) == paNoError ){
+        return result;
+    }else{
+        return 0;
+    }
+}
+
+
+static KSPIN_COMMUNICATION GetKSFilterPinPropertyCommunication( HANDLE deviceHandle, int pinId )
+{
+    KSPIN_COMMUNICATION result;
+
+    if( WdmGetPinPropertySimple( deviceHandle, pinId, KSPROPERTY_PIN_COMMUNICATION, &result, sizeof(result) ) == paNoError ){
+        return result;
+    }else{
+        return KSPIN_COMMUNICATION_NONE;
+    }
+}
+
+
+static KSPIN_DATAFLOW GetKSFilterPinPropertyDataflow( HANDLE deviceHandle, int pinId )
+{
+    KSPIN_DATAFLOW result;
+
+    if( WdmGetPinPropertySimple( deviceHandle, pinId, KSPROPERTY_PIN_DATAFLOW, &result, sizeof(result) ) == paNoError ){
+        return result;
+    }else{
+        return (KSPIN_DATAFLOW)0;
+    }
+}
+
+
+static int KSFilterPinPropertyIdentifiersInclude( 
+        HANDLE deviceHandle, int pinId, unsigned long property, const GUID *identifierSet, unsigned long identifierId  )
+{
+    KSMULTIPLE_ITEM* item = NULL;
+    KSIDENTIFIER* identifier;
+    int i;
+    int result = 0;
+
+    if( WdmGetPinPropertyMulti( deviceHandle, pinId, property, &item) != paNoError )
+        return 0;
+    
+    identifier = (KSIDENTIFIER*)(item+1);
+
+    for( i = 0; i < (int)item->Count; i++ )
+    {
+        if( !memcmp( (void*)&identifier[i].Set, (void*)identifierSet, sizeof( GUID ) ) &&
+           ( identifier[i].Id == identifierId ) )
+        {
+            result = 1;
+            break;
+        }
+    }
+
+    PaUtil_FreeMemory( item );
+
+    return result;
+}
+
+
+/* return the maximum channel count supported by any pin on the device. 
+   if isInput is non-zero we query input pins, otherwise output pins.
+*/
+int PaWin_WDMKS_QueryFilterMaximumChannelCount( void *wcharDevicePath, int isInput )
+{
+    HANDLE deviceHandle;
+	ULONG i;
+    int pinCount, pinId;
+    int result = 0;
+    KSPIN_DATAFLOW requiredDataflowDirection = (isInput ? KSPIN_DATAFLOW_OUT : KSPIN_DATAFLOW_IN );
+    
+    if( !wcharDevicePath )
+        return 0;
+
+    deviceHandle = CreateFileW( (LPCWSTR)wcharDevicePath, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL );
+    if( deviceHandle == INVALID_HANDLE_VALUE )
+        return 0;
+
+    pinCount = GetKSFilterPinCount( deviceHandle );
+    for( pinId = 0; pinId < pinCount; ++pinId )
+    {
+        KSPIN_COMMUNICATION communication = GetKSFilterPinPropertyCommunication( deviceHandle, pinId );
+        KSPIN_DATAFLOW dataflow = GetKSFilterPinPropertyDataflow( deviceHandle, pinId );
+        if( ( dataflow == requiredDataflowDirection ) &&
+                (( communication == KSPIN_COMMUNICATION_SINK) ||
+                 ( communication == KSPIN_COMMUNICATION_BOTH)) 
+             && ( KSFilterPinPropertyIdentifiersInclude( deviceHandle, pinId, 
+                    KSPROPERTY_PIN_INTERFACES, &pa_KSINTERFACESETID_Standard, KSINTERFACE_STANDARD_STREAMING )
+                || KSFilterPinPropertyIdentifiersInclude( deviceHandle, pinId, 
+                    KSPROPERTY_PIN_INTERFACES, &pa_KSINTERFACESETID_Standard, KSINTERFACE_STANDARD_LOOPED_STREAMING ) )
+             && KSFilterPinPropertyIdentifiersInclude( deviceHandle, pinId, 
+                    KSPROPERTY_PIN_MEDIUMS, &pa_KSMEDIUMSETID_Standard, KSMEDIUM_STANDARD_DEVIO ) )
+         {
+            KSMULTIPLE_ITEM* item = NULL;
+            if( WdmGetPinPropertyMulti( deviceHandle, pinId, KSPROPERTY_PIN_DATARANGES, &item ) == paNoError )
+            {
+                KSDATARANGE *dataRange = (KSDATARANGE*)(item+1);
+
+                for( i=0; i < item->Count; ++i ){
+
+                    if( pa_IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat)
+                            || memcmp( (void*)&dataRange->SubFormat, (void*)&pa_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID) ) == 0
+                            || memcmp( (void*)&dataRange->SubFormat, (void*)&pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID) ) == 0
+                            || ( ( memcmp( (void*)&dataRange->MajorFormat, (void*)&pa_KSDATAFORMAT_TYPE_AUDIO, sizeof(GUID) ) == 0 )
+                                && ( memcmp( (void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof(GUID) ) == 0 ) ) )
+                    {
+                        KSDATARANGE_AUDIO *dataRangeAudio = (KSDATARANGE_AUDIO*)dataRange;
+                        
+                        /*
+                        printf( ">>> %d %d %d %d %S\n", isInput, dataflow, communication, dataRangeAudio->MaximumChannels, devicePath );
+                       
+                        if( memcmp((void*)&dataRange->Specifier, (void*)&KSDATAFORMAT_SPECIFIER_WAVEFORMATEX, sizeof(GUID) ) == 0 )
+                            printf( "\tspecifier: KSDATAFORMAT_SPECIFIER_WAVEFORMATEX\n" );
+                        else if( memcmp((void*)&dataRange->Specifier, (void*)&KSDATAFORMAT_SPECIFIER_DSOUND, sizeof(GUID) ) == 0 )
+                            printf( "\tspecifier: KSDATAFORMAT_SPECIFIER_DSOUND\n" );
+                        else if( memcmp((void*)&dataRange->Specifier, (void*)&KSDATAFORMAT_SPECIFIER_WILDCARD, sizeof(GUID) ) == 0 )
+                            printf( "\tspecifier: KSDATAFORMAT_SPECIFIER_WILDCARD\n" );
+                        else
+                            printf( "\tspecifier: ?\n" );
+                        */
+
+                        /*
+                            We assume that very high values for MaximumChannels are not useful and indicate
+                            that the driver isn't prepared to tell us the real number of channels which it supports.
+                        */
+                        if( dataRangeAudio->MaximumChannels  < 0xFFFFUL && (int)dataRangeAudio->MaximumChannels > result )
+                            result = (int)dataRangeAudio->MaximumChannels;
+                    }
+                    
+                    dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize);
+                }
+
+                PaUtil_FreeMemory( item );
+            }
+        }
+    }
+    
+    CloseHandle( deviceHandle );
+    return result;
+}
diff --git a/external/portaudio/pa_win_wmme.c b/external/portaudio/pa_win_wmme.c
index a77bbdd..614a799 100644
--- a/external/portaudio/pa_win_wmme.c
+++ b/external/portaudio/pa_win_wmme.c
@@ -1,6 +1,6 @@
 #ifdef _WIN32
 /*
- * $Id: pa_win_wmme.c 1286 2007-09-26 21:34:23Z rossb $
+ * $Id: pa_win_wmme.c 1874 2012-10-31 06:20:59Z rbencina $
  * pa_win_wmme.c
  * Implementation of PortAudio for Windows MultiMedia Extensions (WMME)       
  *                                                                                         
@@ -63,36 +63,9 @@
 */
 
 /** @file
-	@ingroup hostaip_src
-
-	@todo Fix buffer catch up code, can sometimes get stuck (perhaps fixed now,
-            needs to be reviewed and tested.)
-
-    @todo implement paInputUnderflow, paOutputOverflow streamCallback statusFlags, paNeverDropInput.
-
-    @todo BUG: PA_MME_SET_LAST_WAVEIN/OUT_ERROR is used in functions which may
-                be called asynchronously from the callback thread. this is bad.
-
-    @todo implement inputBufferAdcTime in callback thread
-
-    @todo review/fix error recovery and cleanup in marked functions
-
-    @todo implement timeInfo for stream priming
-
-    @todo handle the case where the callback returns paAbort or paComplete during stream priming.
-
-    @todo review input overflow and output underflow handling in ReadStream and WriteStream
-
-Non-critical stuff for the future:
-
-    @todo Investigate supporting host buffer formats > 16 bits
-    
-    @todo define UNICODE and _UNICODE in the project settings and see what breaks
-
-    @todo refactor conversion of MMSYSTEM errors into PA arrors into a single function.
-
-    @todo cleanup WAVEFORMATEXTENSIBLE retry in InitializeWaveHandles to not use a for loop
+	@ingroup hostapi_src
 
+    @brief Win32 host API implementation for the Windows MultiMedia Extensions (WMME) audio API.
 */
 
 /*
@@ -101,7 +74,7 @@ Non-critical stuff for the future:
     For both callback and blocking read/write streams we open the MME devices
     in CALLBACK_EVENT mode. In this mode, MME signals an Event object whenever
     it has finished with a buffer (either filled it for input, or played it
-    for output). Where necessary we block waiting for Event objects using
+    for output). Where necessary, we block waiting for Event objects using
     WaitMultipleObjects().
 
     When implementing a PA callback stream, we set up a high priority thread
@@ -112,7 +85,6 @@ Non-critical stuff for the future:
     Events (when necessary) inside the ReadStream() and WriteStream() functions.
 */
 
-#undef UNICODE
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
@@ -123,7 +95,7 @@ Non-critical stuff for the future:
 #endif
 #include <assert.h>
 /* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */
-#ifndef __MWERKS__XXX
+#ifndef __MWERKS__
 #include <malloc.h>
 #include <memory.h>
 #endif /* __MWERKS__ */
@@ -151,7 +123,17 @@ Non-critical stuff for the future:
 #endif
 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
 
-#if (defined(UNDER_CE))
+/* use CreateThread for CYGWIN, _beginthreadex for all others */
+#if !defined(__CYGWIN__) && !defined(_WIN32_WCE)
+#define CREATE_THREAD (HANDLE)_beginthreadex( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId )
+#define PA_THREAD_FUNC static unsigned WINAPI
+#define PA_THREAD_ID unsigned
+#else
+#define CREATE_THREAD CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId )
+#define PA_THREAD_FUNC static DWORD WINAPI
+#define PA_THREAD_ID DWORD
+#endif
+#if (defined(_WIN32_WCE))
 #pragma comment(lib, "Coredll.lib")
 #elif (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
 #pragma comment(lib, "winmm.lib")
@@ -161,7 +143,11 @@ Non-critical stuff for the future:
  provided in newer platform sdks
  */
 #ifndef DWORD_PTR
-#define DWORD_PTR DWORD
+    #if defined(_WIN64)
+        #define DWORD_PTR unsigned __int64
+    #else
+        #define DWORD_PTR unsigned long
+    #endif
 #endif
 
 /************************************************* Constants ********/
@@ -169,35 +155,81 @@ Non-critical stuff for the future:
 #define PA_MME_USE_HIGH_DEFAULT_LATENCY_    (0)  /* For debugging glitches. */
 
 #if PA_MME_USE_HIGH_DEFAULT_LATENCY_
- #define PA_MME_WIN_9X_DEFAULT_LATENCY_                     (0.4)
- #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_               (4)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_	(4)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_	(4)
- #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_	(16)
- #define PA_MME_MAX_HOST_BUFFER_SECS_				        (0.3)       /* Do not exceed unless user buffer exceeds */
- #define PA_MME_MAX_HOST_BUFFER_BYTES_				        (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
+ #define PA_MME_WIN_9X_DEFAULT_LATENCY_                             (0.4)
+ #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_                       (4)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_	        (4)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_	        (4)
+ #define PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_	(16)
+ #define PA_MME_MAX_HOST_BUFFER_SECS_				                (0.3)       /* Do not exceed unless user buffer exceeds */
+ #define PA_MME_MAX_HOST_BUFFER_BYTES_				                (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
 #else
- #define PA_MME_WIN_9X_DEFAULT_LATENCY_                     (0.2)
- #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_               (2)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_	(3)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_	(2)
- #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_	(16)
- #define PA_MME_MAX_HOST_BUFFER_SECS_				        (0.1)       /* Do not exceed unless user buffer exceeds */
- #define PA_MME_MAX_HOST_BUFFER_BYTES_				        (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
+ #define PA_MME_WIN_9X_DEFAULT_LATENCY_                             (0.2)
+ #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_                       (2)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_	        (3)         /* always use at least 3 input buffers for full duplex */
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_	        (2)
+ #define PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_	(16)
+ #define PA_MME_MAX_HOST_BUFFER_SECS_				                (0.1)       /* Do not exceed unless user buffer exceeds */
+ #define PA_MME_MAX_HOST_BUFFER_BYTES_				                (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
 #endif
 
 /* Use higher latency for NT because it is even worse at real-time
    operation than Win9x.
 */
-#define PA_MME_WIN_NT_DEFAULT_LATENCY_      (PA_MME_WIN_9X_DEFAULT_LATENCY_ * 2)
-#define PA_MME_WIN_WDM_DEFAULT_LATENCY_     (PA_MME_WIN_9X_DEFAULT_LATENCY_)
+#define PA_MME_WIN_NT_DEFAULT_LATENCY_                              (0.4)
+
+/* Default low latency for WDM based systems. This is based on a rough
+   survey of workable latency settings using patest_wmme_find_best_latency_params.c.
+   See pdf attached to ticket 185 for a graph of the survey results:
+   http://www.portaudio.com/trac/ticket/185
+   
+   Workable latencies varied between 40ms and ~80ms on different systems (different
+   combinations of hardware, 32 and 64 bit, WinXP, Vista and Win7. We didn't
+   get enough Vista results to know if Vista has systemically worse latency.
+   For now we choose a safe value across all Windows versions here.
+*/
+#define PA_MME_WIN_WDM_DEFAULT_LATENCY_                             (0.090)
+
 
+/* When client suggestedLatency could result in many host buffers, we aim to have around 8, 
+   based off Windows documentation that suggests that the kmixer uses 8 buffers. This choice
+   is somewhat arbitrary here, since we havn't observed significant stability degredation 
+   with using either more, or less buffers.     
+*/
+#define PA_MME_TARGET_HOST_BUFFER_COUNT_    8
 
 #define PA_MME_MIN_TIMEOUT_MSEC_        (1000)
 
 static const char constInputMapperSuffix_[] = " - Input";
 static const char constOutputMapperSuffix_[] = " - Output";
 
+/*
+copies TCHAR string to explicit char string
+*/
+char *StrTCpyToC(char *to, const TCHAR *from)
+{
+#if !defined(_UNICODE) && !defined(UNICODE)
+	return strcpy(to, from);
+#else
+	int count = wcslen(from);
+	if (count != 0)
+		if (WideCharToMultiByte(CP_ACP, 0, from, count, to, count, NULL, NULL) == 0)
+			return NULL;
+	return to;
+#endif
+}
+
+/*
+returns length of TCHAR string
+*/
+size_t StrTLen(const TCHAR *str)
+{
+#if !defined(_UNICODE) && !defined(UNICODE)
+	return strlen(str);
+#else
+	return wcslen(str);	
+#endif
+}
+
 /********************************************************************/
 
 typedef struct PaWinMmeStream PaWinMmeStream;     /* forward declaration */
@@ -464,6 +496,21 @@ static UINT LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hos
 }
 
 
+static int SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( PaSampleFormat sampleFormat, unsigned long winMmeSpecificFlags )
+{
+    int waveFormatTag = 0;
+
+    if( winMmeSpecificFlags & paWinMmeWaveFormatDolbyAc3Spdif )
+        waveFormatTag = PAWIN_WAVE_FORMAT_DOLBY_AC3_SPDIF;
+    else if( winMmeSpecificFlags & paWinMmeWaveFormatWmaSpdif )
+        waveFormatTag = PAWIN_WAVE_FORMAT_WMA_SPDIF;
+    else
+        waveFormatTag = PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat );
+
+    return waveFormatTag;
+}
+
+
 static PaError QueryInputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx )
 {
     MMRESULT mmresult;
@@ -518,43 +565,55 @@ static PaError QueryOutputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx
 
 static PaError QueryFormatSupported( PaDeviceInfo *deviceInfo,
         PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*),
-        int winMmeDeviceId, int channels, double sampleRate )
+        int winMmeDeviceId, int channels, double sampleRate, unsigned long winMmeSpecificFlags )
 {
     PaWinMmeDeviceInfo *winMmeDeviceInfo = (PaWinMmeDeviceInfo*)deviceInfo;
     PaWinWaveFormat waveFormat;
+    PaSampleFormat sampleFormat;
+    int waveFormatTag;
     
-    if( sampleRate == 11025.0
-        && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1M16))
-            || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1S16)) ) ){
+    /* @todo at the moment we only query with 16 bit sample format and directout speaker config*/
 
-        return paNoError;
-    }
+    sampleFormat = paInt16;
+    waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags );
 
-    if( sampleRate == 22050.0
-        && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2M16))
-            || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2S16)) ) ){
+    if( waveFormatTag == PaWin_SampleFormatToLinearWaveFormatTag( paInt16 ) ){
+    
+        /* attempt bypass querying the device for linear formats */
 
-        return paNoError;
-    }
+        if( sampleRate == 11025.0
+            && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1M16))
+                || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1S16)) ) ){
 
-    if( sampleRate == 44100.0
-        && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4M16))
-            || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4S16)) ) ){
+            return paNoError;
+        }
 
-        return paNoError;
+        if( sampleRate == 22050.0
+            && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2M16))
+                || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2S16)) ) ){
+
+            return paNoError;
+        }
+
+        if( sampleRate == 44100.0
+            && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4M16))
+                || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4S16)) ) ){
+
+            return paNoError;
+        }
     }
 
+
     /* first, attempt to query the device using WAVEFORMATEXTENSIBLE, 
        if this fails we fall back to WAVEFORMATEX */
 
-    /* @todo at the moment we only query with 16 bit sample format and directout speaker config*/
-    PaWin_InitializeWaveFormatExtensible( &waveFormat, channels, 
-            paInt16, sampleRate, PAWIN_SPEAKER_DIRECTOUT );
+    PaWin_InitializeWaveFormatExtensible( &waveFormat, channels, sampleFormat, waveFormatTag,
+            sampleRate, PAWIN_SPEAKER_DIRECTOUT );
 
     if( waveFormatExQueryFunction( winMmeDeviceId, (WAVEFORMATEX*)&waveFormat ) == paNoError )
         return paNoError;
 
-    PaWin_InitializeWaveFormatEx( &waveFormat, channels, paInt16, sampleRate );
+    PaWin_InitializeWaveFormatEx( &waveFormat, channels, sampleFormat, waveFormatTag, sampleRate );
 
     return waveFormatExQueryFunction( winMmeDeviceId, (WAVEFORMATEX*)&waveFormat );
 }
@@ -576,7 +635,7 @@ static void DetectDefaultSampleRate( PaWinMmeDeviceInfo *winMmeDeviceInfo, int w
     for( i=0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i )
     {
         double sampleRate = defaultSampleRateSearchOrder_[ i ]; 
-        PaError paerror = QueryFormatSupported( deviceInfo, waveFormatExQueryFunction, winMmeDeviceId, maxChannels, sampleRate );
+        PaError paerror = QueryFormatSupported( deviceInfo, waveFormatExQueryFunction, winMmeDeviceId, maxChannels, sampleRate, 0 );
         if( paerror == paNoError )
         {
             deviceInfo->defaultSampleRate = sampleRate;
@@ -626,12 +685,12 @@ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeH
     PaError result = paNoError;
     char *deviceName; /* non-const ptr */
     MMRESULT mmresult;
-    WAVEINCAPSA wic;
+    WAVEINCAPS wic;
     PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo;
     
     *success = 0;
 
-    mmresult = waveInGetDevCapsA( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPSA ) );
+    mmresult = waveInGetDevCaps( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPS ) );
     if( mmresult == MMSYSERR_NOMEM )
     {
         result = paInsufficientMemory;
@@ -651,25 +710,27 @@ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeH
     {
         /* Append I/O suffix to WAVE_MAPPER device. */
         deviceName = (char *)PaUtil_GroupAllocateMemory(
-                    winMmeHostApi->allocations, strlen( wic.szPname ) + 1 + sizeof(constInputMapperSuffix_) );
+                    winMmeHostApi->allocations,
+					(long) (StrTLen( wic.szPname ) + 1 + sizeof(constInputMapperSuffix_)) );
         if( !deviceName )
         {
             result = paInsufficientMemory;
             goto error;
         }
-        strcpy( deviceName, wic.szPname );
+        StrTCpyToC( deviceName, wic.szPname );
         strcat( deviceName, constInputMapperSuffix_ );
     }
     else
     {
         deviceName = (char*)PaUtil_GroupAllocateMemory(
-                    winMmeHostApi->allocations, strlen( wic.szPname ) + 1 );
+                    winMmeHostApi->allocations, 
+					(long) (StrTLen( wic.szPname ) + 1) );
         if( !deviceName )
         {
             result = paInsufficientMemory;
             goto error;
         }
-        strcpy( deviceName, wic.szPname  );
+        StrTCpyToC( deviceName, wic.szPname  );
     }
     deviceInfo->name = deviceName;
 
@@ -749,12 +810,15 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme
     PaError result = paNoError;
     char *deviceName; /* non-const ptr */
     MMRESULT mmresult;
-    WAVEOUTCAPSA woc;
+    WAVEOUTCAPS woc;
     PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo;
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+    int wdmksDeviceOutputChannelCountIsKnown;
+#endif
 
     *success = 0;
 
-    mmresult = waveOutGetDevCapsA( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPSA ) );
+    mmresult = waveOutGetDevCaps( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPS ) );
     if( mmresult == MMSYSERR_NOMEM )
     {
         result = paInsufficientMemory;
@@ -774,25 +838,27 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme
     {
         /* Append I/O suffix to WAVE_MAPPER device. */
         deviceName = (char *)PaUtil_GroupAllocateMemory(
-                    winMmeHostApi->allocations, strlen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_) );
+                    winMmeHostApi->allocations, 
+					(long) (StrTLen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_)) );
         if( !deviceName )
         {
             result = paInsufficientMemory;
             goto error;
         }
-        strcpy( deviceName, woc.szPname );
+        StrTCpyToC( deviceName, woc.szPname );
         strcat( deviceName, constOutputMapperSuffix_ );
     }
     else
     {
         deviceName = (char*)PaUtil_GroupAllocateMemory(
-                    winMmeHostApi->allocations, strlen( woc.szPname ) + 1 );
+                    winMmeHostApi->allocations, 
+					(long) (StrTLen( woc.szPname ) + 1) );
         if( !deviceName )
         {
             result = paInsufficientMemory;
             goto error;
         }
-        strcpy( deviceName, woc.szPname  );
+        StrTCpyToC( deviceName, woc.szPname  );
     }
     deviceInfo->name = deviceName;
 
@@ -816,8 +882,10 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme
     }
 
 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
-    winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 
-            QueryWaveOutKSFilterMaxChannels( winMmeOutputDeviceId, &deviceInfo->maxOutputChannels );
+    wdmksDeviceOutputChannelCountIsKnown = QueryWaveOutKSFilterMaxChannels( 
+			winMmeOutputDeviceId, &deviceInfo->maxOutputChannels );
+    if( wdmksDeviceOutputChannelCountIsKnown && !winMmeDeviceInfo->deviceOutputChannelCountIsKnown )
+        winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 1;
 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
 
     winMmeDeviceInfo->dwFormats = woc.dwFormats;
@@ -906,11 +974,11 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
     /* the following calls assume that if wave*Message fails the preferred device parameter won't be modified */
     preferredDeviceStatusFlags = 0;
     waveInPreferredDevice = -1;
-    waveInMessage( (HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD)&waveInPreferredDevice, (DWORD)&preferredDeviceStatusFlags );
+    waveInMessage( (HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveInPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags );
 
     preferredDeviceStatusFlags = 0;
     waveOutPreferredDevice = -1;
-    waveOutMessage( (HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD)&waveOutPreferredDevice, (DWORD)&preferredDeviceStatusFlags );
+    waveOutMessage( (HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveOutPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags );
 
     maximumPossibleDeviceCount = 0;
 
@@ -1170,7 +1238,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 
                 /* test for valid sample rate, see comment above */
                 winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputStreamInfo->devices[i].device );
-                paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, winMmeInputDeviceId, inputStreamInfo->devices[i].channelCount, sampleRate );
+                paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, 
+                        winMmeInputDeviceId, inputStreamInfo->devices[i].channelCount, sampleRate, 
+                        ((inputStreamInfo) ? inputStreamInfo->flags : 0) );
                 if( paerror != paNoError )
                     return paInvalidSampleRate;
             }
@@ -1192,7 +1262,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 
             /* test for valid sample rate, see comment above */
             winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputParameters->device );
-            paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, winMmeInputDeviceId, inputChannelCount, sampleRate );
+            paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx, 
+                    winMmeInputDeviceId, inputChannelCount, sampleRate,
+                    ((inputStreamInfo) ? inputStreamInfo->flags : 0) );
             if( paerror != paNoError )
                 return paInvalidSampleRate;
         }
@@ -1230,7 +1302,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 
                 /* test for valid sample rate, see comment above */
                 winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputStreamInfo->devices[i].device );
-                paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, winMmeOutputDeviceId, outputStreamInfo->devices[i].channelCount, sampleRate );
+                paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, 
+                        winMmeOutputDeviceId, outputStreamInfo->devices[i].channelCount, sampleRate,
+                        ((outputStreamInfo) ? outputStreamInfo->flags : 0) );
                 if( paerror != paNoError )
                     return paInvalidSampleRate;
             }
@@ -1252,7 +1326,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 
             /* test for valid sample rate, see comment above */
             winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputParameters->device );
-            paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, winMmeOutputDeviceId, outputChannelCount, sampleRate );
+            paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx, 
+                    winMmeOutputDeviceId, outputChannelCount, sampleRate,
+                    ((outputStreamInfo) ? outputStreamInfo->flags : 0) );
             if( paerror != paNoError )
                 return paInvalidSampleRate;
         }
@@ -1273,126 +1349,194 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 }
 
 
-
-static void SelectBufferSizeAndCount( unsigned long baseBufferSize,
-    unsigned long requestedLatency,
-    unsigned long baseBufferCount, unsigned long minimumBufferCount,
-    unsigned long maximumBufferSize, unsigned long *hostBufferSize,
-    unsigned long *hostBufferCount )
+static unsigned long ComputeHostBufferCountForFixedBufferSizeFrames(
+        unsigned long suggestedLatencyFrames,
+        unsigned long hostBufferSizeFrames,
+        unsigned long minimumBufferCount )
 {
-    unsigned long sizeMultiplier, bufferCount, latency;
-    unsigned long nextLatency, nextBufferSize;
-    int baseBufferSizeIsPowerOfTwo;
-    
-    sizeMultiplier = 1;
-    bufferCount = baseBufferCount;
+    /* Calculate the number of buffers of length hostFramesPerBuffer 
+       that fit in suggestedLatencyFrames, rounding up to the next integer.
 
-    /* count-1 below because latency is always determined by one less
-        than the total number of buffers.
+       The value (hostBufferSizeFrames - 1) below is to ensure the buffer count is rounded up.
     */
-    latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1);
+    unsigned long resultBufferCount = ((suggestedLatencyFrames + (hostBufferSizeFrames - 1)) / hostBufferSizeFrames);
 
-    if( latency > requestedLatency )
-    {
+    /* We always need one extra buffer for processing while the rest are queued/playing.
+       i.e. latency is framesPerBuffer * (bufferCount - 1)
+    */
+    resultBufferCount += 1;
 
-        /* reduce number of buffers without falling below suggested latency */
+    if( resultBufferCount < minimumBufferCount ) /* clamp to minimum buffer count */
+        resultBufferCount = minimumBufferCount;
 
-        nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2);
-        while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency )
-        {
-            --bufferCount;
-            nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2);
-        }
+    return resultBufferCount;
+}
 
-    }else if( latency < requestedLatency ){
 
-        baseBufferSizeIsPowerOfTwo = (! (baseBufferSize & (baseBufferSize - 1)));
-        if( baseBufferSizeIsPowerOfTwo ){
+static unsigned long ComputeHostBufferSizeGivenHardUpperLimit( 
+        unsigned long userFramesPerBuffer,
+        unsigned long absoluteMaximumBufferSizeFrames )
+{
+    static unsigned long primes_[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 
+            29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 0 }; /* zero terminated */
 
-            /* double size of buffers without exceeding requestedLatency */
+    unsigned long result = userFramesPerBuffer;
+    int i;
 
-            nextBufferSize = (baseBufferSize * (sizeMultiplier*2));
-            nextLatency = nextBufferSize * (bufferCount-1);
-            while( nextBufferSize <= maximumBufferSize
-                    && nextLatency < requestedLatency )
-            {
-                sizeMultiplier *= 2;
-                nextBufferSize = (baseBufferSize * (sizeMultiplier*2));
-                nextLatency = nextBufferSize * (bufferCount-1);
-            }   
+    assert( absoluteMaximumBufferSizeFrames > 67 ); /* assume maximum is large and we're only factoring by small primes */
 
-        }else{
+    /* search for the largest integer factor of userFramesPerBuffer less 
+       than or equal to absoluteMaximumBufferSizeFrames */
 
-            /* increase size of buffers upto first excess of requestedLatency */
+    /* repeatedly divide by smallest prime factors until a buffer size 
+       smaller than absoluteMaximumBufferSizeFrames is found */
+    while( result > absoluteMaximumBufferSizeFrames ){
 
-            nextBufferSize = (baseBufferSize * (sizeMultiplier+1));
-            nextLatency = nextBufferSize * (bufferCount-1);
-            while( nextBufferSize <= maximumBufferSize
-                    && nextLatency < requestedLatency )
+        /* search for the smallest prime factor of result */
+        for( i=0; primes_[i] != 0; ++i ) 
+        {
+            unsigned long p = primes_[i];
+            unsigned long divided = result / p;
+            if( divided*p == result )
             {
-                ++sizeMultiplier;
-                nextBufferSize = (baseBufferSize * (sizeMultiplier+1));
-                nextLatency = nextBufferSize * (bufferCount-1);
+                result = divided;
+                break; /* continue with outer while loop */
             }
-
-            if( nextLatency < requestedLatency )
-                ++sizeMultiplier;            
         }
-
-        /* increase number of buffers until requestedLatency is reached */
-
-        latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1);
-        while( latency < requestedLatency )
-        {
-            ++bufferCount;
-            latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1);
+        if( primes_[i] == 0 )
+        { /* loop failed to find a prime factor, return an approximate result */
+            unsigned long d = (userFramesPerBuffer + (absoluteMaximumBufferSizeFrames-1))
+                    / absoluteMaximumBufferSizeFrames;
+            return userFramesPerBuffer / d;
         }
     }
 
-    *hostBufferSize = baseBufferSize * sizeMultiplier;
-    *hostBufferCount = bufferCount;
+    return result;
 }
 
 
-static void ReselectBufferCount( unsigned long bufferSize,
-    unsigned long requestedLatency,
-    unsigned long baseBufferCount, unsigned long minimumBufferCount,
-    unsigned long *hostBufferCount )
+static PaError SelectHostBufferSizeFramesAndHostBufferCount(
+        unsigned long suggestedLatencyFrames,
+        unsigned long userFramesPerBuffer,
+        unsigned long minimumBufferCount,
+        unsigned long preferredMaximumBufferSizeFrames, /* try not to exceed this. for example, don't exceed when coalescing buffers */
+        unsigned long absoluteMaximumBufferSizeFrames,  /* never exceed this, a hard limit */
+        unsigned long *hostBufferSizeFrames,
+        unsigned long *hostBufferCount )
 {
-    unsigned long bufferCount, latency;
-    unsigned long nextLatency;
+    unsigned long effectiveUserFramesPerBuffer;
+    unsigned long numberOfUserBuffersPerHostBuffer;
 
-    bufferCount = baseBufferCount;
 
-    /* count-1 below because latency is always determined by one less
-        than the total number of buffers.
-    */
-    latency = bufferSize * (bufferCount-1);
+    if( userFramesPerBuffer == paFramesPerBufferUnspecified ){
+
+        effectiveUserFramesPerBuffer = PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_;
+
+    }else{
+
+        if( userFramesPerBuffer > absoluteMaximumBufferSizeFrames ){
+
+            /* user has requested a user buffer that's larger than absoluteMaximumBufferSizeFrames.
+               try to choose a buffer size that is equal or smaller than absoluteMaximumBufferSizeFrames
+               but is also an integer factor of userFramesPerBuffer, so as to distribute computation evenly.
+               the buffer processor will handle the block adaption between host and user buffer sizes.
+               see http://www.portaudio.com/trac/ticket/189 for discussion.
+            */
+
+            effectiveUserFramesPerBuffer = ComputeHostBufferSizeGivenHardUpperLimit( userFramesPerBuffer, absoluteMaximumBufferSizeFrames );
+            assert( effectiveUserFramesPerBuffer <= absoluteMaximumBufferSizeFrames );
+
+            /* try to ensure that duration of host buffering is at least as 
+                large as duration of user buffer. */
+            if( suggestedLatencyFrames < userFramesPerBuffer )
+                suggestedLatencyFrames = userFramesPerBuffer; 
+
+        }else{
+
+            effectiveUserFramesPerBuffer = userFramesPerBuffer;
+        }
+    }
+                        
+    /* compute a host buffer count based on suggestedLatencyFrames and our granularity */
 
-    if( latency > requestedLatency )
+    *hostBufferSizeFrames = effectiveUserFramesPerBuffer;
+
+    *hostBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+            suggestedLatencyFrames, *hostBufferSizeFrames, minimumBufferCount );
+
+    if( *hostBufferSizeFrames >= userFramesPerBuffer )
     {
-        /* reduce number of buffers without falling below suggested latency */
+        /*
+            If there are too many host buffers we would like to coalesce 
+            them by packing an integer number of user buffers into each host buffer.
+            We try to coalesce such that hostBufferCount will lie between 
+            PA_MME_TARGET_HOST_BUFFER_COUNT_ and (PA_MME_TARGET_HOST_BUFFER_COUNT_*2)-1.
+            We limit coalescing to avoid exceeding either absoluteMaximumBufferSizeFrames and
+            preferredMaximumBufferSizeFrames. 
+
+            First, compute a coalescing factor: the number of user buffers per host buffer.
+            The goal is to achieve PA_MME_TARGET_HOST_BUFFER_COUNT_ total buffer count.
+            Since our latency is computed based on (*hostBufferCount - 1) we compute a
+            coalescing factor based on (*hostBufferCount - 1) and (PA_MME_TARGET_HOST_BUFFER_COUNT_-1).
 
-        nextLatency = bufferSize * (bufferCount-2);
-        while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency )
+            The + (PA_MME_TARGET_HOST_BUFFER_COUNT_-2) term below is intended to round up.
+        */
+        numberOfUserBuffersPerHostBuffer = ((*hostBufferCount - 1) + (PA_MME_TARGET_HOST_BUFFER_COUNT_-2)) / (PA_MME_TARGET_HOST_BUFFER_COUNT_ - 1);
+        
+        if( numberOfUserBuffersPerHostBuffer > 1 )
         {
-            --bufferCount;
-            nextLatency = bufferSize * (bufferCount-2);
+            unsigned long maxCoalescedBufferSizeFrames = (absoluteMaximumBufferSizeFrames < preferredMaximumBufferSizeFrames) /* minimum of our limits */
+                            ? absoluteMaximumBufferSizeFrames
+                            : preferredMaximumBufferSizeFrames;
+
+            unsigned long maxUserBuffersPerHostBuffer = maxCoalescedBufferSizeFrames / effectiveUserFramesPerBuffer; /* don't coalesce more than this */
+
+            if( numberOfUserBuffersPerHostBuffer > maxUserBuffersPerHostBuffer )
+                numberOfUserBuffersPerHostBuffer = maxUserBuffersPerHostBuffer;
+
+            *hostBufferSizeFrames = effectiveUserFramesPerBuffer * numberOfUserBuffersPerHostBuffer;
+
+            /* recompute hostBufferCount to approximate suggestedLatencyFrames now that hostBufferSizeFrames is larger */
+            *hostBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                    suggestedLatencyFrames, *hostBufferSizeFrames, minimumBufferCount );
         }
+    }
 
-    }else if( latency < requestedLatency ){
+    return paNoError;
+}
 
-        /* increase number of buffers until requestedLatency is reached */
 
-        latency = bufferSize * (bufferCount-1);
-        while( latency < requestedLatency )
+static PaError CalculateMaxHostSampleFrameSizeBytes(
+        int channelCount,
+        PaSampleFormat hostSampleFormat,
+        const PaWinMmeStreamInfo *streamInfo,
+        int *hostSampleFrameSizeBytes )
+{
+    unsigned int i;
+    /* PA WMME streams may aggregate multiple WMME devices. When the stream addresses 
+       more than one device in a single direction, maxDeviceChannelCount is the maximum 
+       number of channels used by a single device.
+    */
+    int maxDeviceChannelCount = channelCount;
+    int hostSampleSizeBytes = Pa_GetSampleSize( hostSampleFormat );
+    if( hostSampleSizeBytes < 0 )
+    {
+        return hostSampleSizeBytes; /* the value of hostSampleSize here is an error code, not a sample size */
+    }
+
+    if( streamInfo && ( streamInfo->flags & paWinMmeUseMultipleDevices ) )
+    {
+        maxDeviceChannelCount = streamInfo->devices[0].channelCount;
+        for( i=1; i< streamInfo->deviceCount; ++i )
         {
-            ++bufferCount;
-            latency = bufferSize * (bufferCount-1);
-        }                                                         
+            if( streamInfo->devices[i].channelCount > maxDeviceChannelCount )
+                maxDeviceChannelCount = streamInfo->devices[i].channelCount;
+        }
     }
 
-    *hostBufferCount = bufferCount;
+    *hostSampleFrameSizeBytes = hostSampleSizeBytes * maxDeviceChannelCount;
+
+    return paNoError;
 }
 
 
@@ -1402,51 +1546,29 @@ static void ReselectBufferCount( unsigned long bufferSize,
 */
 
 static PaError CalculateBufferSettings(
-        unsigned long *framesPerHostInputBuffer, unsigned long *hostInputBufferCount,
-        unsigned long *framesPerHostOutputBuffer, unsigned long *hostOutputBufferCount,
+        unsigned long *hostFramesPerInputBuffer, unsigned long *hostInputBufferCount,
+        unsigned long *hostFramesPerOutputBuffer, unsigned long *hostOutputBufferCount,
         int inputChannelCount, PaSampleFormat hostInputSampleFormat,
-        PaTime suggestedInputLatency, PaWinMmeStreamInfo *inputStreamInfo,
+        PaTime suggestedInputLatency, const PaWinMmeStreamInfo *inputStreamInfo,
         int outputChannelCount, PaSampleFormat hostOutputSampleFormat,
-        PaTime suggestedOutputLatency, PaWinMmeStreamInfo *outputStreamInfo,
-        double sampleRate, unsigned long framesPerBuffer )
+        PaTime suggestedOutputLatency, const PaWinMmeStreamInfo *outputStreamInfo,
+        double sampleRate, unsigned long userFramesPerBuffer )
 {
     PaError result = paNoError;
-    int effectiveInputChannelCount, effectiveOutputChannelCount;
-    int hostInputFrameSize = 0;
-    unsigned int i;
     
-    if( inputChannelCount > 0 )
+    if( inputChannelCount > 0 ) /* stream has input */
     {
-        int hostInputSampleSize = Pa_GetSampleSize( hostInputSampleFormat );
-        if( hostInputSampleSize < 0 )
-        {
-            result = hostInputSampleSize;
+        int hostInputFrameSizeBytes;
+        result = CalculateMaxHostSampleFrameSizeBytes( 
+                inputChannelCount, hostInputSampleFormat, inputStreamInfo, &hostInputFrameSizeBytes );
+        if( result != paNoError )
             goto error;
-        }
-
-        if( inputStreamInfo
-                && ( inputStreamInfo->flags & paWinMmeUseMultipleDevices ) )
-        {
-            /* set effectiveInputChannelCount to the largest number of
-                channels on any one device.
-            */
-            effectiveInputChannelCount = 0;
-            for( i=0; i< inputStreamInfo->deviceCount; ++i )
-            {
-                if( inputStreamInfo->devices[i].channelCount > effectiveInputChannelCount )
-                    effectiveInputChannelCount = inputStreamInfo->devices[i].channelCount;
-            }
-        }
-        else
-        {
-            effectiveInputChannelCount = inputChannelCount;
-        }
-
-        hostInputFrameSize = hostInputSampleSize * effectiveInputChannelCount;
 
         if( inputStreamInfo
                 && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
         {
+            /* input - using low level latency parameters if provided */
+
             if( inputStreamInfo->bufferCount <= 0
                     || inputStreamInfo->framesPerBuffer <= 0 )
             {
@@ -1454,46 +1576,45 @@ static PaError CalculateBufferSettings(
                 goto error;
             }
 
-            *framesPerHostInputBuffer = inputStreamInfo->framesPerBuffer;
+            *hostFramesPerInputBuffer = inputStreamInfo->framesPerBuffer;
             *hostInputBufferCount = inputStreamInfo->bufferCount;
         }
         else
         {
-            unsigned long hostBufferSizeBytes, hostBufferCount;
+            /* input - not using low level latency parameters, so compute 
+               hostFramesPerInputBuffer and hostInputBufferCount
+               based on userFramesPerBuffer and suggestedInputLatency. */
+
             unsigned long minimumBufferCount = (outputChannelCount > 0)
                     ? PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_
                     : PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_;
 
-            unsigned long maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostInputFrameSize);
-            if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ )
-                maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_;
-
-            /* compute the following in bytes, then convert back to frames */
-
-            SelectBufferSizeAndCount(
-                ((framesPerBuffer == paFramesPerBufferUnspecified)
-                    ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_
-                    : framesPerBuffer ) * hostInputFrameSize, /* baseBufferSize */
-                ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */
-                4, /* baseBufferCount */
-                minimumBufferCount, maximumBufferSize,
-                &hostBufferSizeBytes, &hostBufferCount );
-
-            *framesPerHostInputBuffer = hostBufferSizeBytes / hostInputFrameSize;
-            *hostInputBufferCount = hostBufferCount;
+            result = SelectHostBufferSizeFramesAndHostBufferCount(
+                    (unsigned long)(suggestedInputLatency * sampleRate), /* (truncate) */
+                    userFramesPerBuffer,
+                    minimumBufferCount,
+                    (unsigned long)(PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate), /* in frames. preferred maximum */
+                    (PA_MME_MAX_HOST_BUFFER_BYTES_ / hostInputFrameSizeBytes),  /* in frames. a hard limit. note truncation due to 
+                                                                                division is intentional here to limit max bytes */
+                    hostFramesPerInputBuffer,
+                    hostInputBufferCount );
+            if( result != paNoError )
+                goto error;
         }
     }
     else
     {
-        *framesPerHostInputBuffer = 0;
+        *hostFramesPerInputBuffer = 0;
         *hostInputBufferCount = 0;
     }
 
-    if( outputChannelCount > 0 )
+    if( outputChannelCount > 0 ) /* stream has output */
     {
         if( outputStreamInfo
                 && ( outputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
         {
+            /* output - using low level latency parameters */
+
             if( outputStreamInfo->bufferCount <= 0
                     || outputStreamInfo->framesPerBuffer <= 0 )
             {
@@ -1501,24 +1622,25 @@ static PaError CalculateBufferSettings(
                 goto error;
             }
 
-            *framesPerHostOutputBuffer = outputStreamInfo->framesPerBuffer;
+            *hostFramesPerOutputBuffer = outputStreamInfo->framesPerBuffer;
             *hostOutputBufferCount = outputStreamInfo->bufferCount;
 
-            
             if( inputChannelCount > 0 ) /* full duplex */
             {
-                if( *framesPerHostInputBuffer != *framesPerHostOutputBuffer )
+                /* harmonize hostFramesPerInputBuffer and hostFramesPerOutputBuffer */
+
+                if( *hostFramesPerInputBuffer != *hostFramesPerOutputBuffer )
                 {
                     if( inputStreamInfo
                             && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
                     { 
                         /* a custom StreamInfo was used for specifying both input
-                            and output buffer sizes, the larger buffer size
+                            and output buffer sizes. We require that the larger buffer size
                             must be a multiple of the smaller buffer size */
 
-                        if( *framesPerHostInputBuffer < *framesPerHostOutputBuffer )
+                        if( *hostFramesPerInputBuffer < *hostFramesPerOutputBuffer )
                         {
-                            if( *framesPerHostOutputBuffer % *framesPerHostInputBuffer != 0 )
+                            if( *hostFramesPerOutputBuffer % *hostFramesPerInputBuffer != 0 )
                             {
                                 result = paIncompatibleHostApiSpecificStreamInfo;
                                 goto error;
@@ -1526,8 +1648,8 @@ static PaError CalculateBufferSettings(
                         }
                         else
                         {
-                            assert( *framesPerHostInputBuffer > *framesPerHostOutputBuffer );
-                            if( *framesPerHostInputBuffer % *framesPerHostOutputBuffer != 0 )
+                            assert( *hostFramesPerInputBuffer > *hostFramesPerOutputBuffer );
+                            if( *hostFramesPerInputBuffer % *hostFramesPerOutputBuffer != 0 )
                             {
                                 result = paIncompatibleHostApiSpecificStreamInfo;
                                 goto error;
@@ -1537,111 +1659,72 @@ static PaError CalculateBufferSettings(
                     else
                     {
                         /* a custom StreamInfo was not used for specifying the input buffer size,
-                            so use the output buffer size, and approximately the same latency. */
+                            so use the output buffer size, and approximately the suggested input latency. */
 
-                        *framesPerHostInputBuffer = *framesPerHostOutputBuffer;
-                        *hostInputBufferCount = (((unsigned long)(suggestedInputLatency * sampleRate)) / *framesPerHostInputBuffer) + 1;
+                        *hostFramesPerInputBuffer = *hostFramesPerOutputBuffer;
 
-                        if( *hostInputBufferCount < PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ )
-                            *hostInputBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_;
+                        *hostInputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                                (unsigned long)(suggestedInputLatency * sampleRate), 
+                                *hostFramesPerInputBuffer, 
+                                PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ );
                     }
                 }
             }
         }
         else
         {
-            unsigned long hostBufferSizeBytes, hostBufferCount;
-            unsigned long minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_;
-            unsigned long maximumBufferSize;
-            int hostOutputFrameSize;
-            int hostOutputSampleSize;
-
-            hostOutputSampleSize = Pa_GetSampleSize( hostOutputSampleFormat );
-            if( hostOutputSampleSize < 0 )
-            {
-                result = hostOutputSampleSize;
-                goto error;
-            }
+            /* output - no low level latency parameters, so compute hostFramesPerOutputBuffer and hostOutputBufferCount
+                based on userFramesPerBuffer and suggestedOutputLatency. */
 
-            if( outputStreamInfo
-                && ( outputStreamInfo->flags & paWinMmeUseMultipleDevices ) )
-            {
-                /* set effectiveOutputChannelCount to the largest number of
-                    channels on any one device.
-                */
-                effectiveOutputChannelCount = 0;
-                for( i=0; i< outputStreamInfo->deviceCount; ++i )
-                {
-                    if( outputStreamInfo->devices[i].channelCount > effectiveOutputChannelCount )
-                        effectiveOutputChannelCount = outputStreamInfo->devices[i].channelCount;
-                }
-            }
-            else
-            {
-                effectiveOutputChannelCount = outputChannelCount;
-            }
-
-            hostOutputFrameSize = hostOutputSampleSize * effectiveOutputChannelCount;
-            
-            maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostOutputFrameSize);
-            if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ )
-                maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_;
-
-
-            /* compute the following in bytes, then convert back to frames */
-
-            SelectBufferSizeAndCount(
-                ((framesPerBuffer == paFramesPerBufferUnspecified)
-                    ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_
-                    : framesPerBuffer ) * hostOutputFrameSize, /* baseBufferSize */
-                ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */
-                4, /* baseBufferCount */
-                minimumBufferCount,
-                maximumBufferSize,
-                &hostBufferSizeBytes, &hostBufferCount );
-
-            *framesPerHostOutputBuffer = hostBufferSizeBytes / hostOutputFrameSize;
-            *hostOutputBufferCount = hostBufferCount;
+            int hostOutputFrameSizeBytes;
+            result = CalculateMaxHostSampleFrameSizeBytes( 
+                    outputChannelCount, hostOutputSampleFormat, outputStreamInfo, &hostOutputFrameSizeBytes );
+            if( result != paNoError )
+                goto error;
 
+            /* compute the output buffer size and count */
+
+            result = SelectHostBufferSizeFramesAndHostBufferCount(
+                    (unsigned long)(suggestedOutputLatency * sampleRate), /* (truncate) */
+                    userFramesPerBuffer,
+                    PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_,
+                    (unsigned long)(PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate), /* in frames. preferred maximum */
+                    (PA_MME_MAX_HOST_BUFFER_BYTES_ / hostOutputFrameSizeBytes),  /* in frames. a hard limit. note truncation due to 
+                                                                                 division is intentional here to limit max bytes */
+                    hostFramesPerOutputBuffer,
+                    hostOutputBufferCount );
+            if( result != paNoError )
+                goto error;
 
-            if( inputChannelCount > 0 )
+            if( inputChannelCount > 0 ) /* full duplex */
             {
+                /* harmonize hostFramesPerInputBuffer and hostFramesPerOutputBuffer */
+
                 /* ensure that both input and output buffer sizes are the same.
                     if they don't match at this stage, choose the smallest one
-                    and use that for input and output
+                    and use that for input and output and recompute the corresponding
+                    buffer count accordingly.
                 */
 
-                if( *framesPerHostOutputBuffer != *framesPerHostInputBuffer )
+                if( *hostFramesPerOutputBuffer != *hostFramesPerInputBuffer )
                 {
-                    if( framesPerHostInputBuffer < framesPerHostOutputBuffer )
+                    if( hostFramesPerInputBuffer < hostFramesPerOutputBuffer )
                     {
-                        unsigned long framesPerHostBuffer = *framesPerHostInputBuffer;
-                        
-                        minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_;
-                        ReselectBufferCount(
-                            framesPerHostBuffer * hostOutputFrameSize, /* bufferSize */
-                            ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */
-                            4, /* baseBufferCount */
-                            minimumBufferCount,
-                            &hostBufferCount );
-
-                        *framesPerHostOutputBuffer = framesPerHostBuffer;
-                        *hostOutputBufferCount = hostBufferCount;
+                        *hostFramesPerOutputBuffer = *hostFramesPerInputBuffer;
+
+                        *hostOutputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                                (unsigned long)(suggestedOutputLatency * sampleRate), 
+                                *hostOutputBufferCount, 
+                                PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ );
                     }
                     else
                     {
-                        unsigned long framesPerHostBuffer = *framesPerHostOutputBuffer;
-                        
-                        minimumBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_;
-                        ReselectBufferCount(
-                            framesPerHostBuffer * hostInputFrameSize, /* bufferSize */
-                            ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */
-                            4, /* baseBufferCount */
-                            minimumBufferCount,
-                            &hostBufferCount );
-
-                        *framesPerHostInputBuffer = framesPerHostBuffer;
-                        *hostInputBufferCount = hostBufferCount;
+                        *hostFramesPerInputBuffer = *hostFramesPerOutputBuffer;
+
+                        *hostInputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                                (unsigned long)(suggestedInputLatency * sampleRate), 
+                                *hostFramesPerInputBuffer, 
+                                PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ );
                     }
                 }   
             }
@@ -1649,7 +1732,7 @@ static PaError CalculateBufferSettings(
     }
     else
     {
-        *framesPerHostOutputBuffer = 0;
+        *hostFramesPerOutputBuffer = 0;
         *hostOutputBufferCount = 0;
     }
 
@@ -1676,6 +1759,7 @@ typedef struct
 static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers );
 static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
         PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
+        unsigned long winMmeSpecificFlags,
         unsigned long bytesPerHostSample,
         double sampleRate, PaWinMmeDeviceAndChannelCount *devices,
         unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput );
@@ -1700,6 +1784,7 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH
 
 static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
         PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
+        unsigned long winMmeSpecificFlags,
         unsigned long bytesPerHostSample,
         double sampleRate, PaWinMmeDeviceAndChannelCount *devices,
         unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput )
@@ -1707,6 +1792,8 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA
     PaError result;
     MMRESULT mmresult;
     signed int i, j;
+    PaSampleFormat sampleFormat;
+    int waveFormatTag;
 
     /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers()
         has already been called to zero some fields */       
@@ -1734,6 +1821,10 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA
             ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0;
     }
 
+    /* @todo at the moment we only use 16 bit sample format */
+    sampleFormat = paInt16;
+    waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags );
+
     for( i = 0; i < (signed int)deviceCount; ++i )
     {
         PaWinWaveFormat waveFormat;
@@ -1744,21 +1835,21 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA
 
         for( j = 0; j < 2; ++j )
         {
-            if( j == 0 )
-            { 
-                /* first, attempt to open the device using WAVEFORMATEXTENSIBLE, 
-                    if this fails we fall back to WAVEFORMATEX */
+            switch(j){
+                case 0:     
+                    /* first, attempt to open the device using WAVEFORMATEXTENSIBLE, 
+                        if this fails we fall back to WAVEFORMATEX */
 
-                /* @todo at the moment we only use 16 bit sample format */
-                PaWin_InitializeWaveFormatExtensible( &waveFormat, devices[i].channelCount, 
-                        paInt16, sampleRate, channelMask );
-
-            }
-            else
-            {
-                /* retry with WAVEFORMATEX */
+                    PaWin_InitializeWaveFormatExtensible( &waveFormat, devices[i].channelCount, 
+                            sampleFormat, waveFormatTag, sampleRate, channelMask );
+                    break;
+                
+                case 1:
+                    /* retry with WAVEFORMATEX */
 
-                PaWin_InitializeWaveFormatEx( &waveFormat, devices[i].channelCount, paInt16, sampleRate );
+                    PaWin_InitializeWaveFormatEx( &waveFormat, devices[i].channelCount, 
+                            sampleFormat, waveFormatTag, sampleRate );
+                    break;
             }
 
             /* REVIEW: consider not firing an event for input when a full duplex
@@ -2042,7 +2133,7 @@ struct PaWinMmeStream
     /* Processing thread management -------------- */
     HANDLE abortEvent;
     HANDLE processingThread;
-    DWORD processingThreadId;
+    PA_THREAD_ID processingThreadId;
 
     char throttleProcessingThreadOnOverload; /* 0 -> don't throtte, non-0 -> throttle */
     int processingThreadPriority;
@@ -2063,6 +2154,7 @@ struct PaWinMmeStream
 static PaError ValidateWinMmeSpecificStreamInfo(
         const PaStreamParameters *streamParameters,
         const PaWinMmeStreamInfo *streamInfo,
+        unsigned long *winMmeSpecificFlags,
         char *throttleProcessingThreadOnOverload,
         unsigned long *deviceCount )
 {
@@ -2074,6 +2166,8 @@ static PaError ValidateWinMmeSpecificStreamInfo(
 	        return paIncompatibleHostApiSpecificStreamInfo;
 	    }
 
+        *winMmeSpecificFlags = streamInfo->flags;
+
 	    if( streamInfo->flags & paWinMmeDontThrottleOverloadedProcessingThread )
 	        *throttleProcessingThreadOnOverload = 0;
             
@@ -2217,8 +2311,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     unsigned long hostOutputBufferCount;
     unsigned long framesPerBufferProcessorCall;
     PaWinMmeDeviceAndChannelCount *inputDevices = 0;  /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */
+    unsigned long winMmeSpecificInputFlags = 0;
     unsigned long inputDeviceCount = 0;            
     PaWinMmeDeviceAndChannelCount *outputDevices = 0;
+    unsigned long winMmeSpecificOutputFlags = 0;
     unsigned long outputDeviceCount = 0;                /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */
     char throttleProcessingThreadOnOverload = 1;
 
@@ -2234,6 +2330,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 		/* validate input hostApiSpecificStreamInfo */
         inputStreamInfo = (PaWinMmeStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
 		result = ValidateWinMmeSpecificStreamInfo( inputParameters, inputStreamInfo,
+                &winMmeSpecificInputFlags,
 				&throttleProcessingThreadOnOverload,
 				&inputDeviceCount );
 		if( result != paNoError ) return result;
@@ -2283,6 +2380,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 		/* validate output hostApiSpecificStreamInfo */
         outputStreamInfo = (PaWinMmeStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
 		result = ValidateWinMmeSpecificStreamInfo( outputParameters, outputStreamInfo,
+                &winMmeSpecificOutputFlags,
 				&throttleProcessingThreadOnOverload,
 				&outputDeviceCount );
 		if( result != paNoError ) return result;
@@ -2332,6 +2430,14 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         return paInvalidFlag; /* unexpected platform specific flag */
 
 
+    /* always disable clipping and dithering if we are outputting a raw spdif stream */
+    if( (winMmeSpecificOutputFlags & paWinMmeWaveFormatDolbyAc3Spdif)
+            || (winMmeSpecificOutputFlags & paWinMmeWaveFormatWmaSpdif) ){
+
+        streamFlags = streamFlags | paClipOff | paDitherOff;
+    }
+
+
     result = CalculateBufferSettings( &framesPerHostInputBuffer, &hostInputBufferCount,
                 &framesPerHostOutputBuffer, &hostOutputBufferCount,
                 inputChannelCount, hostInputSampleFormat, suggestedInputLatency, inputStreamInfo,
@@ -2402,12 +2508,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     
     bufferProcessorIsInitialized = 1;
 
+    /* stream info input latency is the minimum buffering latency (unlike suggested and default which are *maximums*) */
     stream->streamRepresentation.streamInfo.inputLatency =
-            (double)(PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)
-                +(framesPerHostInputBuffer * (hostInputBufferCount-1))) / sampleRate;
+            (double)(PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)
+                + framesPerHostInputBuffer) / sampleRate;
     stream->streamRepresentation.streamInfo.outputLatency =
-            (double)(PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)
-                +(framesPerHostOutputBuffer * (hostOutputBufferCount-1))) / sampleRate;
+            (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
+                + (framesPerHostOutputBuffer * (hostOutputBufferCount-1))) / sampleRate;
     stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
 
     stream->primeStreamUsingCallback = ( (streamFlags&paPrimeOutputBuffersUsingStreamCallback) && streamCallback ) ? 1 : 0;
@@ -2430,6 +2537,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     if( inputParameters )
     {
         result = InitializeWaveHandles( winMmeHostApi, &stream->input,
+                winMmeSpecificInputFlags,
                 stream->bufferProcessor.bytesPerHostInputSample, sampleRate,
                 inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ );
         if( result != paNoError ) goto error;
@@ -2438,6 +2546,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     if( outputParameters )
     {
         result = InitializeWaveHandles( winMmeHostApi, &stream->output,
+                winMmeSpecificOutputFlags,
                 stream->bufferProcessor.bytesPerHostOutputSample, sampleRate,
                 outputDevices, outputDeviceCount, outputChannelMask, 0 /* isInput */ );
         if( result != paNoError ) goto error;
@@ -2691,7 +2800,7 @@ static PaError CatchUpOutputBuffers( PaWinMmeStream *stream )
 }
 
 
-static DWORD WINAPI ProcessingThreadProc( void *pArg )
+PA_THREAD_FUNC ProcessingThreadProc( void *pArg )
 {
     PaWinMmeStream *stream = (PaWinMmeStream *)pArg;
     HANDLE events[3];
@@ -2734,7 +2843,7 @@ static DWORD WINAPI ProcessingThreadProc( void *pArg )
         if( waitResult == WAIT_FAILED )
         {
             result = paUnanticipatedHostError;
-            /** @todo FIXME/REVIEW: can't return host error info from an asyncronous thread */
+            /** @todo FIXME/REVIEW: can't return host error info from an asyncronous thread. see http://www.portaudio.com/trac/ticket/143 */
             done = 1;
         }
         else if( waitResult == WAIT_TIMEOUT )
@@ -2787,7 +2896,7 @@ static DWORD WINAPI ProcessingThreadProc( void *pArg )
                                we discard all but the most recent. This is an
                                input buffer overflow. FIXME: these buffers should
                                be passed to the callback in a paNeverDropInput
-                               stream.
+                               stream. http://www.portaudio.com/trac/ticket/142
 
                                note that it is also possible for an input overflow
                                to happen while the callback is processing a buffer.
@@ -2946,7 +3055,9 @@ static DWORD WINAPI ProcessingThreadProc( void *pArg )
                     {
                         stream->abortProcessing = 1;
                         done = 1;
-                        /** @todo FIXME: should probably reset the output device immediately once the callback returns paAbort */
+                        /** @todo FIXME: should probably reset the output device immediately once the callback returns paAbort 
+                            see: http://www.portaudio.com/trac/ticket/141
+                        */
                         result = paNoError;
                     }
                     else
@@ -3244,7 +3355,7 @@ static PaError StartStream( PaStream *s )
         if( result != paNoError ) goto error;
 
         /* Create thread that waits for audio buffers to be ready for processing. */
-        stream->processingThread = CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId );
+        stream->processingThread = CREATE_THREAD;
         if( !stream->processingThread )
         {
             result = paUnanticipatedHostError;
@@ -3600,7 +3711,7 @@ static PaError ReadStream( PaStream* s,
         }
         else
         {
-            userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.inputChannelCount );
+            userBuffer = (void*)alloca( sizeof(void*) * stream->bufferProcessor.inputChannelCount );
             if( !userBuffer )
                 return paInsufficientMemory;
             for( i = 0; i<stream->bufferProcessor.inputChannelCount; ++i )
@@ -3614,7 +3725,9 @@ static PaError ReadStream( PaStream* s,
                 {
                     /** @todo REVIEW: consider what to do if the input overflows.
                         do we requeue all of the buffers? should we be running
-                        a thread to make sure they are always queued? */
+                        a thread to make sure they are always queued? 
+                        see: http://www.portaudio.com/trac/ticket/117
+                        */
 
                     result = paInputOverflowed;
                 }
@@ -3704,7 +3817,7 @@ static PaError WriteStream( PaStream* s,
         }
         else
         {
-            userBuffer = alloca( sizeof(void*) * stream->bufferProcessor.outputChannelCount );
+            userBuffer = (const void*)alloca( sizeof(void*) * stream->bufferProcessor.outputChannelCount );
             if( !userBuffer )
                 return paInsufficientMemory;
             for( i = 0; i<stream->bufferProcessor.outputChannelCount; ++i )
@@ -3719,7 +3832,9 @@ static PaError WriteStream( PaStream* s,
                     /** @todo REVIEW: consider what to do if the output
                     underflows. do we requeue all the existing buffers with
                     zeros? should we run a separate thread to keep the buffers
-                    enqueued at all times? */
+                    enqueued at all times? 
+                    see: http://www.portaudio.com/trac/ticket/117
+                    */
 
                     result = paOutputUnderflowed;
                 }
@@ -3896,8 +4011,4 @@ HWAVEOUT PaWinMME_GetStreamOutputHandle( PaStream* s, int handleIndex )
         return 0;
 }
 
-
-
 #endif
-
-
diff --git a/external/portaudio/pa_win_wmme.h b/external/portaudio/pa_win_wmme.h
index 2e61a52..ac5efe7 100644
--- a/external/portaudio/pa_win_wmme.h
+++ b/external/portaudio/pa_win_wmme.h
@@ -1,7 +1,7 @@
 #ifndef PA_WIN_WMME_H
 #define PA_WIN_WMME_H
 /*
- * $Id: pa_win_wmme.h 1247 2007-08-11 16:29:09Z rossb $
+ * $Id: pa_win_wmme.h 1592 2011-02-04 10:41:58Z rossb $
  * PortAudio Portable Real-Time Audio Library
  * MME specific extensions
  *
@@ -39,10 +39,10 @@
  */
 
 /** @file
+ @ingroup public_header
  @brief WMME-specific PortAudio API extension header file.
 */
 
-
 #include "portaudio.h"
 #include "pa_win_waveformat.h"
 
@@ -52,6 +52,10 @@ extern "C"
 #endif /* __cplusplus */
 
 
+/* The following are flags which can be set in 
+  PaWinMmeStreamInfo's flags field.
+*/
+
 #define paWinMmeUseLowLevelLatencyParameters            (0x01)
 #define paWinMmeUseMultipleDevices                      (0x02)  /* use mme specific multiple device feature */
 #define paWinMmeUseChannelMask                          (0x04)
@@ -63,6 +67,11 @@ extern "C"
 */
 #define paWinMmeDontThrottleOverloadedProcessingThread  (0x08)
 
+/*  Flags for non-PCM spdif passthrough.
+*/
+#define paWinMmeWaveFormatDolbyAc3Spdif                 (0x10)
+#define paWinMmeWaveFormatWmaSpdif                      (0x20)
+
 
 typedef struct PaWinMmeDeviceAndChannelCount{
     PaDeviceIndex device;
diff --git a/external/portaudio/pa_x86_plain_converters.c b/external/portaudio/pa_x86_plain_converters.c
new file mode 100644
index 0000000..ae43d28
--- /dev/null
+++ b/external/portaudio/pa_x86_plain_converters.c
@@ -0,0 +1,1218 @@
+/*
+ * Plain Intel IA32 assembly implementations of PortAudio sample converter functions.
+ * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however, 
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also 
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+/** @file
+ @ingroup win_src
+*/
+
+#include "pa_x86_plain_converters.h"
+
+#include "pa_converters.h"
+#include "pa_dither.h"
+
+/*
+    the main reason these versions are faster than the equivalent C versions
+    is that float -> int casting is expensive in C on x86 because the rounding
+    mode needs to be changed for every cast. these versions only set
+    the rounding mode once outside the loop.
+
+    small additional speed gains are made by the way that clamping is
+    implemented.
+
+TODO:
+    o- inline dither code
+    o- implement Dither only (no-clip) versions
+    o- implement int8 and uint8 versions
+    o- test thouroughly
+
+    o- the packed 24 bit functions could benefit from unrolling and avoiding
+        byte and word sized register access.
+*/
+
+/* -------------------------------------------------------------------------- */
+
+/*
+#define PA_CLIP_( val, min, max )\
+    { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); }
+*/
+
+/*
+    the following notes were used to determine whether a floating point
+    value should be saturated (ie >1 or <-1) by loading it into an integer
+    register. these should be rewritten so that they make sense.
+
+    an ieee floating point value
+
+    1.xxxxxxxxxxxxxxxxxxxx?
+
+
+    is less than  or equal to 1 and greater than or equal to -1 either:
+
+        if the mantissa is 0 and the unbiased exponent is 0
+
+        OR
+
+        if the unbiased exponent < 0
+
+    this translates to:
+
+        if the mantissa is 0 and the biased exponent is 7F
+
+        or
+
+        if the biased exponent is less than 7F
+
+
+    therefore the value is greater than 1 or less than -1 if
+
+        the mantissa is not 0 and the biased exponent is 7F
+
+        or
+
+        if the biased exponent is greater than 7F
+
+
+    in other words, if we mask out the sign bit, the value is
+    greater than 1 or less than -1 if its integer representation is greater than:
+
+    0 01111111 0000 0000 0000 0000 0000 000
+
+    0011 1111 1000 0000 0000 0000 0000 0000 => 0x3F800000
+*/
+
+#if defined(_WIN64) || defined(_WIN32_WCE)
+
+/*
+	-EMT64/AMD64 uses different asm
+	-VC2005 doesnt allow _WIN64 with inline assembly either!
+ */
+void PaUtil_InitializeX86PlainConverters( void )
+{
+}
+
+#else
+
+/* -------------------------------------------------------------------------- */
+
+static const short fpuControlWord_ = 0x033F; /*round to nearest, 64 bit precision, all exceptions masked*/
+static const double int32Scaler_ = 0x7FFFFFFF;
+static const double ditheredInt32Scaler_ = 0x7FFFFFFE;
+static const double int24Scaler_ = 0x7FFFFF;
+static const double ditheredInt24Scaler_ = 0x7FFFFE;
+static const double int16Scaler_ = 0x7FFF;
+static const double ditheredInt16Scaler_ = 0x7FFE;
+
+#define PA_DITHER_BITS_   (15)
+/* Multiply by PA_FLOAT_DITHER_SCALE_ to get a float between -2.0 and +1.99999 */
+#define PA_FLOAT_DITHER_SCALE_  (1.0F / ((1<<PA_DITHER_BITS_)-1))
+static const float const_float_dither_scale_ = PA_FLOAT_DITHER_SCALE_;
+#define PA_DITHER_SHIFT_  ((32 - PA_DITHER_BITS_) + 1)
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int32(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    signed long *dest =  (signed long*)destinationBuffer;
+    (void)ditherGenerator; // unused parameter
+
+    while( count-- )
+    {
+        // REVIEW
+        double scaled = *src * 0x7FFFFFFF;
+        *dest = (signed long) scaled;
+
+        src += sourceStride;
+        dest += destinationStride;
+    }
+*/
+
+    short savedFpuControlWord;
+
+    (void) ditherGenerator; /* unused parameter */
+
+
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32 and int32
+        mov     eax, sourceStride
+        imul    eax, edx
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi
+    
+        mov     edi, destinationBuffer
+        
+        mov     ebx, destinationStride
+        imul    ebx, edx
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     int32Scaler_             // stack:  (int)0x7FFFFFFF
+
+    Float32_To_Int32_loop:
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, (int)0x7FFFFFFF
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*0x7FFFFFFF, (int)0x7FFFFFFF
+        /*
+            note: we could store to a temporary qword here which would cause
+            wraparound distortion instead of int indefinite 0x10. that would
+            be more work, and given that not enabling clipping is only advisable
+            when you know that your signal isn't going to clip it isn't worth it.
+        */
+        fistp   dword ptr [edi]         // pop st(0) into dest, stack:  (int)0x7FFFFFFF
+
+        add     edi, ebx                // increment destination ptr
+        //lea     edi, [edi+ebx]
+
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int32_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int32_Clip(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    signed long *dest =  (signed long*)destinationBuffer;
+    (void) ditherGenerator; // unused parameter
+
+    while( count-- )
+    {
+        // REVIEW
+        double scaled = *src * 0x7FFFFFFF;
+        PA_CLIP_( scaled, -2147483648., 2147483647.  );
+        *dest = (signed long) scaled;
+
+        src += sourceStride;
+        dest += destinationStride;
+    }
+*/
+
+    short savedFpuControlWord;
+
+    (void) ditherGenerator; /* unused parameter */
+
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32 and int32
+        mov     eax, sourceStride
+        imul    eax, edx
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi
+    
+        mov     edi, destinationBuffer
+        
+        mov     ebx, destinationStride
+        imul    ebx, edx
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     int32Scaler_             // stack:  (int)0x7FFFFFFF
+
+    Float32_To_Int32_Clip_loop:
+
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+
+        and     edx, 0x7FFFFFFF         // mask off sign
+        cmp     edx, 0x3F800000         // greater than 1.0 or less than -1.0
+
+        jg      Float32_To_Int32_Clip_clamp
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, (int)0x7FFFFFFF
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*0x7FFFFFFF, (int)0x7FFFFFFF
+        fistp   dword ptr [edi]         // pop st(0) into dest, stack:  (int)0x7FFFFFFF
+        jmp     Float32_To_Int32_Clip_stored
+    
+    Float32_To_Int32_Clip_clamp:
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+        shr     edx, 31                 // move sign bit into bit 0
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        add     edx, 0x7FFFFFFF         // convert to maximum range integers
+        mov     dword ptr [edi], edx
+
+    Float32_To_Int32_Clip_stored:
+
+        //add     edi, ebx                // increment destination ptr
+        lea     edi, [edi+ebx]
+
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int32_Clip_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int32_DitherClip(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+    /*
+    float *src = (float*)sourceBuffer;
+    signed long *dest =  (signed long*)destinationBuffer;
+
+    while( count-- )
+    {
+        // REVIEW
+        double dither  = PaUtil_GenerateFloatTriangularDither( ditherGenerator );
+        // use smaller scaler to prevent overflow when we add the dither
+        double dithered = ((double)*src * (2147483646.0)) + dither;
+        PA_CLIP_( dithered, -2147483648., 2147483647.  );
+        *dest = (signed long) dithered;
+
+
+        src += sourceStride;
+        dest += destinationStride;
+    }
+    */
+
+    short savedFpuControlWord;
+
+    // spill storage:
+    signed long sourceByteStride;
+    signed long highpassedDither;
+
+    // dither state:
+    unsigned long ditherPrevious = ditherGenerator->previous;
+    unsigned long ditherRandSeed1 = ditherGenerator->randSeed1;
+    unsigned long ditherRandSeed2 = ditherGenerator->randSeed2;
+                    
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32 and int32
+        mov     eax, sourceStride
+        imul    eax, edx
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi
+    
+        mov     edi, destinationBuffer
+        
+        mov     ebx, destinationStride
+        imul    ebx, edx
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     ditheredInt32Scaler_    // stack:  int scaler
+
+    Float32_To_Int32_DitherClip_loop:
+
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+
+        and     edx, 0x7FFFFFFF         // mask off sign
+        cmp     edx, 0x3F800000         // greater than 1.0 or less than -1.0
+
+        jg      Float32_To_Int32_DitherClip_clamp
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, int scaler
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*(int scaler), int scaler
+
+        /*
+        // call PaUtil_GenerateFloatTriangularDither with C calling convention
+        mov     sourceByteStride, eax   // save eax
+        mov     sourceEnd, ecx          // save ecx
+        push    ditherGenerator         // pass ditherGenerator parameter on stack
+	    call    PaUtil_GenerateFloatTriangularDither  // stack:  dither, value*(int scaler), int scaler
+	    pop     edx                     // clear parameter off stack
+        mov     ecx, sourceEnd          // restore ecx
+        mov     eax, sourceByteStride   // restore eax
+        */
+
+    // generate dither
+        mov     sourceByteStride, eax   // save eax
+        mov     edx, 196314165
+        mov     eax, ditherRandSeed1
+        mul     edx                     // eax:edx = eax * 196314165
+        //add     eax, 907633515
+        lea     eax, [eax+907633515]
+        mov     ditherRandSeed1, eax
+        mov     edx, 196314165
+        mov     eax, ditherRandSeed2
+        mul     edx                     // eax:edx = eax * 196314165
+        //add     eax, 907633515
+        lea     eax, [eax+907633515]
+        mov     edx, ditherRandSeed1
+        shr     edx, PA_DITHER_SHIFT_
+        mov     ditherRandSeed2, eax
+        shr     eax, PA_DITHER_SHIFT_
+        //add     eax, edx                // eax -> current
+        lea     eax, [eax+edx]
+        mov     edx, ditherPrevious
+        neg     edx
+        lea     edx, [eax+edx]          // highpass = current - previous
+        mov     highpassedDither, edx
+        mov     ditherPrevious, eax     // previous = current
+        mov     eax, sourceByteStride   // restore eax
+        fild    highpassedDither
+        fmul    const_float_dither_scale_
+    // end generate dither, dither signal in st(0)
+    
+        faddp   st(1), st(0)            // stack: dither + value*(int scaler), int scaler
+        fistp   dword ptr [edi]         // pop st(0) into dest, stack:  int scaler
+        jmp     Float32_To_Int32_DitherClip_stored
+    
+    Float32_To_Int32_DitherClip_clamp:
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+        shr     edx, 31                 // move sign bit into bit 0
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        add     edx, 0x7FFFFFFF         // convert to maximum range integers
+        mov     dword ptr [edi], edx
+
+    Float32_To_Int32_DitherClip_stored:
+
+        //add     edi, ebx              // increment destination ptr
+        lea     edi, [edi+ebx]
+
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int32_DitherClip_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+
+    ditherGenerator->previous = ditherPrevious;
+    ditherGenerator->randSeed1 = ditherRandSeed1;
+    ditherGenerator->randSeed2 = ditherRandSeed2;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int24(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    unsigned char *dest = (unsigned char*)destinationBuffer;
+    signed long temp;
+
+    (void) ditherGenerator; // unused parameter
+    
+    while( count-- )
+    {
+        // convert to 32 bit and drop the low 8 bits
+        double scaled = *src * 0x7FFFFFFF;
+        temp = (signed long) scaled;
+
+        dest[0] = (unsigned char)(temp >> 8);
+        dest[1] = (unsigned char)(temp >> 16);
+        dest[2] = (unsigned char)(temp >> 24);
+
+        src += sourceStride;
+        dest += destinationStride * 3;
+    }
+*/
+
+    short savedFpuControlWord;
+    
+    signed long tempInt32;
+
+    (void) ditherGenerator; /* unused parameter */
+                 
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32
+        mov     eax, sourceStride
+        imul    eax, edx
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi
+
+        mov     edi, destinationBuffer
+
+        mov     edx, 3                  // sizeof int24
+        mov     ebx, destinationStride
+        imul    ebx, edx
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     int24Scaler_             // stack:  (int)0x7FFFFF
+
+    Float32_To_Int24_loop:
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, (int)0x7FFFFF
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*0x7FFFFF, (int)0x7FFFFF
+        fistp   tempInt32               // pop st(0) into tempInt32, stack:  (int)0x7FFFFF
+        mov     edx, tempInt32
+
+        mov     byte ptr [edi], DL
+        shr     edx, 8
+        //mov     byte ptr [edi+1], DL
+        //mov     byte ptr [edi+2], DH
+        mov     word ptr [edi+1], DX
+
+        //add     edi, ebx                // increment destination ptr
+        lea     edi, [edi+ebx]
+
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int24_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int24_Clip(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    unsigned char *dest = (unsigned char*)destinationBuffer;
+    signed long temp;
+
+    (void) ditherGenerator; // unused parameter
+    
+    while( count-- )
+    {
+        // convert to 32 bit and drop the low 8 bits
+        double scaled = *src * 0x7FFFFFFF;
+        PA_CLIP_( scaled, -2147483648., 2147483647.  );
+        temp = (signed long) scaled;
+
+        dest[0] = (unsigned char)(temp >> 8);
+        dest[1] = (unsigned char)(temp >> 16);
+        dest[2] = (unsigned char)(temp >> 24);
+
+        src += sourceStride;
+        dest += destinationStride * 3;
+    }
+*/
+
+    short savedFpuControlWord;
+    
+    signed long tempInt32;
+
+    (void) ditherGenerator; /* unused parameter */
+                 
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32
+        mov     eax, sourceStride
+        imul    eax, edx
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi
+
+        mov     edi, destinationBuffer
+
+        mov     edx, 3                  // sizeof int24
+        mov     ebx, destinationStride
+        imul    ebx, edx
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     int24Scaler_             // stack:  (int)0x7FFFFF
+
+    Float32_To_Int24_Clip_loop:
+
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+
+        and     edx, 0x7FFFFFFF         // mask off sign
+        cmp     edx, 0x3F800000         // greater than 1.0 or less than -1.0
+
+        jg      Float32_To_Int24_Clip_clamp
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, (int)0x7FFFFF
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*0x7FFFFF, (int)0x7FFFFF
+        fistp   tempInt32               // pop st(0) into tempInt32, stack:  (int)0x7FFFFF
+        mov     edx, tempInt32
+        jmp     Float32_To_Int24_Clip_store
+    
+    Float32_To_Int24_Clip_clamp:
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+        shr     edx, 31                 // move sign bit into bit 0
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        add     edx, 0x7FFFFF           // convert to maximum range integers
+
+    Float32_To_Int24_Clip_store:
+
+        mov     byte ptr [edi], DL
+        shr     edx, 8
+        //mov     byte ptr [edi+1], DL
+        //mov     byte ptr [edi+2], DH
+        mov     word ptr [edi+1], DX
+
+        //add     edi, ebx                // increment destination ptr
+        lea     edi, [edi+ebx]
+
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int24_Clip_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int24_DitherClip(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    unsigned char *dest = (unsigned char*)destinationBuffer;
+    signed long temp;
+    
+    while( count-- )
+    {
+        // convert to 32 bit and drop the low 8 bits
+
+        // FIXME: the dither amplitude here appears to be too small by 8 bits
+        double dither  = PaUtil_GenerateFloatTriangularDither( ditherGenerator );
+        // use smaller scaler to prevent overflow when we add the dither
+        double dithered = ((double)*src * (2147483646.0)) + dither;
+        PA_CLIP_( dithered, -2147483648., 2147483647.  );
+        
+        temp = (signed long) dithered;
+
+        dest[0] = (unsigned char)(temp >> 8);
+        dest[1] = (unsigned char)(temp >> 16);
+        dest[2] = (unsigned char)(temp >> 24);
+
+        src += sourceStride;
+        dest += destinationStride * 3;
+    }
+*/
+
+    short savedFpuControlWord;
+
+    // spill storage:
+    signed long sourceByteStride;
+    signed long highpassedDither;
+
+    // dither state:
+    unsigned long ditherPrevious = ditherGenerator->previous;
+    unsigned long ditherRandSeed1 = ditherGenerator->randSeed1;
+    unsigned long ditherRandSeed2 = ditherGenerator->randSeed2;
+    
+    signed long tempInt32;
+                 
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32
+        mov     eax, sourceStride
+        imul    eax, edx
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi
+
+        mov     edi, destinationBuffer
+
+        mov     edx, 3                  // sizeof int24
+        mov     ebx, destinationStride
+        imul    ebx, edx
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     ditheredInt24Scaler_    // stack:  int scaler
+
+    Float32_To_Int24_DitherClip_loop:
+
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+
+        and     edx, 0x7FFFFFFF         // mask off sign
+        cmp     edx, 0x3F800000         // greater than 1.0 or less than -1.0
+
+        jg      Float32_To_Int24_DitherClip_clamp
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, int scaler
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*(int scaler), int scaler
+
+    /*
+        // call PaUtil_GenerateFloatTriangularDither with C calling convention
+        mov     sourceByteStride, eax   // save eax
+        mov     sourceEnd, ecx          // save ecx
+        push    ditherGenerator         // pass ditherGenerator parameter on stack
+	    call    PaUtil_GenerateFloatTriangularDither  // stack:  dither, value*(int scaler), int scaler
+	    pop     edx                     // clear parameter off stack
+        mov     ecx, sourceEnd          // restore ecx
+        mov     eax, sourceByteStride   // restore eax
+    */
+    
+    // generate dither
+        mov     sourceByteStride, eax   // save eax
+        mov     edx, 196314165
+        mov     eax, ditherRandSeed1
+        mul     edx                     // eax:edx = eax * 196314165
+        //add     eax, 907633515
+        lea     eax, [eax+907633515]
+        mov     ditherRandSeed1, eax
+        mov     edx, 196314165
+        mov     eax, ditherRandSeed2
+        mul     edx                     // eax:edx = eax * 196314165
+        //add     eax, 907633515
+        lea     eax, [eax+907633515]
+        mov     edx, ditherRandSeed1
+        shr     edx, PA_DITHER_SHIFT_
+        mov     ditherRandSeed2, eax
+        shr     eax, PA_DITHER_SHIFT_
+        //add     eax, edx                // eax -> current
+        lea     eax, [eax+edx]
+        mov     edx, ditherPrevious
+        neg     edx
+        lea     edx, [eax+edx]          // highpass = current - previous
+        mov     highpassedDither, edx
+        mov     ditherPrevious, eax     // previous = current
+        mov     eax, sourceByteStride   // restore eax
+        fild    highpassedDither
+        fmul    const_float_dither_scale_
+    // end generate dither, dither signal in st(0)
+
+        faddp   st(1), st(0)            // stack: dither * value*(int scaler), int scaler
+        fistp   tempInt32               // pop st(0) into tempInt32, stack:  int scaler
+        mov     edx, tempInt32
+        jmp     Float32_To_Int24_DitherClip_store
+    
+    Float32_To_Int24_DitherClip_clamp:
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+        shr     edx, 31                 // move sign bit into bit 0
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        add     edx, 0x7FFFFF           // convert to maximum range integers
+
+    Float32_To_Int24_DitherClip_store:
+
+        mov     byte ptr [edi], DL
+        shr     edx, 8
+        //mov     byte ptr [edi+1], DL
+        //mov     byte ptr [edi+2], DH
+        mov     word ptr [edi+1], DX
+
+        //add     edi, ebx                // increment destination ptr
+        lea     edi, [edi+ebx]
+
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int24_DitherClip_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+
+    ditherGenerator->previous = ditherPrevious;
+    ditherGenerator->randSeed1 = ditherRandSeed1;
+    ditherGenerator->randSeed2 = ditherRandSeed2;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int16(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    signed short *dest =  (signed short*)destinationBuffer;
+    (void)ditherGenerator; // unused parameter
+
+    while( count-- )
+    {
+
+        short samp = (short) (*src * (32767.0f));
+        *dest = samp;
+
+        src += sourceStride;
+        dest += destinationStride;
+    }
+*/
+
+    short savedFpuControlWord;
+   
+    (void) ditherGenerator; /* unused parameter */
+
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32
+        mov     eax, sourceStride
+        imul    eax, edx                // source byte stride
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi                // source end ptr = count * source byte stride + source ptr
+
+        mov     edi, destinationBuffer
+
+        mov     edx, 2                  // sizeof int16
+        mov     ebx, destinationStride
+        imul    ebx, edx                // destination byte stride
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     int16Scaler_            // stack:  (int)0x7FFF
+
+    Float32_To_Int16_loop:
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, (int)0x7FFF
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*0x7FFF, (int)0x7FFF
+        fistp   word ptr [edi]          // store scaled int into dest, stack:  (int)0x7FFF
+
+        add     edi, ebx                // increment destination ptr
+        //lea     edi, [edi+ebx]
+        
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int16_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int16_Clip(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    signed short *dest =  (signed short*)destinationBuffer;
+    (void)ditherGenerator; // unused parameter
+
+    while( count-- )
+    {
+        long samp = (signed long) (*src * (32767.0f));
+        PA_CLIP_( samp, -0x8000, 0x7FFF );
+        *dest = (signed short) samp;
+
+        src += sourceStride;
+        dest += destinationStride;
+    }
+*/
+
+    short savedFpuControlWord;
+   
+    (void) ditherGenerator; /* unused parameter */
+
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32
+        mov     eax, sourceStride
+        imul    eax, edx                // source byte stride
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi                // source end ptr = count * source byte stride + source ptr
+
+        mov     edi, destinationBuffer
+
+        mov     edx, 2                  // sizeof int16
+        mov     ebx, destinationStride
+        imul    ebx, edx                // destination byte stride
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     int16Scaler_            // stack:  (int)0x7FFF
+
+    Float32_To_Int16_Clip_loop:
+
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+
+        and     edx, 0x7FFFFFFF         // mask off sign
+        cmp     edx, 0x3F800000         // greater than 1.0 or less than -1.0
+
+        jg      Float32_To_Int16_Clip_clamp
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, (int)0x7FFF
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*0x7FFF, (int)0x7FFF
+        fistp   word ptr [edi]          // store scaled int into dest, stack:  (int)0x7FFF
+        jmp     Float32_To_Int16_Clip_stored
+    
+    Float32_To_Int16_Clip_clamp:
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+        shr     edx, 31                 // move sign bit into bit 0
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        add     dx, 0x7FFF              // convert to maximum range integers
+        mov     word ptr [edi], dx      // store clamped into into dest
+
+    Float32_To_Int16_Clip_stored:
+
+        add     edi, ebx                // increment destination ptr
+        //lea     edi, [edi+ebx]
+        
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int16_Clip_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void Float32_To_Int16_DitherClip(
+    void *destinationBuffer, signed int destinationStride,
+    void *sourceBuffer, signed int sourceStride,
+    unsigned int count, PaUtilTriangularDitherGenerator *ditherGenerator )
+{
+/*
+    float *src = (float*)sourceBuffer;
+    signed short *dest =  (signed short*)destinationBuffer;
+    (void)ditherGenerator; // unused parameter
+
+    while( count-- )
+    {
+
+        float dither  = PaUtil_GenerateFloatTriangularDither( ditherGenerator );
+        // use smaller scaler to prevent overflow when we add the dither 
+        float dithered = (*src * (32766.0f)) + dither;
+        signed long samp = (signed long) dithered;
+        PA_CLIP_( samp, -0x8000, 0x7FFF );
+        *dest = (signed short) samp;
+
+        src += sourceStride;
+        dest += destinationStride;
+    }
+*/
+
+    short savedFpuControlWord;
+
+    // spill storage:
+    signed long sourceByteStride;
+    signed long highpassedDither;
+
+    // dither state:
+    unsigned long ditherPrevious = ditherGenerator->previous;
+    unsigned long ditherRandSeed1 = ditherGenerator->randSeed1;
+    unsigned long ditherRandSeed2 = ditherGenerator->randSeed2;
+
+    __asm{
+        // esi -> source ptr
+        // eax -> source byte stride
+        // edi -> destination ptr
+        // ebx -> destination byte stride
+        // ecx -> source end ptr
+        // edx -> temp
+
+        mov     esi, sourceBuffer
+
+        mov     edx, 4                  // sizeof float32
+        mov     eax, sourceStride
+        imul    eax, edx                // source byte stride
+
+        mov     ecx, count
+        imul    ecx, eax
+        add     ecx, esi                // source end ptr = count * source byte stride + source ptr
+
+        mov     edi, destinationBuffer
+
+        mov     edx, 2                  // sizeof int16
+        mov     ebx, destinationStride
+        imul    ebx, edx                // destination byte stride
+
+        fwait
+        fstcw   savedFpuControlWord
+        fldcw   fpuControlWord_
+
+        fld     ditheredInt16Scaler_    // stack:  int scaler
+
+    Float32_To_Int16_DitherClip_loop:
+
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+
+        and     edx, 0x7FFFFFFF         // mask off sign
+        cmp     edx, 0x3F800000         // greater than 1.0 or less than -1.0
+
+        jg      Float32_To_Int16_DitherClip_clamp
+
+        // load unscaled value into st(0)
+        fld     dword ptr [esi]         // stack:  value, int scaler
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        fmul    st(0), st(1)            // st(0) *= st(1), stack:  value*(int scaler), int scaler
+
+        /*
+        // call PaUtil_GenerateFloatTriangularDither with C calling convention
+        mov     sourceByteStride, eax   // save eax
+        mov     sourceEnd, ecx          // save ecx
+        push    ditherGenerator         // pass ditherGenerator parameter on stack
+	    call    PaUtil_GenerateFloatTriangularDither  // stack:  dither, value*(int scaler), int scaler
+	    pop     edx                     // clear parameter off stack
+        mov     ecx, sourceEnd          // restore ecx
+        mov     eax, sourceByteStride   // restore eax
+        */
+
+    // generate dither
+        mov     sourceByteStride, eax   // save eax
+        mov     edx, 196314165
+        mov     eax, ditherRandSeed1
+        mul     edx                     // eax:edx = eax * 196314165
+        //add     eax, 907633515
+        lea     eax, [eax+907633515]
+        mov     ditherRandSeed1, eax
+        mov     edx, 196314165
+        mov     eax, ditherRandSeed2
+        mul     edx                     // eax:edx = eax * 196314165
+        //add     eax, 907633515
+        lea     eax, [eax+907633515]
+        mov     edx, ditherRandSeed1
+        shr     edx, PA_DITHER_SHIFT_
+        mov     ditherRandSeed2, eax
+        shr     eax, PA_DITHER_SHIFT_
+        //add     eax, edx                // eax -> current
+        lea     eax, [eax+edx]            // current = randSeed1>>x + randSeed2>>x
+        mov     edx, ditherPrevious
+        neg     edx
+        lea     edx, [eax+edx]          // highpass = current - previous
+        mov     highpassedDither, edx
+        mov     ditherPrevious, eax     // previous = current
+        mov     eax, sourceByteStride   // restore eax
+        fild    highpassedDither
+        fmul    const_float_dither_scale_
+    // end generate dither, dither signal in st(0)
+        
+        faddp   st(1), st(0)            // stack: dither * value*(int scaler), int scaler
+        fistp   word ptr [edi]          // store scaled int into dest, stack:  int scaler
+        jmp     Float32_To_Int16_DitherClip_stored
+    
+    Float32_To_Int16_DitherClip_clamp:
+        mov     edx, dword ptr [esi]    // load floating point value into integer register
+        shr     edx, 31                 // move sign bit into bit 0
+        add     esi, eax                // increment source ptr
+        //lea     esi, [esi+eax]
+        add     dx, 0x7FFF              // convert to maximum range integers
+        mov     word ptr [edi], dx      // store clamped into into dest
+
+    Float32_To_Int16_DitherClip_stored:
+
+        add     edi, ebx                // increment destination ptr
+        //lea     edi, [edi+ebx]
+        
+        cmp     esi, ecx                // has src ptr reached end?
+        jne     Float32_To_Int16_DitherClip_loop
+
+        ffree   st(0)
+        fincstp
+
+        fwait
+        fnclex
+        fldcw   savedFpuControlWord
+    }
+
+    ditherGenerator->previous = ditherPrevious;
+    ditherGenerator->randSeed1 = ditherRandSeed1;
+    ditherGenerator->randSeed2 = ditherRandSeed2;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void PaUtil_InitializeX86PlainConverters( void )
+{
+    paConverters.Float32_To_Int32 = Float32_To_Int32;
+    paConverters.Float32_To_Int32_Clip = Float32_To_Int32_Clip;
+    paConverters.Float32_To_Int32_DitherClip = Float32_To_Int32_DitherClip;
+
+    paConverters.Float32_To_Int24 = Float32_To_Int24;
+    paConverters.Float32_To_Int24_Clip = Float32_To_Int24_Clip;
+    paConverters.Float32_To_Int24_DitherClip = Float32_To_Int24_DitherClip;
+    
+    paConverters.Float32_To_Int16 = Float32_To_Int16;
+    paConverters.Float32_To_Int16_Clip = Float32_To_Int16_Clip;
+    paConverters.Float32_To_Int16_DitherClip = Float32_To_Int16_DitherClip;
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
diff --git a/external/portaudio/pa_trace.h b/external/portaudio/pa_x86_plain_converters.h
similarity index 64%
copy from external/portaudio/pa_trace.h
copy to external/portaudio/pa_x86_plain_converters.h
index a4d2a33..1623115 100644
--- a/external/portaudio/pa_trace.h
+++ b/external/portaudio/pa_x86_plain_converters.h
@@ -1,12 +1,6 @@
-#ifndef PA_TRACE_H
-#define PA_TRACE_H
 /*
- * $Id: pa_trace.h 1097 2006-08-26 08:27:53Z rossb $
- * Portable Audio I/O Library Trace Facility
- * Store trace information in real-time for later printing.
- *
- * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2000 Phil Burk
+ * Plain Intel IA32 assembly implementations of PortAudio sample converter functions.
+ * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
@@ -40,16 +34,11 @@
  */
 
 /** @file
- @ingroup common_src
-
- @brief Event trace mechanism for debugging.
-
- Allows data to be written to the buffer at interrupt time and dumped later.
+ @ingroup win_src
 */
 
-
-#define PA_TRACE_REALTIME_EVENTS     (0)   /* Keep log of various real-time events. */
-#define PA_MAX_TRACE_RECORDS      (2048)
+#ifndef PA_X86_PLAIN_CONVERTERS_H
+#define PA_X86_PLAIN_CONVERTERS_H
 
 #ifdef __cplusplus
 extern "C"
@@ -57,23 +46,15 @@ extern "C"
 #endif /* __cplusplus */
 
 
-#if PA_TRACE_REALTIME_EVENTS
+/**
+ @brief Install optimized converter functions suitable for all IA32 processors
 
-void PaUtil_ResetTraceMessages();
-void PaUtil_AddTraceMessage( const char *msg, int data );
-void PaUtil_DumpTraceMessages();
-    
-#else
-
-#define PaUtil_ResetTraceMessages() /* noop */
-#define PaUtil_AddTraceMessage(msg,data) /* noop */
-#define PaUtil_DumpTraceMessages() /* noop */
-
-#endif
+ It is recommended to call PaUtil_InitializeX86PlainConverters prior to calling Pa_Initialize
+*/
+void PaUtil_InitializeX86PlainConverters( void );
 
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
-
-#endif /* PA_TRACE_H */
+#endif /* PA_X86_PLAIN_CONVERTERS_H */
diff --git a/external/portaudio/portaudio.h b/external/portaudio/portaudio.h
index a8385ad..5e11dad 100644
--- a/external/portaudio/portaudio.h
+++ b/external/portaudio/portaudio.h
@@ -1,7 +1,7 @@
 #ifndef PORTAUDIO_H
 #define PORTAUDIO_H
 /*
- * $Id: portaudio.h 1247 2007-08-11 16:29:09Z rossb $
+ * $Id: portaudio.h 1859 2012-09-01 00:10:13Z philburk $
  * PortAudio Portable Real-Time Audio Library
  * PortAudio API Header File
  * Latest version available at: http://www.portaudio.com/
@@ -40,7 +40,8 @@
  */
 
 /** @file
- @brief The PortAudio API.
+ @ingroup public_header
+ @brief The portable PortAudio API.
 */
 
 
@@ -94,10 +95,10 @@ typedef enum PaErrorCode
     paOutputUnderflowed,
     paHostApiNotFound,
     paInvalidHostApi,
-    paCanNotReadFromACallbackStream,      /**< @todo review error code name */
-    paCanNotWriteToACallbackStream,       /**< @todo review error code name */
-    paCanNotReadFromAnOutputOnlyStream,   /**< @todo review error code name */
-    paCanNotWriteToAnInputOnlyStream,     /**< @todo review error code name */
+    paCanNotReadFromACallbackStream,
+    paCanNotWriteToACallbackStream,
+    paCanNotReadFromAnOutputOnlyStream,
+    paCanNotWriteToAnInputOnlyStream,
     paIncompatibleStreamHostApi,
     paBadBufferPtr
 } PaErrorCode;
@@ -110,7 +111,7 @@ const char *Pa_GetErrorText( PaError errorCode );
 
 
 /** Library initialization function - call this before using PortAudio.
- This function initialises internal data structures and prepares underlying
+ This function initializes internal data structures and prepares underlying
  host APIs for use.  With the exception of Pa_GetVersion(), Pa_GetVersionText(),
  and Pa_GetErrorText(), this function MUST be called before using any other
  PortAudio API functions.
@@ -133,7 +134,7 @@ PaError Pa_Initialize( void );
 
 /** Library termination function - call this when finished using PortAudio.
  This function deallocates all resources allocated by PortAudio since it was
- initializied by a call to Pa_Initialize(). In cases where Pa_Initialise() has
+ initialized by a call to Pa_Initialize(). In cases where Pa_Initialise() has
  been called multiple times, each call must be matched with a corresponding call
  to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically
  close any PortAudio streams that are still open.
@@ -343,13 +344,13 @@ typedef struct PaHostErrorInfo{
 
 /** Return information about the last host error encountered. The error
  information returned by Pa_GetLastHostErrorInfo() will never be modified
- asyncronously by errors occurring in other PortAudio owned threads
+ asynchronously by errors occurring in other PortAudio owned threads
  (such as the thread that manages the stream callback.)
 
  This function is provided as a last resort, primarily to enhance debugging
  by providing clients with access to all available error information.
 
- @return A pointer to an immutable structure constaining information about
+ @return A pointer to an immutable structure constraining information about
  the host error. The values in this structure will only be valid if a
  PortAudio function has previously returned the paUnanticipatedHostError
  error code.
@@ -397,11 +398,13 @@ PaDeviceIndex Pa_GetDefaultInputDevice( void );
 PaDeviceIndex Pa_GetDefaultOutputDevice( void );
 
 
-/** The type used to represent monotonic time in seconds that can be used
- for syncronisation. The type is used for the outTime argument to the
+/** The type used to represent monotonic time in seconds. PaTime is 
+ used for the fields of the PaStreamCallbackTimeInfo argument to the 
  PaStreamCallback and as the result of Pa_GetStreamTime().
+
+ PaTime values have unspecified origin.
      
- @see PaStreamCallback, Pa_GetStreamTime
+ @see PaStreamCallback, PaStreamCallbackTimeInfo, Pa_GetStreamTime
 */
 typedef double PaTime;
 
@@ -418,8 +421,10 @@ typedef double PaTime;
 
  paUInt8 is an unsigned 8 bit format where 128 is considered "ground"
 
- The paNonInterleaved flag indicates that a multichannel buffer is passed
- as a set of non-interleaved pointers.
+ The paNonInterleaved flag indicates that audio data is passed as an array 
+ of pointers to separate buffers, one buffer for each channel. Usually,
+ when this flag is not used, audio data is passed as a single buffer with
+ all channels interleaved.
 
  @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo
  @see paFloat32, paInt16, paInt32, paInt24, paInt8
@@ -434,9 +439,9 @@ typedef unsigned long PaSampleFormat;
 #define paInt16          ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */
 #define paInt8           ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */
 #define paUInt8          ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */
-#define paCustomFormat   ((PaSampleFormat) 0x00010000)/**< @see PaSampleFormat */
+#define paCustomFormat   ((PaSampleFormat) 0x00010000) /**< @see PaSampleFormat */
 
-#define paNonInterleaved ((PaSampleFormat) 0x80000000)
+#define paNonInterleaved ((PaSampleFormat) 0x80000000) /**< @see PaSampleFormat */
 
 /** A structure providing information and capabilities of PortAudio devices.
  Devices may support input, output or both input and output.
@@ -445,15 +450,15 @@ typedef struct PaDeviceInfo
 {
     int structVersion;  /* this is struct version 2 */
     const char *name;
-    PaHostApiIndex hostApi; /* note this is a host API index, not a type id*/
+    PaHostApiIndex hostApi; /**< note this is a host API index, not a type id*/
     
     int maxInputChannels;
     int maxOutputChannels;
 
-    /* Default latency values for interactive performance. */
+    /** Default latency values for interactive performance. */
     PaTime defaultLowInputLatency;
     PaTime defaultLowOutputLatency;
-    /* Default latency values for robust non-interactive applications (eg. playing sound files). */
+    /** Default latency values for robust non-interactive applications (eg. playing sound files). */
     PaTime defaultHighInputLatency;
     PaTime defaultHighOutputLatency;
 
@@ -506,8 +511,8 @@ typedef struct PaStreamParameters
      configure their latency based on these parameters, otherwise they may
      choose the closest viable latency instead. Unless the suggested latency
      is greater than the absolute upper limit for the device implementations
-     should round the suggestedLatency up to the next practial value - ie to
-     provide an equal or higher latency than suggestedLatency wherever possibe.
+     should round the suggestedLatency up to the next practical value - ie to
+     provide an equal or higher latency than suggestedLatency wherever possible.
      Actual latency values for an open stream may be retrieved using the
      inputLatency and outputLatency fields of the PaStreamInfo structure
      returned by Pa_GetStreamInfo().
@@ -635,11 +640,15 @@ typedef unsigned long PaStreamFlags;
 
 /**
  Timing information for the buffers passed to the stream callback.
+
+ Time values are expressed in seconds and are synchronised with the time base used by Pa_GetStreamTime() for the associated stream.
+ 
+ @see PaStreamCallback, Pa_GetStreamTime
 */
 typedef struct PaStreamCallbackTimeInfo{
-    PaTime inputBufferAdcTime;
-    PaTime currentTime;
-    PaTime outputBufferDacTime;
+    PaTime inputBufferAdcTime;  /**< The time when the first sample of the input buffer was captured at the ADC input */
+    PaTime currentTime;         /**< The time when the stream callback was invoked */
+    PaTime outputBufferDacTime; /**< The time when the first sample of the output buffer will output the DAC */
 } PaStreamCallbackTimeInfo;
 
 
@@ -692,9 +701,9 @@ typedef unsigned long PaStreamCallbackFlags;
 */
 typedef enum PaStreamCallbackResult
 {
-    paContinue=0,
-    paComplete=1,
-    paAbort=2
+    paContinue=0,   /**< Signal that the stream should continue invoking the callback and processing audio. */
+    paComplete=1,   /**< Signal that the stream should stop invoking the callback and finish once all output samples have played. */
+    paAbort=2       /**< Signal that the stream should stop invoking the callback and finish as soon as possible. */
 } PaStreamCallbackResult;
 
 
@@ -702,19 +711,44 @@ typedef enum PaStreamCallbackResult
  Functions of type PaStreamCallback are implemented by PortAudio clients.
  They consume, process or generate audio in response to requests from an
  active PortAudio stream.
+
+ When a stream is running, PortAudio calls the stream callback periodically.
+ The callback function is responsible for processing buffers of audio samples 
+ passed via the input and output parameters.
+
+ The PortAudio stream callback runs at very high or real-time priority.
+ It is required to consistently meet its time deadlines. Do not allocate 
+ memory, access the file system, call library functions or call other functions 
+ from the stream callback that may block or take an unpredictable amount of
+ time to complete.
+
+ In order for a stream to maintain glitch-free operation the callback
+ must consume and return audio data faster than it is recorded and/or
+ played. PortAudio anticipates that each callback invocation may execute for 
+ a duration approaching the duration of frameCount audio frames at the stream 
+ sample rate. It is reasonable to expect to be able to utilise 70% or more of
+ the available CPU time in the PortAudio callback. However, due to buffer size 
+ adaption and other factors, not all host APIs are able to guarantee audio 
+ stability under heavy CPU load with arbitrary fixed callback buffer sizes. 
+ When high callback CPU utilisation is required the most robust behavior 
+ can be achieved by using paFramesPerBufferUnspecified as the 
+ Pa_OpenStream() framesPerBuffer parameter.
      
- @param input and @param output are arrays of interleaved samples,
- the format, packing and number of channels used by the buffers are
+ @param input and @param output are either arrays of interleaved samples or;
+ if non-interleaved samples were requested using the paNonInterleaved sample 
+ format flag, an array of buffer pointers, one non-interleaved buffer for 
+ each channel.
+
+ The format, packing and number of channels used by the buffers are
  determined by parameters to Pa_OpenStream().
      
  @param frameCount The number of sample frames to be processed by
  the stream callback.
 
- @param timeInfo The time in seconds when the first sample of the input
- buffer was received at the audio input, the time in seconds when the first
- sample of the output buffer will begin being played at the audio output, and
- the time in seconds when the stream callback was called.
- See also Pa_GetStreamTime()
+ @param timeInfo Timestamps indicating the ADC capture time of the first sample
+ in the input buffer, the DAC output time of the first sample in the output buffer
+ and the time the callback was invoked. 
+ See PaStreamCallbackTimeInfo and Pa_GetStreamTime()
 
  @param statusFlags Flags indicating whether input and/or output buffers
  have been inserted or will be dropped to overcome underflow or overflow
@@ -725,21 +759,21 @@ typedef enum PaStreamCallbackResult
 
  @return
  The stream callback should return one of the values in the
- PaStreamCallbackResult enumeration. To ensure that the callback continues
+ ::PaStreamCallbackResult enumeration. To ensure that the callback continues
  to be called, it should return paContinue (0). Either paComplete or paAbort
  can be returned to finish stream processing, after either of these values is
  returned the callback will not be called again. If paAbort is returned the
  stream will finish as soon as possible. If paComplete is returned, the stream
  will continue until all buffers generated by the callback have been played.
  This may be useful in applications such as soundfile players where a specific
- duration of output is required. However, it is not necessary to utilise this
+ duration of output is required. However, it is not necessary to utilize this
  mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also
  be used to stop the stream. The callback must always fill the entire output
  buffer irrespective of its return value.
 
  @see Pa_OpenStream, Pa_OpenDefaultStream
 
- @note With the exception of Pa_GetStreamCpuLoad() it is not permissable to call
+ @note With the exception of Pa_GetStreamCpuLoad() it is not permissible to call
  PortAudio API functions from within the stream callback.
 */
 typedef int PaStreamCallback(
@@ -769,7 +803,7 @@ typedef int PaStreamCallback(
  @param framesPerBuffer The number of frames passed to the stream callback
  function, or the preferred block granularity for a blocking read/write stream.
  The special value paFramesPerBufferUnspecified (0) may be used to request that
- the stream callback will recieve an optimal (and possibly varying) number of
+ the stream callback will receive an optimal (and possibly varying) number of
  frames based on host requirements and the requested latency settings.
  Note: With some host APIs, the use of non-zero framesPerBuffer for a callback
  stream may introduce an additional layer of buffering which could introduce
@@ -778,7 +812,7 @@ typedef int PaStreamCallback(
  that a non-zero framesPerBuffer value only be used when your algorithm
  requires a fixed number of frames per stream callback.
  
- @param streamFlags Flags which modify the behaviour of the streaming process.
+ @param streamFlags Flags which modify the behavior of the streaming process.
  This parameter may contain a combination of flags ORed together. Some flags may
  only be relevant to certain buffer formats.
      
@@ -827,7 +861,7 @@ PaError Pa_OpenStream( PaStream** stream,
 
  @param numOutputChannels The number of channels of sound to be delivered to the
  stream callback or passed to Pa_WriteStream. It can range from 1 to the value
- of maxOutputChannels in the PaDeviceInfo record for the default output dvice.
+ of maxOutputChannels in the PaDeviceInfo record for the default output device.
  If 0 the stream is opened as an output-only stream.
 
  @param sampleFormat The sample format of both the input and output buffers
@@ -987,7 +1021,7 @@ typedef struct PaStreamInfo
 /** Retrieve a pointer to a PaStreamInfo structure containing information
  about the specified stream.
  @return A pointer to an immutable PaStreamInfo structure. If the stream
- parameter invalid, or an error is encountered, the function returns NULL.
+ parameter is invalid, or an error is encountered, the function returns NULL.
 
  @param stream A pointer to an open stream previously created with Pa_OpenStream.
 
@@ -1000,13 +1034,20 @@ typedef struct PaStreamInfo
 const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream );
 
 
-/** Determine the current time for the stream according to the same clock used
- to generate buffer timestamps. This time may be used for syncronising other
- events to the audio stream, for example synchronizing audio to MIDI.
+/** Returns the current time in seconds for a stream according to the same clock used
+ to generate callback PaStreamCallbackTimeInfo timestamps. The time values are
+ monotonically increasing and have unspecified origin. 
+ 
+ Pa_GetStreamTime returns valid time values for the entire life of the stream,
+ from when the stream is opened until it is closed. Starting and stopping the stream
+ does not affect the passage of time returned by Pa_GetStreamTime.
+
+ This time may be used for synchronizing other events to the audio stream, for 
+ example synchronizing audio to MIDI.
                                         
  @return The stream's current time in seconds, or 0 if an error occurred.
 
- @see PaTime, PaStreamCallback
+ @see PaTime, PaStreamCallback, PaStreamCallbackTimeInfo
 */
 PaTime Pa_GetStreamTime( PaStream *stream );
 
@@ -1025,7 +1066,7 @@ PaTime Pa_GetStreamTime( PaStream *stream );
  to maintain real-time operation. A value of 0.5 would imply that PortAudio and
  the stream callback was consuming roughly 50% of the available CPU time. The
  return value may exceed 1.0. A value of 0.0 will always be returned for a
- blocking read/write stream, or if an error occurrs.
+ blocking read/write stream, or if an error occurs.
 */
 double Pa_GetStreamCpuLoad( PaStream* stream );
 
@@ -1039,9 +1080,9 @@ double Pa_GetStreamCpuLoad( PaStream* stream );
  @param buffer A pointer to a buffer of sample frames. The buffer contains
  samples in the format specified by the inputParameters->sampleFormat field
  used to open the stream, and the number of channels specified by
- inputParameters->numChannels. If non-interleaved samples were requested,
- buffer is a pointer to the first element of an array of non-interleaved
- buffer pointers, one for each channel.
+ inputParameters->numChannels. If non-interleaved samples were requested using
+ the paNonInterleaved sample format flag, buffer is a pointer to the first element 
+ of an array of buffer pointers, one non-interleaved buffer for each channel.
 
  @param frames The number of frames to be read into buffer. This parameter
  is not constrained to a specific range, however high performance applications
@@ -1065,9 +1106,9 @@ PaError Pa_ReadStream( PaStream* stream,
  @param buffer A pointer to a buffer of sample frames. The buffer contains
  samples in the format specified by the outputParameters->sampleFormat field
  used to open the stream, and the number of channels specified by
- outputParameters->numChannels. If non-interleaved samples were requested,
- buffer is a pointer to the first element of an array of non-interleaved
- buffer pointers, one for each channel.
+ outputParameters->numChannels. If non-interleaved samples were requested using
+ the paNonInterleaved sample format flag, buffer is a pointer to the first element 
+ of an array of buffer pointers, one non-interleaved buffer for each channel.
 
  @param frames The number of frames to be written from buffer. This parameter
  is not constrained to a specific range, however high performance applications
diff --git a/fon/AmplitudeTier.cpp b/fon/AmplitudeTier.cpp
index 240452f..4a7ff79 100644
--- a/fon/AmplitudeTier.cpp
+++ b/fon/AmplitudeTier.cpp
@@ -1,6 +1,6 @@
 /* AmplitudeTier.cpp
  *
- * Copyright (C) 2003-2011 Paul Boersma
+ * Copyright (C) 2003-2011,2014 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
@@ -357,7 +357,7 @@ Sound AmplitudeTier_to_Sound (AmplitudeTier me, double samplingFrequency, long i
 			long begin = mid - interpolationDepth, end = mid + interpolationDepth;
 			if (begin < 1) begin = 1;
 			if (end > thy nx) end = thy nx;
-			angle = NUMpi * (Sampled_indexToX (thee.peek(), begin) - t) / thy dx;
+			angle = NUMpi * (thy f_indexToX (begin) - t) / thy dx;
 			halfampsinangle = 0.5 * amplitude * sin (angle);
 			for (j = begin; j <= end; j ++) {
 				if (fabs (angle) < 1e-6)
diff --git a/fon/AmplitudeTierEditor.cpp b/fon/AmplitudeTierEditor.cpp
index 211ef2d..4f5972f 100644
--- a/fon/AmplitudeTierEditor.cpp
+++ b/fon/AmplitudeTierEditor.cpp
@@ -1,6 +1,6 @@
 /* AmplitudeTierEditor.cpp
  *
- * Copyright (C) 2003-2011,2012 Paul Boersma
+ * Copyright (C) 2003-2011,2012,2014 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
@@ -29,11 +29,11 @@ void structAmplitudeTierEditor :: v_createHelpMenuItems (EditorMenu menu) {
 	EditorMenu_addCommand (menu, L"AmplitudeTier help", 0, menu_cb_AmplitudeTierHelp);
 }
 
-void structAmplitudeTierEditor :: v_play (double tmin, double tmax) {
-	if (d_sound.data) {
-		Sound_playPart (d_sound.data, tmin, tmax, theFunctionEditor_playCallback, this);
+void structAmplitudeTierEditor :: v_play (double fromTime, double toTime) {
+	if (our d_sound.data) {
+		Sound_playPart (our d_sound.data, fromTime, toTime, theFunctionEditor_playCallback, this);
 	} else {
-		//AmplitudeTier_playPart (data, tmin, tmax, FALSE);
+		//AmplitudeTier_playPart (data, fromTime, toTime, FALSE);
 	}
 }
 
diff --git a/fon/AmplitudeTierEditor.h b/fon/AmplitudeTierEditor.h
index bae2833..a92b2f0 100644
--- a/fon/AmplitudeTierEditor.h
+++ b/fon/AmplitudeTierEditor.h
@@ -2,7 +2,7 @@
 #define _AmplitudeTierEditor_h_
 /* AmplitudeTierEditor.h
  *
- * Copyright (C) 2003-2011,2012 Paul Boersma
+ * Copyright (C) 2003-2011,2012,2014 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
@@ -26,7 +26,7 @@
 Thing_define (AmplitudeTierEditor, RealTierEditor) {
 	// overridden methods:
 		virtual void v_createHelpMenuItems (EditorMenu menu);
-		virtual void v_play (double tmin, double tmax);
+		virtual void v_play (double fromTime, double toTime);
 		virtual const wchar_t *
 			v_quantityText ()
 				{ return L"Sound pressure (Pa)"; }
diff --git a/fon/DurationTierEditor.cpp b/fon/DurationTierEditor.cpp
index 2cef264..930de11 100644
--- a/fon/DurationTierEditor.cpp
+++ b/fon/DurationTierEditor.cpp
@@ -1,6 +1,6 @@
 /* DurationTierEditor.cpp
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2014 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
@@ -29,11 +29,11 @@ void structDurationTierEditor :: v_createHelpMenuItems (EditorMenu menu) {
 	EditorMenu_addCommand (menu, L"DurationTier help", 0, menu_cb_DurationTierHelp);
 }
 
-void structDurationTierEditor :: v_play (double a_tmin, double a_tmax) {
-	if (d_sound.data) {
-		Sound_playPart (d_sound.data, a_tmin, a_tmax, NULL, NULL);
+void structDurationTierEditor :: v_play (double fromTime, double toTime) {
+	if (our d_sound.data) {
+		Sound_playPart (our d_sound.data, fromTime, toTime, NULL, NULL);
 	} else {
-		//DurationTier_playPart (data, a_tmin, a_tmax, FALSE);
+		//DurationTier_playPart (data, fromTime, toTime, FALSE);
 	}
 }
 
diff --git a/fon/DurationTierEditor.h b/fon/DurationTierEditor.h
index b2e9dff..a482d78 100644
--- a/fon/DurationTierEditor.h
+++ b/fon/DurationTierEditor.h
@@ -2,7 +2,7 @@
 #define _DurationTierEditor_h_
 /* DurationTierEditor.h
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2014 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
@@ -26,7 +26,7 @@
 Thing_define (DurationTierEditor, RealTierEditor) {
 	// overridden methods:
 		virtual void v_createHelpMenuItems (EditorMenu menu);
-		virtual void v_play (double tmin, double tmax);
+		virtual void v_play (double fromTime, double toTime);
 		virtual double v_minimumLegalValue () { return 0.0; }
 		virtual const wchar_t * v_quantityText () { return L"Relative duration"; }
 		virtual const wchar_t * v_quantityKey () { return L"Relative duration"; }
diff --git a/fon/Formant.cpp b/fon/Formant.cpp
index 6a922c8..565fe24 100644
--- a/fon/Formant.cpp
+++ b/fon/Formant.cpp
@@ -124,7 +124,7 @@ void Formant_drawTracks (Formant me, Graphics g, double tmin, double tmax, doubl
 	for (long itrack = 1; itrack <= ntrack; itrack ++) {
 		for (long iframe = itmin; iframe < itmax; iframe ++) {
 			Formant_Frame curFrame = & my d_frames [iframe], nextFrame = & my d_frames [iframe + 1];
-			double x1 = Sampled_indexToX (me, iframe), x2 = Sampled_indexToX (me, iframe + 1);
+			double x1 = my f_indexToX (iframe), x2 = my f_indexToX (iframe + 1);
 			double f1 = curFrame -> formant [itrack]. frequency;
 			double f2 = nextFrame -> formant [itrack]. frequency;
 			if (NUMdefined (x1) && NUMdefined (f1) && NUMdefined (x2) && NUMdefined (f2))
@@ -142,7 +142,7 @@ void Formant_drawTracks (Formant me, Graphics g, double tmin, double tmax, doubl
 }
 
 void Formant_drawSpeckles_inside (Formant me, Graphics g, double tmin, double tmax, double fmin, double fmax,
-	double suppress_dB, double dotSize)
+	double suppress_dB)
 {
 	long itmin, itmax;
 	double maximumIntensity = 0.0, minimumIntensity;
@@ -162,12 +162,12 @@ void Formant_drawSpeckles_inside (Formant me, Graphics g, double tmin, double tm
 
 	for (long iframe = itmin; iframe <= itmax; iframe ++) {
 		Formant_Frame frame = & my d_frames [iframe];
-		double x = Sampled_indexToX (me, iframe);
+		double x = my f_indexToX (iframe);
 		if (frame -> intensity < minimumIntensity) continue;
 		for (long iformant = 1; iformant <= frame -> nFormants; iformant ++) {
 			double frequency = frame -> formant [iformant]. frequency;
 			if (frequency >= fmin && frequency <= fmax)
-				Graphics_fillCircle_mm (g, x, frequency, dotSize);
+				Graphics_speckle (g, x, frequency);
 		}
 	}
 }
@@ -176,7 +176,7 @@ void Formant_drawSpeckles (Formant me, Graphics g, double tmin, double tmax, dou
 	int garnish)
 {
 	Graphics_setInner (g);
-	Formant_drawSpeckles_inside (me, g, tmin, tmax, 0.0, fmax, suppress_dB, 1.0);
+	Formant_drawSpeckles_inside (me, g, tmin, tmax, 0.0, fmax, suppress_dB);
 	Graphics_unsetInner (g);
 	if (garnish) {
 		Graphics_drawInnerBox (g);
@@ -404,7 +404,7 @@ static double getLocalCost (long iframe, long icand, int itrack, void *closure)
 	if (icand > frame -> nFormants) return 1e30;
 	candidate = & frame -> formant [icand];
 	/*if (candidate -> frequency <= 0.0) candidate -> frequency = 0.001;
-		/*Melder_fatal ("Weird formant frequency %ls Hz.", Melder_double (candidate -> frequency))*/;
+		Melder_fatal ("Weird formant frequency %ls Hz.", Melder_double (candidate -> frequency))*/;
 	Melder_assert (candidate -> bandwidth > 0.0);
 	Melder_assert (itrack > 0 && itrack <= 5);
 	return my dfCost * fabs (candidate -> frequency - my refF [itrack]) +
diff --git a/fon/Formant.h b/fon/Formant.h
index 8614c9c..13558ef 100644
--- a/fon/Formant.h
+++ b/fon/Formant.h
@@ -71,7 +71,7 @@ void Formant_sort (Formant me);
 
 void Formant_drawTracks (Formant me, Graphics g, double tmin, double tmax, double fmax, int garnish);
 void Formant_drawSpeckles_inside (Formant me, Graphics g, double tmin, double tmax, double fmin, double fmax,
-	double suppress_dB, double dotSize);
+	double suppress_dB);
 void Formant_drawSpeckles (Formant me, Graphics g, double tmin, double tmax, double fmax,
 	double suppress_dB, int garnish);
 void Formant_scatterPlot (Formant me, Graphics g, double tmin, double tmax,
diff --git a/fon/FormantGrid.cpp b/fon/FormantGrid.cpp
index b8f2c7b..e184d54 100644
--- a/fon/FormantGrid.cpp
+++ b/fon/FormantGrid.cpp
@@ -1,6 +1,6 @@
 /* FormantGrid.cpp
  *
- * Copyright (C) 2008-2011 Paul Boersma & David Weenink
+ * Copyright (C) 2008-2011,2014 Paul Boersma & David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -76,8 +76,7 @@ void structFormantGrid :: v_scaleX (double xminfrom, double xmaxfrom, double xmi
 	}
 }
 
-void FormantGrid_init (I, double tmin, double tmax, long numberOfFormants) {
-	iam (FormantGrid);
+void FormantGrid_init (FormantGrid me, double tmin, double tmax, long numberOfFormants) {
 	my formants = Ordered_create ();
 	my bandwidths = Ordered_create ();
 	for (long iformant = 1; iformant <= numberOfFormants; iformant ++) {
@@ -295,7 +294,7 @@ FormantGrid Formant_downto_FormantGrid (Formant me) {
 		autoFormantGrid thee = FormantGrid_createEmpty (my xmin, my xmax, my maxnFormants);
 		for (long iframe = 1; iframe <= my nx; iframe ++) {
 			Formant_Frame frame = & my d_frames [iframe];
-			double t = Sampled_indexToX (me, iframe);
+			double t = my f_indexToX (iframe);
 			for (long iformant = 1; iformant <= frame -> nFormants; iformant ++) {
 				Formant_Formant pair = & frame -> formant [iformant];
 				FormantGrid_addFormantPoint (thee.peek(), iformant, t, pair -> frequency);
diff --git a/fon/FormantGrid.h b/fon/FormantGrid.h
index 4090bbd..1dca6cf 100644
--- a/fon/FormantGrid.h
+++ b/fon/FormantGrid.h
@@ -2,7 +2,7 @@
 #define _FormantGrid_h_
 /* FormantGrid.h
  *
- * Copyright (C) 2008-2011 Paul Boersma & David Weenink
+ * Copyright (C) 2008-2011,2014 Paul Boersma & David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,7 +28,7 @@ Thing_declare (Interpreter);
 #include "FormantGrid_def.h"
 oo_CLASS_CREATE (FormantGrid, Function);
 
-void FormantGrid_init (I, double tmin, double tmax, long numberOfFormants);
+void FormantGrid_init (FormantGrid me, double tmin, double tmax, long numberOfFormants);
 FormantGrid FormantGrid_createEmpty (double tmin, double tmax, long numberOfFormants);
 
 FormantGrid FormantGrid_create (double tmin, double tmax, long numberOfFormants,
diff --git a/fon/FormantGridEditor.cpp b/fon/FormantGridEditor.cpp
index 428be50..c2c41d9 100644
--- a/fon/FormantGridEditor.cpp
+++ b/fon/FormantGridEditor.cpp
@@ -1,6 +1,6 @@
 /* FormantGridEditor.cpp
  *
- * Copyright (C) 2008-2011,2012,2013 Paul Boersma & David Weenink
+ * Copyright (C) 2008-2011,2012,2013,2014 Paul Boersma & David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -109,6 +109,7 @@ static void menu_cb_setBandwidthRange (EDITOR_ARGS) {
 static void menu_cb_showBandwidths (EDITOR_ARGS) {
 	EDITOR_IAM (FormantGridEditor);
 	my editingBandwidths = ! my editingBandwidths;
+	my d_bandwidthsToggle -> f_check (my editingBandwidths);
 	FunctionEditor_redraw (me);
 }
 
@@ -173,7 +174,7 @@ static void menu_cb_pitchSettings (EDITOR_ARGS) {
 void structFormantGridEditor :: v_createMenus () {
 	FormantGridEditor_Parent :: v_createMenus ();
 	EditorMenu menu = Editor_addMenu (this, L"Formant", 0);
-	EditorMenu_addCommand (menu, L"Show bandwidths", GuiMenu_CHECKBUTTON + 'B', menu_cb_showBandwidths);
+	our d_bandwidthsToggle = EditorMenu_addCommand (menu, L"Show bandwidths", GuiMenu_CHECKBUTTON, menu_cb_showBandwidths);
 	EditorMenu_addCommand (menu, L"Set formant range...", 0, menu_cb_setFormantRange);
 	EditorMenu_addCommand (menu, L"Set bandwidth range...", 0, menu_cb_setBandwidthRange);
 	EditorMenu_addCommand (menu, L"-- select formant --", 0, NULL);
@@ -192,7 +193,7 @@ void structFormantGridEditor :: v_createMenus () {
 	EditorMenu_addCommand (menu, L"Add point at...", 0, menu_cb_addPointAt);
 	EditorMenu_addCommand (menu, L"-- remove point --", 0, NULL);
 	EditorMenu_addCommand (menu, L"Remove point(s)", GuiMenu_OPTION + 'T', menu_cb_removePoints);
-	if (v_hasSourceMenu ()) {
+	if (our v_hasSourceMenu ()) {
 		menu = Editor_addMenu (this, L"Source", 0);
 		EditorMenu_addCommand (menu, L"Pitch settings...", 0, menu_cb_pitchSettings);
 		//EditorMenu_addCommand (menu, L"Phonation settings...", 0, menu_cb_phonationSettings);
@@ -202,91 +203,91 @@ void structFormantGridEditor :: v_createMenus () {
 /********** DRAWING AREA **********/
 
 void structFormantGridEditor :: v_draw () {
-	FormantGrid grid = (FormantGrid) data;
-	Ordered tiers = editingBandwidths ? grid -> bandwidths : grid -> formants;
+	FormantGrid grid = (FormantGrid) our data;
+	Ordered tiers = our editingBandwidths ? grid -> bandwidths : grid -> formants;
 	RealTier selectedTier = (RealTier) tiers -> item [selectedFormant];
-	double ymin = editingBandwidths ? p_bandwidthFloor   : p_formantFloor;
-	double ymax = editingBandwidths ? p_bandwidthCeiling : p_formantCeiling;
-	Graphics_setColour (d_graphics, Graphics_WHITE);
-	Graphics_setWindow (d_graphics, 0, 1, 0, 1);
-	Graphics_fillRectangle (d_graphics, 0, 1, 0, 1);
-	Graphics_setWindow (d_graphics, d_startWindow, d_endWindow, ymin, ymax);
-	Graphics_setColour (d_graphics, Graphics_RED);
-	Graphics_line (d_graphics, d_startWindow, ycursor, d_endWindow, ycursor);
-	Graphics_setTextAlignment (d_graphics, Graphics_RIGHT, Graphics_HALF);
-	Graphics_text1 (d_graphics, d_startWindow, ycursor, Melder_float (Melder_half (ycursor)));
-	Graphics_setColour (d_graphics, Graphics_BLUE);
-	Graphics_setTextAlignment (d_graphics, Graphics_LEFT, Graphics_TOP);
-	Graphics_text2 (d_graphics, d_endWindow, ymax, Melder_float (Melder_half (ymax)), L" Hz");
-	Graphics_setTextAlignment (d_graphics, Graphics_LEFT, Graphics_HALF);
-	Graphics_text2 (d_graphics, d_endWindow, ymin, Melder_float (Melder_half (ymin)), L" Hz");
-	Graphics_setLineWidth (d_graphics, 1);
-	Graphics_setColour (d_graphics, Graphics_GREY);
-	for (long iformant = 1; iformant <= grid -> formants -> size; iformant ++) if (iformant != selectedFormant) {
+	double ymin = our editingBandwidths ? our p_bandwidthFloor   : our p_formantFloor;
+	double ymax = our editingBandwidths ? our p_bandwidthCeiling : our p_formantCeiling;
+	Graphics_setColour (our d_graphics, Graphics_WHITE);
+	Graphics_setWindow (our d_graphics, 0, 1, 0, 1);
+	Graphics_fillRectangle (our d_graphics, 0, 1, 0, 1);
+	Graphics_setWindow (our d_graphics, our d_startWindow, our d_endWindow, ymin, ymax);
+	Graphics_setColour (our d_graphics, Graphics_RED);
+	Graphics_line (our d_graphics, our d_startWindow, our ycursor, our d_endWindow, our ycursor);
+	Graphics_setTextAlignment (our d_graphics, Graphics_RIGHT, Graphics_HALF);
+	Graphics_text1 (our d_graphics, our d_startWindow, our ycursor, Melder_float (Melder_half (our ycursor)));
+	Graphics_setColour (our d_graphics, Graphics_BLUE);
+	Graphics_setTextAlignment (our d_graphics, Graphics_LEFT, Graphics_TOP);
+	Graphics_text2 (our d_graphics, our d_endWindow, ymax, Melder_float (Melder_half (ymax)), L" Hz");
+	Graphics_setTextAlignment (our d_graphics, Graphics_LEFT, Graphics_HALF);
+	Graphics_text2 (our d_graphics, our d_endWindow, ymin, Melder_float (Melder_half (ymin)), L" Hz");
+	Graphics_setLineWidth (our d_graphics, 1);
+	Graphics_setColour (our d_graphics, Graphics_GREY);
+	for (long iformant = 1; iformant <= grid -> formants -> size; iformant ++) if (iformant != our selectedFormant) {
 		RealTier tier = (RealTier) tiers -> item [iformant];
-		long imin = AnyTier_timeToHighIndex (tier, d_startWindow);
-		long imax = AnyTier_timeToLowIndex (tier, d_endWindow);
+		long imin = AnyTier_timeToHighIndex (tier, our d_startWindow);
+		long imax = AnyTier_timeToLowIndex (tier, our d_endWindow);
 		long n = tier -> points -> size;
 		if (n == 0) {
 		} else if (imax < imin) {
-			double yleft = RealTier_getValueAtTime (tier, d_startWindow);
-			double yright = RealTier_getValueAtTime (tier, d_endWindow);
-			Graphics_line (d_graphics, d_startWindow, yleft, d_endWindow, yright);
+			double yleft = RealTier_getValueAtTime (tier, our d_startWindow);
+			double yright = RealTier_getValueAtTime (tier, our d_endWindow);
+			Graphics_line (our d_graphics, our d_startWindow, yleft, our d_endWindow, yright);
 		} else for (long i = imin; i <= imax; i ++) {
 			RealPoint point = (RealPoint) tier -> points -> item [i];
 			double t = point -> number, y = point -> value;
-			Graphics_fillCircle_mm (d_graphics, t, y, 2);
+			Graphics_fillCircle_mm (our d_graphics, t, y, 2);
 			if (i == 1)
-				Graphics_line (d_graphics, d_startWindow, y, t, y);
+				Graphics_line (our d_graphics, our d_startWindow, y, t, y);
 			else if (i == imin)
-				Graphics_line (d_graphics, t, y, d_startWindow, RealTier_getValueAtTime (tier, d_startWindow));
+				Graphics_line (our d_graphics, t, y, our d_startWindow, RealTier_getValueAtTime (tier, our d_startWindow));
 			if (i == n)
-				Graphics_line (d_graphics, t, y, d_endWindow, y);
+				Graphics_line (our d_graphics, t, y, our d_endWindow, y);
 			else if (i == imax)
-				Graphics_line (d_graphics, t, y, d_endWindow, RealTier_getValueAtTime (tier, d_endWindow));
+				Graphics_line (our d_graphics, t, y, our d_endWindow, RealTier_getValueAtTime (tier, our d_endWindow));
 			else {
 				RealPoint pointRight = (RealPoint) tier -> points -> item [i + 1];
-				Graphics_line (d_graphics, t, y, pointRight -> number, pointRight -> value);
+				Graphics_line (our d_graphics, t, y, pointRight -> number, pointRight -> value);
 			}
 		}
 	}
-	Graphics_setColour (d_graphics, Graphics_BLUE);
-	long ifirstSelected = AnyTier_timeToHighIndex (selectedTier, d_startSelection);
-	long ilastSelected = AnyTier_timeToLowIndex (selectedTier, d_endSelection);
+	Graphics_setColour (our d_graphics, Graphics_BLUE);
+	long ifirstSelected = AnyTier_timeToHighIndex (selectedTier, our d_startSelection);
+	long ilastSelected = AnyTier_timeToLowIndex (selectedTier, our d_endSelection);
 	long n = selectedTier -> points -> size;
-	long imin = AnyTier_timeToHighIndex (selectedTier, d_startWindow);
-	long imax = AnyTier_timeToLowIndex (selectedTier, d_endWindow);
-	Graphics_setLineWidth (d_graphics, 2);
+	long imin = AnyTier_timeToHighIndex (selectedTier, our d_startWindow);
+	long imax = AnyTier_timeToLowIndex (selectedTier, our d_endWindow);
+	Graphics_setLineWidth (our d_graphics, 2);
 	if (n == 0) {
-		Graphics_setTextAlignment (d_graphics, Graphics_CENTRE, Graphics_HALF);
-		Graphics_text (d_graphics, 0.5 * (d_startWindow + d_endWindow),
+		Graphics_setTextAlignment (our d_graphics, Graphics_CENTRE, Graphics_HALF);
+		Graphics_text (our d_graphics, 0.5 * (our d_startWindow + our d_endWindow),
 			0.5 * (ymin + ymax), L"(no points in selected formant tier)");
 	} else if (imax < imin) {
-		double yleft = RealTier_getValueAtTime (selectedTier, d_startWindow);
-		double yright = RealTier_getValueAtTime (selectedTier, d_endWindow);
-		Graphics_line (d_graphics, d_startWindow, yleft, d_endWindow, yright);
+		double yleft = RealTier_getValueAtTime (selectedTier, our d_startWindow);
+		double yright = RealTier_getValueAtTime (selectedTier, our d_endWindow);
+		Graphics_line (our d_graphics, our d_startWindow, yleft, our d_endWindow, yright);
 	} else for (long i = imin; i <= imax; i ++) {
 		RealPoint point = (RealPoint) selectedTier -> points -> item [i];
 		double t = point -> number, y = point -> value;
 		if (i >= ifirstSelected && i <= ilastSelected)
-			Graphics_setColour (d_graphics, Graphics_RED);	
-		Graphics_fillCircle_mm (d_graphics, t, y, 3);
-		Graphics_setColour (d_graphics, Graphics_BLUE);
+			Graphics_setColour (our d_graphics, Graphics_RED);
+		Graphics_fillCircle_mm (our d_graphics, t, y, 3);
+		Graphics_setColour (our d_graphics, Graphics_BLUE);
 		if (i == 1)
-			Graphics_line (d_graphics, d_startWindow, y, t, y);
+			Graphics_line (our d_graphics, our d_startWindow, y, t, y);
 		else if (i == imin)
-			Graphics_line (d_graphics, t, y, d_startWindow, RealTier_getValueAtTime (selectedTier, d_startWindow));
+			Graphics_line (our d_graphics, t, y, our d_startWindow, RealTier_getValueAtTime (selectedTier, our d_startWindow));
 		if (i == n)
-			Graphics_line (d_graphics, t, y, d_endWindow, y);
+			Graphics_line (our d_graphics, t, y, our d_endWindow, y);
 		else if (i == imax)
-			Graphics_line (d_graphics, t, y, d_endWindow, RealTier_getValueAtTime (selectedTier, d_endWindow));
+			Graphics_line (our d_graphics, t, y, our d_endWindow, RealTier_getValueAtTime (selectedTier, our d_endWindow));
 		else {
 			RealPoint pointRight = (RealPoint) selectedTier -> points -> item [i + 1];
-			Graphics_line (d_graphics, t, y, pointRight -> number, pointRight -> value);
+			Graphics_line (our d_graphics, t, y, pointRight -> number, pointRight -> value);
 		}
 	}
-	Graphics_setLineWidth (d_graphics, 1);
-	Graphics_setColour (d_graphics, Graphics_BLACK);
+	Graphics_setLineWidth (our d_graphics, 1);
+	Graphics_setColour (our d_graphics, Graphics_BLACK);
 }
 
 static void drawWhileDragging (FormantGridEditor me, double xWC, double yWC, long first, long last, double dt, double dy) {
@@ -324,23 +325,23 @@ static void drawWhileDragging (FormantGridEditor me, double xWC, double yWC, lon
 }
 
 int structFormantGridEditor :: v_click (double xWC, double yWC, bool shiftKeyPressed) {
-	FormantGrid grid = (FormantGrid) data;
+	FormantGrid grid = (FormantGrid) our data;
 	Ordered tiers = editingBandwidths ? grid -> bandwidths : grid -> formants;
 	RealTier tier = (RealTier) tiers -> item [selectedFormant];
-	double ymin = editingBandwidths ? p_bandwidthFloor   : p_formantFloor;
-	double ymax = editingBandwidths ? p_bandwidthCeiling : p_formantCeiling;
+	double ymin = our editingBandwidths ? our p_bandwidthFloor   : our p_formantFloor;
+	double ymax = our editingBandwidths ? our p_bandwidthCeiling : our p_formantCeiling;
 	long inearestPoint, ifirstSelected, ilastSelected;
 	RealPoint nearestPoint;
 	double dt = 0, df = 0;
-	int draggingSelection;
+	bool draggingSelection;
 
 	/*
 	 * Perform the default action: move cursor.
 	 */
-	d_startSelection = d_endSelection = xWC;
-	ycursor = (1.0 - yWC) * ymin + yWC * ymax;
-	Graphics_setWindow (d_graphics, d_startWindow, d_endWindow, ymin, ymax);
-	yWC = ycursor;
+	//d_startSelection = d_endSelection = xWC;
+	our ycursor = (1.0 - yWC) * ymin + yWC * ymax;
+	Graphics_setWindow (our d_graphics, our d_startWindow, our d_endWindow, ymin, ymax);
+	yWC = our ycursor;
 
 	/*
 	 * Clicked on a point?
@@ -350,18 +351,18 @@ int structFormantGridEditor :: v_click (double xWC, double yWC, bool shiftKeyPre
 		return FormantGridEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
 	}
 	nearestPoint = (RealPoint) tier -> points -> item [inearestPoint];
-	if (Graphics_distanceWCtoMM (d_graphics, xWC, yWC, nearestPoint -> number, nearestPoint -> value) > 1.5) {
-		return FormantGridEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
+	if (Graphics_distanceWCtoMM (our d_graphics, xWC, yWC, nearestPoint -> number, nearestPoint -> value) > 1.5) {
+		return our FormantGridEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
 	}
 
 	/*
 	 * Clicked on a selected point?
 	 */
 	draggingSelection = shiftKeyPressed &&
-		nearestPoint -> number > d_startSelection && nearestPoint -> number < d_endSelection;
+		nearestPoint -> number > our d_startSelection && nearestPoint -> number < our d_endSelection;
 	if (draggingSelection) {
-		ifirstSelected = AnyTier_timeToHighIndex (tier, d_startSelection);
-		ilastSelected = AnyTier_timeToLowIndex (tier, d_endSelection);
+		ifirstSelected = AnyTier_timeToHighIndex (tier, our d_startSelection);
+		ilastSelected = AnyTier_timeToLowIndex (tier, our d_endSelection);
 		Editor_save (this, L"Drag points");
 	} else {
 		ifirstSelected = ilastSelected = inearestPoint;
@@ -371,11 +372,11 @@ int structFormantGridEditor :: v_click (double xWC, double yWC, bool shiftKeyPre
 	/*
 	 * Drag.
 	 */
-	Graphics_xorOn (d_graphics, Graphics_MAROON);
+	Graphics_xorOn (our d_graphics, Graphics_MAROON);
 	drawWhileDragging (this, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
-	while (Graphics_mouseStillDown (d_graphics)) {
+	while (Graphics_mouseStillDown (our d_graphics)) {
 		double xWC_new, yWC_new;
-		Graphics_getMouseLocation (d_graphics, & xWC_new, & yWC_new);
+		Graphics_getMouseLocation (our d_graphics, & xWC_new, & yWC_new);
 		if (xWC_new != xWC || yWC_new != yWC) {
 			drawWhileDragging (this, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
 			dt += xWC_new - xWC, df += yWC_new - yWC;
@@ -383,7 +384,7 @@ int structFormantGridEditor :: v_click (double xWC, double yWC, bool shiftKeyPre
 			drawWhileDragging (this, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
 		}
 	}
-	Graphics_xorOff (d_graphics);
+	Graphics_xorOff (our d_graphics);
 
 	/*
 	 * Dragged inside window?
@@ -395,11 +396,11 @@ int structFormantGridEditor :: v_click (double xWC, double yWC, bool shiftKeyPre
 	 */
 	RealPoint *points = (RealPoint *) tier -> points -> item;
 	double newTime = points [ifirstSelected] -> number + dt;
-	if (newTime < d_tmin) return 1;   // outside domain
+	if (newTime < our tmin) return 1;   // outside domain
 	if (ifirstSelected > 1 && newTime <= points [ifirstSelected - 1] -> number)
 		return 1;   // past left neighbour
 	newTime = points [ilastSelected] -> number + dt;
-	if (newTime > d_tmax) return 1;   // outside domain
+	if (newTime > our tmax) return 1;   // outside domain
 	if (ilastSelected < tier -> points -> size && newTime >= points [ilastSelected + 1] -> number)
 		return 1;   // past right neighbour
 
@@ -416,34 +417,34 @@ int structFormantGridEditor :: v_click (double xWC, double yWC, bool shiftKeyPre
 	 * Make sure that the same points are still selected (a problem with Undo...).
 	 */
 
-	if (draggingSelection) d_startSelection += dt, d_endSelection += dt;
+	if (draggingSelection) our d_startSelection += dt, our d_endSelection += dt;
 	if (ifirstSelected == ilastSelected) {
 		/*
 		 * Move crosshair to only selected formant point.
 		 */
 		RealPoint point = (RealPoint) tier -> points -> item [ifirstSelected];
-		d_startSelection = d_endSelection = point -> number;
-		ycursor = point -> value;
+		our d_startSelection = our d_endSelection = point -> number;
+		our ycursor = point -> value;
 	} else {
 		/*
 		 * Move crosshair to mouse location.
 		 */
-		/*cursor += dt;*/
-		ycursor += df;
+		/*our cursor += dt;*/
+		our ycursor += df;
 	}
 
-	broadcastDataChanged ();
+	our broadcastDataChanged ();
 	return 1;   // update needed
 }
 
 void structFormantGridEditor :: v_play (double tmin, double tmax) {
-	FormantGrid_playPart ((FormantGrid) data, tmin, tmax, p_play_samplingFrequency,
-		p_source_pitch_tStart, p_source_pitch_f0Start,
-		p_source_pitch_tMid,   p_source_pitch_f0Mid,
-		p_source_pitch_tEnd,   p_source_pitch_f0End,
-		p_source_phonation_adaptFactor, p_source_phonation_maximumPeriod,
-		p_source_phonation_openPhase,   p_source_phonation_collisionPhase,
-		p_source_phonation_power1,      p_source_phonation_power2,
+	FormantGrid_playPart ((FormantGrid) our data, tmin, tmax, our p_play_samplingFrequency,
+		our p_source_pitch_tStart, our p_source_pitch_f0Start,
+		our p_source_pitch_tMid,   our p_source_pitch_f0Mid,
+		our p_source_pitch_tEnd,   our p_source_pitch_f0End,
+		our p_source_phonation_adaptFactor, our p_source_phonation_maximumPeriod,
+		our p_source_phonation_openPhase,   our p_source_phonation_collisionPhase,
+		our p_source_phonation_power1,      our p_source_phonation_power2,
 		theFunctionEditor_playCallback, this);
 }
 
diff --git a/fon/FormantGridEditor.h b/fon/FormantGridEditor.h
index c5f56a6..84cb339 100644
--- a/fon/FormantGridEditor.h
+++ b/fon/FormantGridEditor.h
@@ -26,6 +26,7 @@ Thing_define (FormantGridEditor, FunctionEditor) {
 	// new data:
 	public:
 		bool editingBandwidths;
+		GuiMenuItem d_bandwidthsToggle;
 		long selectedFormant;
 		double ycursor;
 	// overridden methods:
diff --git a/fon/FormantTier.cpp b/fon/FormantTier.cpp
index f0783f8..ccadf98 100644
--- a/fon/FormantTier.cpp
+++ b/fon/FormantTier.cpp
@@ -1,6 +1,6 @@
 /* FormantTier.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -144,7 +144,7 @@ void FormantTier_speckle (FormantTier me, Graphics g, double tmin, double tmax,
 		double t = point -> time;
 		for (long j = 1; j <= point -> numberOfFormants; j ++) {
 			double f = point -> formant [j-1];
-			if (f <= fmax) Graphics_fillCircle_mm (g, t, f, 1.0);
+			if (f <= fmax) Graphics_speckle (g, t, f);
 		}
 	}
 	Graphics_unsetInner (g);
@@ -162,7 +162,7 @@ FormantTier Formant_downto_FormantTier (Formant me) {
 		autoFormantTier thee = FormantTier_create (my xmin, my xmax);
 		for (long i = 1; i <= my nx; i ++) {
 			Formant_Frame frame = & my d_frames [i];
-			autoFormantPoint point = FormantPoint_create (Sampled_indexToX (me, i));
+			autoFormantPoint point = FormantPoint_create (my f_indexToX (i));
 			point -> numberOfFormants = frame -> nFormants > 10 ? 10 : frame -> nFormants;
 			for (long j = 1; j <= point -> numberOfFormants; j ++) {
 				Formant_Formant pair = & frame -> formant [j];
diff --git a/fon/FunctionEditor.cpp b/fon/FunctionEditor.cpp
index c2d0774..3c80a56 100644
--- a/fon/FunctionEditor.cpp
+++ b/fon/FunctionEditor.cpp
@@ -1,6 +1,6 @@
 /* FunctionEditor.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -53,15 +53,15 @@ static int group_equalDomain (double tmin, double tmax) {
 	if (nGroup == 0) return 1;
 	for (int i = 1; i <= maxGroup; i ++)
 		if (theGroup [i])
-			return tmin == theGroup [i] -> d_tmin && tmax == theGroup [i] -> d_tmax;
+			return tmin == theGroup [i] -> tmin && tmax == theGroup [i] -> tmax;
 	return 0;   // should not occur
 }
 
 static void updateScrollBar (FunctionEditor me) {
 /* We cannot call this immediately after creation. */
-	int slider_size = (my d_endWindow - my d_startWindow) / (my d_tmax - my d_tmin) * maximumScrollBarValue - 1;
+	int slider_size = (my d_endWindow - my d_startWindow) / (my tmax - my tmin) * maximumScrollBarValue - 1;
 	int increment, page_increment;
-	int value = (my d_startWindow - my d_tmin) / (my d_tmax - my d_tmin) * maximumScrollBarValue + 1;
+	int value = (my d_startWindow - my tmin) / (my tmax - my tmin) * maximumScrollBarValue + 1;
 	if (slider_size < 1) slider_size = 1;
 	if (value > maximumScrollBarValue - slider_size)
 		value = maximumScrollBarValue - slider_size;
@@ -88,8 +88,8 @@ static void updateGroup (FunctionEditor me) {
 }
 
 static void drawNow (FunctionEditor me) {
-	int leftFromWindow = my d_startWindow > my d_tmin;
-	int rightFromWindow = my d_endWindow < my d_tmax;
+	int leftFromWindow = my d_startWindow > my tmin;
+	int rightFromWindow = my d_endWindow < my tmax;
 	int cursorVisible = my d_startSelection == my d_endSelection && my d_startSelection >= my d_startWindow && my d_startSelection <= my d_endWindow;
 	int selection = my d_endSelection > my d_startSelection;
 	int beginVisible, endVisible;
@@ -166,7 +166,7 @@ static void drawNow (FunctionEditor me) {
 		double window = my d_endWindow - my d_startWindow;
 		double left =
 			my d_startSelection == my d_startWindow ? my functionViewerLeft + MARGIN :
-			my d_startSelection == my d_tmin ? my functionViewerLeft :
+			my d_startSelection == my tmin ? my functionViewerLeft :
 			my d_startSelection < my d_startWindow ? my functionViewerLeft + MARGIN * 0.3 :
 			my d_startSelection < my d_endWindow ? my functionViewerLeft + MARGIN + (my functionViewerRight - my functionViewerLeft - MARGIN * 2) * (my d_startSelection - my d_startWindow) / window :
 			my d_startSelection == my d_endWindow ? my functionViewerRight - MARGIN : my functionViewerRight - MARGIN * 0.7;
@@ -175,7 +175,7 @@ static void drawNow (FunctionEditor me) {
 			my d_endSelection == my d_startWindow ? my functionViewerLeft + MARGIN :
 			my d_endSelection < my d_endWindow ? my functionViewerLeft + MARGIN + (my functionViewerRight - my functionViewerLeft - MARGIN * 2) * (my d_endSelection - my d_startWindow) / window :
 			my d_endSelection == my d_endWindow ? my functionViewerRight - MARGIN :
-			my d_endSelection < my d_tmax ? my functionViewerRight - MARGIN * 0.3 : my functionViewerRight;
+			my d_endSelection < my tmax ? my functionViewerRight - MARGIN * 0.3 : my functionViewerRight;
 		my rect [7]. left = left;
 		my rect [7]. right = right;
 		my rect [7]. bottom = my height - space - TOP_MARGIN;
@@ -187,7 +187,7 @@ static void drawNow (FunctionEditor me) {
 	 */
 	Graphics_setViewport (my d_graphics, my functionViewerLeft, my functionViewerRight, 0, my height);
 	Graphics_setWindow (my d_graphics, my functionViewerLeft, my functionViewerRight, 0, my height);
-	Graphics_setGrey (my d_graphics, 0.85);
+	Graphics_setColour (my d_graphics, Graphics_WINDOW_BACKGROUND_COLOUR);
 	Graphics_fillRectangle (my d_graphics, my functionViewerLeft + MARGIN, my selectionViewerRight - MARGIN, my height - (TOP_MARGIN + space), my height);
 	Graphics_fillRectangle (my d_graphics, my functionViewerLeft, my functionViewerLeft + MARGIN, BOTTOM_MARGIN + ( leftFromWindow ? space * 2 : 0 ), my height);
 	Graphics_fillRectangle (my d_graphics, my functionViewerRight - MARGIN, my functionViewerRight, BOTTOM_MARGIN + ( rightFromWindow ? space * 2 : 0 ), my height);
@@ -221,7 +221,7 @@ static void drawNow (FunctionEditor me) {
 			const wchar_t *format = my v_format_long ();
 			double value = NUMundefined, inverseValue = 0.0;
 			switch (i) {
-				case 0: format = my v_format_totalDuration (), value = my d_tmax - my d_tmin; break;
+				case 0: format = my v_format_totalDuration (), value = my tmax - my tmin; break;
 				case 1: format = my v_format_window (), value = my d_endWindow - my d_startWindow;
 					/*
 					 * Window domain text.
@@ -234,8 +234,8 @@ static void drawNow (FunctionEditor me) {
 					Graphics_setColour (my d_graphics, Graphics_BLACK);
 					Graphics_setTextAlignment (my d_graphics, Graphics_CENTRE, Graphics_HALF);
 				break;
-				case 2: value = my d_startWindow - my d_tmin; break;
-				case 3: value = my d_tmax - my d_endWindow; break;
+				case 2: value = my d_startWindow - my tmin; break;
+				case 3: value = my tmax - my d_endWindow; break;
 				case 4: value = my marker [1] - my d_startWindow; break;
 				case 5: value = my marker [2] - my marker [1]; break;
 				case 6: value = my marker [3] - my marker [2]; break;
@@ -336,6 +336,7 @@ static void drawNow (FunctionEditor me) {
 	/*
 	 * End of inner drawing.
 	 */
+	Graphics_flushWs (my d_graphics);
 	Graphics_setViewport (my d_graphics, my functionViewerLeft, my selectionViewerRight, 0, my height);
 }
 
@@ -353,13 +354,13 @@ void structFunctionEditor :: v_destroy () {
 
 void structFunctionEditor :: v_info () {
 	FunctionEditor_Parent :: v_info ();
-	MelderInfo_writeLine (L"Editor start: ", Melder_double (d_tmin), L" ", v_format_units ());
-	MelderInfo_writeLine (L"Editor end: ", Melder_double (d_tmax), L" ", v_format_units ());
-	MelderInfo_writeLine (L"Window start: ", Melder_double (d_startWindow), L" ", v_format_units ());
-	MelderInfo_writeLine (L"Window end: ", Melder_double (d_endWindow), L" ", v_format_units ());
-	MelderInfo_writeLine (L"Selection start: ", Melder_double (d_startSelection), L" ", v_format_units ());
-	MelderInfo_writeLine (L"Selection end: ", Melder_double (d_endSelection), L" ", v_format_units ());
-	MelderInfo_writeLine (L"Arrow scroll step: ", Melder_double (p_arrowScrollStep), L" ", v_format_units ());
+	MelderInfo_writeLine (L"Editor start: ", Melder_double (our tmin), L" ", v_format_units ());
+	MelderInfo_writeLine (L"Editor end: ", Melder_double (our tmax), L" ", v_format_units ());
+	MelderInfo_writeLine (L"Window start: ", Melder_double (our d_startWindow), L" ", v_format_units ());
+	MelderInfo_writeLine (L"Window end: ", Melder_double (our d_endWindow), L" ", v_format_units ());
+	MelderInfo_writeLine (L"Selection start: ", Melder_double (our d_startSelection), L" ", v_format_units ());
+	MelderInfo_writeLine (L"Selection end: ", Melder_double (our d_endSelection), L" ", v_format_units ());
+	MelderInfo_writeLine (L"Arrow scroll step: ", Melder_double (our p_arrowScrollStep), L" ", v_format_units ());
 	MelderInfo_writeLine (L"Group: ", group ? L"yes" : L"no");
 }
 
@@ -465,11 +466,11 @@ static void menu_cb_zoom (EDITOR_ARGS) {
 		SET_REAL (L"To", my d_endWindow)
 	EDITOR_DO
 		my d_startWindow = GET_REAL (L"From");
-		if (my d_startWindow < my d_tmin + 1e-12)
-			my d_startWindow = my d_tmin;
+		if (my d_startWindow < my tmin + 1e-12)
+			my d_startWindow = my tmin;
 		my d_endWindow = GET_REAL (L"To");
-		if (my d_endWindow > my d_tmax - 1e-12)
-			my d_endWindow = my d_tmax;
+		if (my d_endWindow > my tmax - 1e-12)
+			my d_endWindow = my tmax;
 		my v_updateText ();
 		updateScrollBar (me);
 		/*Graphics_updateWs (my d_graphics);*/ drawNow (me);
@@ -478,8 +479,8 @@ static void menu_cb_zoom (EDITOR_ARGS) {
 }
 
 static void do_showAll (FunctionEditor me) {
-	my d_startWindow = my d_tmin;
-	my d_endWindow = my d_tmax;
+	my d_startWindow = my tmin;
+	my d_endWindow = my tmax;
 	my v_updateText ();
 	updateScrollBar (me);
 	/*Graphics_updateWs (my d_graphics);*/ drawNow (me);
@@ -516,11 +517,11 @@ static void do_zoomOut (FunctionEditor me) {
 	double shift = (my d_endWindow - my d_startWindow) / 2;
 	MelderAudio_stopPlaying (MelderAudio_IMPLICIT);   /* Quickly, before window changes. */
 	my d_startWindow -= shift;
-	if (my d_startWindow < my d_tmin + 1e-12)
-		my d_startWindow = my d_tmin;
+	if (my d_startWindow < my tmin + 1e-12)
+		my d_startWindow = my tmin;
 	my d_endWindow += shift;
-	if (my d_endWindow > my d_tmax - 1e-12)
-		my d_endWindow = my d_tmax;
+	if (my d_endWindow > my tmax - 1e-12)
+		my d_endWindow = my tmax;
 	my v_updateText ();
 	updateScrollBar (me);
 	/*Graphics_updateWs (my d_graphics);*/ drawNow (me);
@@ -659,11 +660,11 @@ static void menu_cb_select (EDITOR_ARGS) {
 		SET_REAL (L"End of selection", my d_endSelection)
 	EDITOR_DO
 		my d_startSelection = GET_REAL (L"Start of selection");
-		if (my d_startSelection < my d_tmin + 1e-12)
-			my d_startSelection = my d_tmin;
+		if (my d_startSelection < my tmin + 1e-12)
+			my d_startSelection = my tmin;
 		my d_endSelection = GET_REAL (L"End of selection");
-		if (my d_endSelection > my d_tmax - 1e-12)
-			my d_endSelection = my d_tmax;
+		if (my d_endSelection > my tmax - 1e-12)
+			my d_endSelection = my tmax;
 		if (my d_startSelection > my d_endSelection) {
 			double dummy = my d_startSelection;
 			my d_startSelection = my d_endSelection;
@@ -699,8 +700,8 @@ static void menu_cb_moveCursorTo (EDITOR_ARGS) {
 		SET_REAL (L"Position", 0.5 * (my d_startSelection + my d_endSelection))
 	EDITOR_DO
 		double position = GET_REAL (L"Position");
-		if (position < my d_tmin + 1e-12) position = my d_tmin;
-		if (position > my d_tmax - 1e-12) position = my d_tmax;
+		if (position < my tmin + 1e-12) position = my tmin;
+		if (position > my tmax - 1e-12) position = my tmax;
 		my d_startSelection = my d_endSelection = position;
 		my v_updateText ();
 		/*Graphics_updateWs (my d_graphics);*/ drawNow (me);
@@ -715,8 +716,8 @@ static void menu_cb_moveCursorBy (EDITOR_ARGS) {
 	EDITOR_OK
 	EDITOR_DO
 		double position = 0.5 * (my d_startSelection + my d_endSelection) + GET_REAL (L"Distance");
-		if (position < my d_tmin) position = my d_tmin;
-		if (position > my d_tmax) position = my d_tmax;
+		if (position < my tmin) position = my tmin;
+		if (position > my tmax) position = my tmax;
 		my d_startSelection = my d_endSelection = position;
 		my v_updateText ();
 		/*Graphics_updateWs (my d_graphics);*/ drawNow (me);
@@ -731,8 +732,8 @@ static void menu_cb_moveBby (EDITOR_ARGS) {
 	EDITOR_OK
 	EDITOR_DO
 		double position = my d_startSelection + GET_REAL (L"Distance");
-		if (position < my d_tmin) position = my d_tmin;
-		if (position > my d_tmax) position = my d_tmax;
+		if (position < my tmin) position = my tmin;
+		if (position > my tmax) position = my tmax;
 		my d_startSelection = position;
 		if (my d_startSelection > my d_endSelection) {
 			double dummy = my d_startSelection;
@@ -752,8 +753,8 @@ static void menu_cb_moveEby (EDITOR_ARGS) {
 	EDITOR_OK
 	EDITOR_DO
 		double position = my d_endSelection + GET_REAL (L"Distance");
-		if (position < my d_tmin) position = my d_tmin;
-		if (position > my d_tmax) position = my d_tmax;
+		if (position < my tmin) position = my tmin;
+		if (position > my tmax) position = my tmax;
 		my d_endSelection = position;
 		if (my d_startSelection > my d_endSelection) {
 			double dummy = my d_startSelection;
@@ -772,18 +773,18 @@ void FunctionEditor_shift (FunctionEditor me, double shift, bool needsUpdateGrou
 	trace ("shifting by %.17g", shift);
 	if (shift < 0.0) {
 		my d_startWindow += shift;
-		if (my d_startWindow < my d_tmin + 1e-12)
-			my d_startWindow = my d_tmin;
+		if (my d_startWindow < my tmin + 1e-12)
+			my d_startWindow = my tmin;
 		my d_endWindow = my d_startWindow + windowLength;
-		if (my d_endWindow > my d_tmax - 1e-12)
-			my d_endWindow = my d_tmax;
+		if (my d_endWindow > my tmax - 1e-12)
+			my d_endWindow = my tmax;
 	} else {
 		my d_endWindow += shift;
-		if (my d_endWindow > my d_tmax - 1e-12)
-			my d_endWindow = my d_tmax;
+		if (my d_endWindow > my tmax - 1e-12)
+			my d_endWindow = my tmax;
 		my d_startWindow = my d_endWindow - windowLength;
-		if (my d_startWindow < my d_tmin + 1e-12)
-			my d_startWindow = my d_tmin;
+		if (my d_startWindow < my tmin + 1e-12)
+			my d_startWindow = my tmin;
 	}
 	FunctionEditor_marksChanged (me, needsUpdateGroup);
 }
@@ -811,38 +812,38 @@ static void scrollToView (FunctionEditor me, double t) {
 static void menu_cb_selectEarlier (EDITOR_ARGS) {
 	EDITOR_IAM (FunctionEditor);
 	my d_startSelection -= my p_arrowScrollStep;
-	if (my d_startSelection < my d_tmin + 1e-12)
-		my d_startSelection = my d_tmin;
+	if (my d_startSelection < my tmin + 1e-12)
+		my d_startSelection = my tmin;
 	my d_endSelection -= my p_arrowScrollStep;
-	if (my d_endSelection < my d_tmin + 1e-12)
-		my d_endSelection = my d_tmin;
+	if (my d_endSelection < my tmin + 1e-12)
+		my d_endSelection = my tmin;
 	scrollToView (me, 0.5 * (my d_startSelection + my d_endSelection));
 }
 
 static void menu_cb_selectLater (EDITOR_ARGS) {
 	EDITOR_IAM (FunctionEditor);
 	my d_startSelection += my p_arrowScrollStep;
-	if (my d_startSelection > my d_tmax - 1e-12)
-		my d_startSelection = my d_tmax;
+	if (my d_startSelection > my tmax - 1e-12)
+		my d_startSelection = my tmax;
 	my d_endSelection += my p_arrowScrollStep;
-	if (my d_endSelection > my d_tmax - 1e-12)
-		my d_endSelection = my d_tmax;
+	if (my d_endSelection > my tmax - 1e-12)
+		my d_endSelection = my tmax;
 	scrollToView (me, 0.5 * (my d_startSelection + my d_endSelection));
 }
 
 static void menu_cb_moveBleft (EDITOR_ARGS) {
 	EDITOR_IAM (FunctionEditor);
 	my d_startSelection -= my p_arrowScrollStep;
-	if (my d_startSelection < my d_tmin + 1e-12)
-		my d_startSelection = my d_tmin;
+	if (my d_startSelection < my tmin + 1e-12)
+		my d_startSelection = my tmin;
 	scrollToView (me, 0.5 * (my d_startSelection + my d_endSelection));
 }
 
 static void menu_cb_moveBright (EDITOR_ARGS) {
 	EDITOR_IAM (FunctionEditor);
 	my d_startSelection += my p_arrowScrollStep;
-	if (my d_startSelection > my d_tmax - 1e-12)
-		my d_startSelection = my d_tmax;
+	if (my d_startSelection > my tmax - 1e-12)
+		my d_startSelection = my tmax;
 	if (my d_startSelection > my d_endSelection) {
 		double dummy = my d_startSelection;
 		my d_startSelection = my d_endSelection;
@@ -854,8 +855,8 @@ static void menu_cb_moveBright (EDITOR_ARGS) {
 static void menu_cb_moveEleft (EDITOR_ARGS) {
 	EDITOR_IAM (FunctionEditor);
 	my d_endSelection -= my p_arrowScrollStep;
-	if (my d_endSelection < my d_tmin + 1e-12)
-		my d_endSelection = my d_tmin;
+	if (my d_endSelection < my tmin + 1e-12)
+		my d_endSelection = my tmin;
 	if (my d_startSelection > my d_endSelection) {
 		double dummy = my d_startSelection;
 		my d_startSelection = my d_endSelection;
@@ -867,8 +868,8 @@ static void menu_cb_moveEleft (EDITOR_ARGS) {
 static void menu_cb_moveEright (EDITOR_ARGS) {
 	EDITOR_IAM (FunctionEditor);
 	my d_endSelection += my p_arrowScrollStep;
-	if (my d_endSelection > my d_tmax - 1e-12)
-		my d_endSelection = my d_tmax;
+	if (my d_endSelection > my tmax - 1e-12)
+		my d_endSelection = my tmax;
 	scrollToView (me, 0.5 * (my d_startSelection + my d_endSelection));
 }
 
@@ -878,24 +879,46 @@ static void gui_cb_scroll (I, GuiScrollBarEvent event) {
 	iam (FunctionEditor);
 	if (my d_graphics == NULL) return;   // ignore events during creation
 	double value = event -> scrollBar -> f_getValue ();
-	double shift = my d_tmin + (value - 1) * (my d_tmax - my d_tmin) / maximumScrollBarValue - my d_startWindow;
-	if (shift != 0.0) {
-		int i;
+	double shift = my tmin + (value - 1) * (my tmax - my tmin) / maximumScrollBarValue - my d_startWindow;
+	bool shifted = shift != 0.0;
+	double oldSliderSize = (my d_endWindow - my d_startWindow) / (my tmax - my tmin) * maximumScrollBarValue - 1;
+	double newSliderSize = event -> scrollBar -> f_getSliderSize ();
+	bool zoomed = newSliderSize != oldSliderSize;
+	#if ! cocoa
+		zoomed = false;
+	#endif
+	if (shifted) {
 		my d_startWindow += shift;
-		if (my d_startWindow < my d_tmin + 1e-12) my d_startWindow = my d_tmin;
+		if (my d_startWindow < my tmin + 1e-12) my d_startWindow = my tmin;
 		my d_endWindow += shift;
-		if (my d_endWindow > my d_tmax - 1e-12) my d_endWindow = my d_tmax;
+		if (my d_endWindow > my tmax - 1e-12) my d_endWindow = my tmax;
+	}
+	if (zoomed) {
+		double zoom = (newSliderSize + 1) * (my tmax - my tmin) / maximumScrollBarValue;
+		my d_endWindow = my d_startWindow + zoom;
+		if (my d_endWindow > my tmax - 1e-12) my d_endWindow = my tmax;
+	}
+	if (shifted || zoomed) {
 		my v_updateText ();
-		/*Graphics_clearWs (my d_graphics);*/
-		drawNow (me);   /* Do not wait for expose event. */
+		updateScrollBar (me);
+		#if cocoa
+			Graphics_updateWs (my d_graphics);
+		#else
+			/*Graphics_clearWs (my d_graphics);*/
+			drawNow (me);   /* Do not wait for expose event. */
+		#endif
 		if (! my group || ! my pref_synchronizedZoomAndScroll ()) return;
-		for (i = 1; i <= maxGroup; i ++) if (theGroup [i] && theGroup [i] != me) {
+		for (int i = 1; i <= maxGroup; i ++) if (theGroup [i] && theGroup [i] != me) {
 			theGroup [i] -> d_startWindow = my d_startWindow;
 			theGroup [i] -> d_endWindow = my d_endWindow;
 			FunctionEditor_updateText (theGroup [i]);
 			updateScrollBar (theGroup [i]);
-			Graphics_clearWs (theGroup [i] -> d_graphics);
-			drawNow (theGroup [i]);
+			#if cocoa
+				Graphics_updateWs (theGroup [i] -> d_graphics);
+			#else
+				Graphics_clearWs (theGroup [i] -> d_graphics);
+				drawNow (theGroup [i]);
+			#endif
 		}
 	}
 }
@@ -916,9 +939,9 @@ static void gui_checkbutton_cb_group (I, GuiCheckButtonEvent event) {
 		}
 		my d_startSelection = thy d_startSelection;
 		my d_endSelection = thy d_endSelection;
-		if (my d_tmin > thy d_tmin || my d_tmax < thy d_tmax) {
-			if (my d_tmin > thy d_tmin) my d_tmin = thy d_tmin;
-			if (my d_tmax < thy d_tmax) my d_tmax = thy d_tmax;
+		if (my tmin > thy tmin || my tmax < thy tmax) {
+			if (my tmin > thy tmin) my tmin = thy tmin;
+			if (my tmax < thy tmax) my tmax = thy tmax;
 			my v_updateText ();
 			updateScrollBar (me);
 			Graphics_updateWs (my d_graphics);
@@ -926,12 +949,12 @@ static void gui_checkbutton_cb_group (I, GuiCheckButtonEvent event) {
 			my v_updateText ();
 			updateScrollBar (me);
 			Graphics_updateWs (my d_graphics);
-			if (my d_tmin < thy d_tmin || my d_tmax > thy d_tmax)
+			if (my tmin < thy tmin || my tmax > thy tmax)
 				for (i = 1; i <= maxGroup; i ++) if (theGroup [i] && theGroup [i] != me) {
-					if (my d_tmin < thy d_tmin)
-						theGroup [i] -> d_tmin = my d_tmin;
-					if (my d_tmax > thy d_tmax)
-						theGroup [i] -> d_tmax = my d_tmax;
+					if (my tmin < thy tmin)
+						theGroup [i] -> tmin = my tmin;
+					if (my tmax > thy tmax)
+						theGroup [i] -> tmax = my tmax;
 					FunctionEditor_updateText (theGroup [i]);
 					updateScrollBar (theGroup [i]);
 					Graphics_updateWs (theGroup [i] -> d_graphics);
@@ -1074,11 +1097,7 @@ static void gui_drawingarea_cb_click (I, GuiDrawingAreaClickEvent event) {
 		if (needsUpdate) my v_updateText ();
 		Graphics_setViewport (my d_graphics, my functionViewerLeft, my functionViewerRight, 0, my height);
 		if (needsUpdate) {
-			#if cocoa
-				Graphics_updateWs (my d_graphics);
-			#else
-				drawNow (me);
-			#endif
+			drawNow (me);
 		}
 		if (needsUpdate) updateGroup (me);
 	}
@@ -1089,10 +1108,10 @@ static void gui_drawingarea_cb_click (I, GuiDrawingAreaClickEvent event) {
 				if (xWC > my rect [i]. left && xWC < my rect [i]. right &&
 					 yWC > my rect [i]. bottom && yWC < my rect [i]. top)
 					switch (i) {
-						case 0: my v_play (my d_tmin, my d_tmax); break;
+						case 0: my v_play (my tmin, my tmax); break;
 						case 1: my v_play (my d_startWindow, my d_endWindow); break;
-						case 2: my v_play (my d_tmin, my d_startWindow); break;
-						case 3: my v_play (my d_endWindow, my d_tmax); break;
+						case 2: my v_play (my tmin, my d_startWindow); break;
+						case 3: my v_play (my d_endWindow, my tmax); break;
 						case 4: my v_play (my d_startWindow, my marker [1]); break;
 						case 5: my v_play (my marker [1], my marker [2]); break;
 						case 6: my v_play (my marker [2], my marker [3]); break;
@@ -1110,70 +1129,75 @@ void structFunctionEditor :: v_createChildren () {
 
 	/***** Create zoom buttons. *****/
 
-	GuiButton_createShown (d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
+	GuiButton_createShown (our d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
 		L"all", gui_button_cb_showAll, this, 0);
 	x += BUTTON_WIDTH + BUTTON_SPACING;
-	GuiButton_createShown (d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
+	GuiButton_createShown (our d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
 		L"in", gui_button_cb_zoomIn, this, 0);
 	x += BUTTON_WIDTH + BUTTON_SPACING;
-	GuiButton_createShown (d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
+	GuiButton_createShown (our d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
 		L"out", gui_button_cb_zoomOut, this, 0);
 	x += BUTTON_WIDTH + BUTTON_SPACING;
-	GuiButton_createShown (d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
+	GuiButton_createShown (our d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
 		L"sel", gui_button_cb_zoomToSelection, this, 0);
 	x += BUTTON_WIDTH + BUTTON_SPACING;
-	GuiButton_createShown (d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
+	GuiButton_createShown (our d_windowForm, x, x + BUTTON_WIDTH, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
 		L"bak", gui_button_cb_zoomBack, this, 0);
 
 	/***** Create scroll bar. *****/
 
-	scrollBar = GuiScrollBar_createShown (d_windowForm,
+	our scrollBar = GuiScrollBar_createShown (our d_windowForm,
 		x += BUTTON_WIDTH + BUTTON_SPACING, -80 - BUTTON_SPACING, -4 - Gui_PUSHBUTTON_HEIGHT, 0,
 		1, maximumScrollBarValue, 1, maximumScrollBarValue - 1, 1, 1,
 		gui_cb_scroll, this, GuiScrollBar_HORIZONTAL);
 
 	/***** Create Group button. *****/
 
-	groupButton = GuiCheckButton_createShown (d_windowForm, -80, 0, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
-		L"Group", gui_checkbutton_cb_group, this, group_equalDomain (d_tmin, d_tmax) ? GuiCheckButton_SET : 0);
+	our groupButton = GuiCheckButton_createShown (d_windowForm, -80, 0, -4 - Gui_PUSHBUTTON_HEIGHT, -4,
+		L"Group", gui_checkbutton_cb_group, this, group_equalDomain (our tmin, our tmax) ? GuiCheckButton_SET : 0);
 
 	/***** Create optional text field. *****/
 
-	if (v_hasText ()) {
-		text = GuiText_createShown (d_windowForm, 0, 0,
+	if (our v_hasText ()) {
+		our text = GuiText_createShown (our d_windowForm, 0, 0,
 			Machine_getMenuBarHeight (),
 			Machine_getMenuBarHeight () + TEXT_HEIGHT, GuiText_WORDWRAP | GuiText_MULTILINE);
 		#if gtk
-			Melder_assert (text -> d_widget);
-			gtk_widget_grab_focus (GTK_WIDGET (text -> d_widget));   // BUG: can hardly be correct (the text should grab the focus of the window, not the global focus)
+			Melder_assert (our text -> d_widget);
+			gtk_widget_grab_focus (GTK_WIDGET (our text -> d_widget));   // BUG: can hardly be correct (the text should grab the focus of the window, not the global focus)
 		#elif cocoa
-			Melder_assert ([(NSView *) text -> d_widget window]);
-			//[[(NSView *) text -> d_widget window] setInitialFirstResponder: (NSView *) text -> d_widget];
-			[[(NSView *) text -> d_widget window] makeFirstResponder: (NSView *) text -> d_widget];
+			Melder_assert ([(NSView *) our text -> d_widget window]);
+			//[[(NSView *) our text -> d_widget window] setInitialFirstResponder: (NSView *) our text -> d_widget];
+			[[(NSView *) our text -> d_widget window] makeFirstResponder: (NSView *) our text -> d_widget];
 		#endif
 	}
 
 	/***** Create drawing area. *****/
 
-	drawingArea = GuiDrawingArea_createShown (d_windowForm,
+	#if cocoa
+		int marginBetweenTextAndDrawingAreaToEnsureCorrectUnhighlighting = 3;
+	#else
+		int marginBetweenTextAndDrawingAreaToEnsureCorrectUnhighlighting = 0;
+	#endif
+	our drawingArea = GuiDrawingArea_createShown (our d_windowForm,
 		0, 0,
-		Machine_getMenuBarHeight () + ( v_hasText () ? TEXT_HEIGHT : 0), -8 - Gui_PUSHBUTTON_HEIGHT,
+		Machine_getMenuBarHeight () + ( our v_hasText () ? TEXT_HEIGHT + marginBetweenTextAndDrawingAreaToEnsureCorrectUnhighlighting : 0), -8 - Gui_PUSHBUTTON_HEIGHT,
 		gui_drawingarea_cb_expose, gui_drawingarea_cb_click, NULL, gui_drawingarea_cb_resize, this, 0);
-	drawingArea -> f_setSwipable (scrollBar, NULL);
+	our drawingArea -> f_setSwipable (our scrollBar, NULL);
 }
 
 void structFunctionEditor :: v_dataChanged () {
-	Function function = (Function) data;
+	Function function = (Function) our data;
 	Melder_assert (Thing_member (function, classFunction));
-	d_tmin = function -> xmin;
- 	d_tmax = function -> xmax;
- 	if (d_startWindow < d_tmin || d_startWindow > d_tmax) d_startWindow = d_tmin;
- 	if (d_endWindow < d_tmin || d_endWindow > d_tmax) d_endWindow = d_tmax;
- 	if (d_startWindow >= d_endWindow) { d_startWindow = d_tmin; d_endWindow = d_tmax; }
- 	if (d_startSelection < d_tmin) d_startSelection = d_tmin;
- 	if (d_startSelection > d_tmax) d_startSelection = d_tmax;
- 	if (d_endSelection < d_tmin) d_endSelection = d_tmin;
- 	if (d_endSelection > d_tmax) d_endSelection = d_tmax;
+	our tmin = function -> xmin;
+ 	our tmax = function -> xmax;
+ 	if (our d_startWindow < our tmin || our d_startWindow > our tmax) our d_startWindow = our tmin;
+ 	if (our d_endWindow < our tmin || our d_endWindow > our tmax) our d_endWindow = our tmax;
+ 	if (our d_startWindow >= our d_endWindow) { our d_startWindow = our tmin; our d_endWindow = our tmax; }
+ 	if (our d_startSelection < our tmin) our d_startSelection = our tmin;
+ 	if (our d_startSelection > our tmax) our d_startSelection = our tmax;
+ 	if (our d_endSelection < our tmin) our d_endSelection = our tmin;
+ 	if (our d_endSelection > our tmax) our d_endSelection = our tmax;
 	FunctionEditor_marksChanged (this, false);
 }
 
@@ -1194,14 +1218,17 @@ static void drawWhileDragging (FunctionEditor me, double x1, double x2) {
 	Graphics_text1 (my d_graphics, xleft, 0.0, Melder_fixed (xleft, 6));
 	Graphics_setTextAlignment (my d_graphics, Graphics_LEFT, Graphics_BOTTOM);
 	Graphics_text1 (my d_graphics, xright, 0.0, Melder_fixed (xright, 6));
+	Graphics_setLineType (my d_graphics, Graphics_DOTTED);
+	Graphics_line (my d_graphics, xleft, 0.0, xleft, 1.0);
+	Graphics_line (my d_graphics, xright, 0.0, xright, 1.0);
+	Graphics_setLineType (my d_graphics, Graphics_DRAWN);
 	Graphics_xorOff (my d_graphics);
 }
 
-int structFunctionEditor :: v_click (double xbegin, double ybegin, bool shiftKeyPressed) {
+int structFunctionEditor :: v_click (double xbegin, double ybegin, bool a_shiftKeyPressed) {
 	bool drag = false;
 	double x = xbegin, y = ybegin;
-    
-    
+
 	/*
 	 * The 'anchor' is the point that will stay fixed during dragging.
 	 * For instance, if she clicks and drags to the right,
@@ -1213,7 +1240,7 @@ int structFunctionEditor :: v_click (double xbegin, double ybegin, bool shiftKey
 	Graphics_setWindow (d_graphics, d_startWindow, d_endWindow, 0, 1);
 
 	double anchorForDragging = xbegin;   // the default (for if the shift key isn't pressed)
-	if (shiftKeyPressed) {
+	if (a_shiftKeyPressed) {
 		/*
 		 * Extend the selection.
 		 * We should always end up with a real selection (B < E),
@@ -1234,8 +1261,10 @@ int structFunctionEditor :: v_click (double xbegin, double ybegin, bool shiftKey
 			/*
 			 * Undraw the visible part of the old selection.
 			 */
-			if (endVisible > startVisible)
+			if (endVisible > startVisible) {
 				v_unhighlightSelection (startVisible, endVisible, 0, 1);
+				//Graphics_flushWs (d_graphics);
+			}
 		}
 		if (xbegin >= secondMark) {
 		 	/*
@@ -1352,30 +1381,30 @@ int structFunctionEditor :: v_click (double xbegin, double ybegin, bool shiftKey
 				continue;
             
 			/*
-			 * Undraw the text.
-			 */
-			drawWhileDragging (this, anchorForDragging, xold);
-			/*
 			 * Undraw previous dragged selection.
 			 */
 			if (xold > anchorForDragging) x1 = anchorForDragging, x2 = xold; else x1 = xold, x2 = anchorForDragging;
 			if (x1 != x2) v_unhighlightSelection (x1, x2, 0, 1);
 			/*
-			 * Draw new dragged selection.
+			 * Undraw the text.
 			 */
-			if (x > anchorForDragging) x1 = anchorForDragging, x2 = x; else x1 = x, x2 = anchorForDragging;
-			if (x1 != x2) v_highlightSelection (x1, x2, 0, 1);
+			drawWhileDragging (this, anchorForDragging, xold);
 			/*
 			 * Redraw the text at the new location.
 			 */
             drawWhileDragging (this, anchorForDragging, x);
+			/*
+			 * Draw new dragged selection.
+			 */
+			if (x > anchorForDragging) x1 = anchorForDragging, x2 = x; else x1 = x, x2 = anchorForDragging;
+			if (x1 != x2) v_highlightSelection (x1, x2, 0, 1);
         } ;
 		/*
 		 * Set the new selection.
 		 */
 		if (x > anchorForDragging) d_startSelection = anchorForDragging, d_endSelection = x;
 		else d_startSelection = x, d_endSelection = anchorForDragging;
-	} else if (! shiftKeyPressed) {
+	} else if (! a_shiftKeyPressed) {
 		/*
 		 * Move the cursor to the clicked position.
 		 */
@@ -1483,13 +1512,13 @@ void structFunctionEditor :: v_unhighlightSelection (double left, double right,
 }
 
 void FunctionEditor_init (FunctionEditor me, const wchar_t *title, Function data) {
-	my d_tmin = data -> xmin;   // set before adding children (see group button)
-	my d_tmax = data -> xmax;
+	my tmin = data -> xmin;   // set before adding children (see group button)
+	my tmax = data -> xmax;
 	Editor_init (me, 0, 0, my pref_shellWidth (), my pref_shellHeight (), title, data);
 
-	my d_startWindow = my d_tmin;
-	my d_endWindow = my d_tmax;
-	my d_startSelection = my d_endSelection = 0.5 * (my d_tmin + my d_tmax);
+	my d_startWindow = my tmin;
+	my d_endWindow = my tmax;
+	my d_startSelection = my d_endSelection = 0.5 * (my tmin + my tmax);
 	#if motif
 		Melder_assert (XtWindow (my drawingArea -> d_widget));
 	#endif
@@ -1503,7 +1532,7 @@ event. height = my drawingArea -> f_getHeight ();
 gui_drawingarea_cb_resize (me, & event);
 
 	my v_updateText ();
-	if (group_equalDomain (my d_tmin, my d_tmax))
+	if (group_equalDomain (my tmin, my tmax))
 		gui_checkbutton_cb_group (me, NULL);   // BUG: NULL
 	my enableUpdates = true;
 }
diff --git a/fon/FunctionEditor.h b/fon/FunctionEditor.h
index 8d19aaf..8849263 100644
--- a/fon/FunctionEditor.h
+++ b/fon/FunctionEditor.h
@@ -2,7 +2,7 @@
 #define _FunctionEditor_h_
 /* FunctionEditor.h
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -35,7 +35,7 @@ Thing_define (FunctionEditor, Editor) {
 		/* but has to respect the invariants, */
 		/* and has to call FunctionEditor_marksChanged () */
 		/* immediately after making the changes. */
-		double d_tmin, d_tmax, d_startWindow, d_endWindow;
+		double tmin, tmax, d_startWindow, d_endWindow;
 		double d_startSelection, d_endSelection;   // markers
 			/* These attributes are all expressed in seconds. Invariants: */
 			/*    tmin <= startWindow < endWindow <= tmax; */
diff --git a/fon/LongSound.cpp b/fon/LongSound.cpp
index fb67c8c..8d82dc6 100644
--- a/fon/LongSound.cpp
+++ b/fon/LongSound.cpp
@@ -1,6 +1,6 @@
 /* LongSound.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma, 2007 Erez Volk (for FLAC and MP3)
+ * Copyright (C) 1992-2012,2014 Paul Boersma, 2007 Erez Volk (for FLAC and MP3)
  *
  * 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
@@ -39,6 +39,7 @@
  * pb 2010/12/20 support for more than 2 channels
  * pb 2011/06/02 C++
  * pb 2011/07/05 C++
+ * pb 2014/06/16 more support for more than 2 channels
  */
 
 #include "LongSound.h"
@@ -535,8 +536,8 @@ void LongSound_playPart (LongSound me, double tmin, double tmax,
 	int (*callback) (void *closure, int phase, double tmin, double tmax, double t), void *closure)
 {
 	struct LongSoundPlay *thee = (struct LongSoundPlay *) & thePlayingLongSound;
-	Melder_free (thy resampledBuffer);   // just in case
 	MelderAudio_stopPlaying (MelderAudio_IMPLICIT);
+	Melder_free (thy resampledBuffer);   // just in case, and after playing has stopped
 	try {
 		int fits = LongSound_haveWindow (me, tmin, tmax);
 		long bestSampleRate = MelderAudio_getOutputBestSampleRate (my sampleRate), n, i1, i2;
@@ -559,7 +560,7 @@ void LongSound_playPart (LongSound me, double tmin, double tmax,
 			thy silenceBefore = (long) (my sampleRate * MelderAudio_getOutputSilenceBefore ());
 			thy silenceAfter = (long) (my sampleRate * MelderAudio_getOutputSilenceAfter ());
 			if (thy callback) thy callback (thy closure, 1, tmin, tmax, tmin);
-			if (thy silenceBefore > 0 || thy silenceAfter > 0) {
+			if (thy silenceBefore > 0 || thy silenceAfter > 0 || 1) {
 				thy resampledBuffer = Melder_calloc (short, (thy silenceBefore + thy numberOfSamples + thy silenceAfter) * my numberOfChannels);
 				memcpy (& thy resampledBuffer [thy silenceBefore * my numberOfChannels], & my buffer [(i1 - my imin) * my numberOfChannels],
 					thy numberOfSamples * sizeof (short) * my numberOfChannels);
@@ -593,15 +594,27 @@ void LongSound_playPart (LongSound me, double tmin, double tmax,
 					double fraction = index - flore;
 					resampledBuffer [i + silenceBefore] = (1 - fraction) * from [flore] + fraction * from [flore + 1];
 				}
-			} else {
+			} else if (my numberOfChannels == 2) {
 				for (i = 0; i < newN; i ++) {
 					double t = t1 + i * dt;
 					double index = (t - t1) * newSampleRate;
-					long flore = index, ii = i + silenceBefore;
+					long flore = index;
 					double fraction = index - flore;
+					long ii = i + silenceBefore;
 					resampledBuffer [ii + ii] = (1 - fraction) * from [flore + flore] + fraction * from [flore + flore + 2];
 					resampledBuffer [ii + ii + 1] = (1 - fraction) * from [flore + flore + 1] + fraction * from [flore + flore + 3];
 				}
+			} else {
+				for (i = 0; i < newN; i ++) {
+					double t = t1 + i * dt;
+					double index = (t - t1) * newSampleRate;
+					long flore = index;
+					double fraction = index - flore;
+					long ii = (i + silenceBefore) * my numberOfChannels;
+					for (long chan = 0; chan < my numberOfChannels; chan ++) {
+						resampledBuffer [ii + chan] = (1 - fraction) * from [flore + flore + chan] + fraction * from [flore + flore + chan + my numberOfChannels];
+					}
+				}
 			}
 			if (thy callback) thy callback (thy closure, 1, tmin, tmax, tmin);
 			MelderAudio_play16 (resampledBuffer, newSampleRate, silenceBefore + newN + silenceAfter, my numberOfChannels, melderPlayCallback, thee);
@@ -641,7 +654,7 @@ void LongSound_concatenate (Collection me, MelderFile file, int audioFileType, i
 			data = (Sampled) my item [i];
 			if (data -> classInfo == classSound) {
 				Sound sound = (Sound) data;
-				sampleRatesMatch = floor (1.0 / sound -> dx + 0.5) == sampleRate;
+				sampleRatesMatch = round (1.0 / sound -> dx) == sampleRate;
 				numbersOfChannelsMatch = sound -> ny == numberOfChannels;
 				n += sound -> nx;
 			} else {
diff --git a/fon/ManipulationEditor.cpp b/fon/ManipulationEditor.cpp
index cf5da73..6dc4006 100644
--- a/fon/ManipulationEditor.cpp
+++ b/fon/ManipulationEditor.cpp
@@ -1,6 +1,6 @@
 /* ManipulationEditor.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -700,7 +700,7 @@ static void drawSoundArea (ManipulationEditor me, double ymin, double ymax) {
 		 */    
 		Graphics_setColour (my d_graphics, Graphics_BLACK);
 		Graphics_function (my d_graphics, sound -> z [1], first, last,
-			Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
+			sound -> f_indexToX (first), sound -> f_indexToX (last));
 	}
 
 	Graphics_resetViewport (my d_graphics, viewport);
@@ -932,14 +932,13 @@ void structManipulationEditor :: v_draw () {
 static void drawWhileDragging (ManipulationEditor me, double xWC, double yWC, long first, long last, double dt, double df) {
 	Manipulation ana = (Manipulation) my data;
 	PitchTier pitch = ana -> pitch;
-	long i;
 	(void) xWC;
 	(void) yWC;
 
 	/*
 	 * Draw all selected pitch points as magenta empty circles, if inside the window.
 	 */
-	for (i = first; i <= last; i ++) {
+	for (long i = first; i <= last; i ++) {
 		RealPoint point = (RealPoint) pitch -> points -> item [i];
 		double t = point -> number + dt, f = YLIN (point -> value) + df;
 		if (t >= my d_startWindow && t <= my d_endWindow)
@@ -1043,11 +1042,11 @@ static int clickPitch (ManipulationEditor me, double xWC, double yWC, bool shift
 	{
 		RealPoint *points = (RealPoint *) pitch -> points -> item;
 		double newTime = points [ifirstSelected] -> number + dt;
-		if (newTime < my d_tmin) return 1;   // outside domain
+		if (newTime < my tmin) return 1;   // outside domain
 		if (ifirstSelected > 1 && newTime <= points [ifirstSelected - 1] -> number)
 			return 1;   /* Past left neighbour. */
 		newTime = points [ilastSelected] -> number + dt;
-		if (newTime > my d_tmax) return 1;   // outside domain
+		if (newTime > my tmax) return 1;   // outside domain
 		if (ilastSelected < pitch -> points -> size && newTime >= points [ilastSelected + 1] -> number)
 			return 1;   // past right neighbour
 	}
@@ -1191,11 +1190,11 @@ static int clickDuration (ManipulationEditor me, double xWC, double yWC, int shi
 	{
 		RealPoint *points = (RealPoint *) duration -> points -> item;
 		double newTime = points [ifirstSelected] -> number + dt;
-		if (newTime < my d_tmin) return 1;   // outside domain
+		if (newTime < my tmin) return 1;   // outside domain
 		if (ifirstSelected > 1 && newTime <= points [ifirstSelected - 1] -> number)
 			return 1;   /* Past left neighbour. */
 		newTime = points [ilastSelected] -> number + dt;
-		if (newTime > my d_tmax) return 1;   // outside domain
+		if (newTime > my tmax) return 1;   // outside domain
 		if (ilastSelected < duration -> points -> size && newTime >= points [ilastSelected + 1] -> number)
 			return 1;   // past right neighbour
 	}
diff --git a/fon/Matrix.cpp b/fon/Matrix.cpp
index 5d51cec..0b9ada5 100644
--- a/fon/Matrix.cpp
+++ b/fon/Matrix.cpp
@@ -1,6 +1,6 @@
 /* Matrix.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -73,11 +73,6 @@ void structMatrix :: v_readText (MelderReadText text) {
 		y1 = texgetr8 (text);
 	} else {
 		Matrix_Parent :: v_readText (text);
-		ymin = texgetr8 (text);
-		ymax = texgetr8 (text);
-		ny = texgeti4 (text);
-		dy = texgetr8 (text);
-		y1 = texgetr8 (text);
 	}
 	if (xmin > xmax)
 		Melder_throw ("xmin should be less than or equal to xmax.");
@@ -154,28 +149,27 @@ Matrix Matrix_createSimple (long numberOfRows, long numberOfColumns) {
 	}
 }
 
-double Matrix_columnToX (I, double column) { iam (Matrix); return my x1 + (column - 1) * my dx; }
+double Matrix_columnToX (Matrix me, double column) { return my x1 + (column - 1) * my dx; }
 
-double Matrix_rowToY (I, double row) { iam (Matrix); return my y1 + (row - 1) * my dy; }
+double Matrix_rowToY (Matrix me, double row) { return my y1 + (row - 1) * my dy; }
 
-double Matrix_xToColumn (I, double x) { iam (Matrix); return (x - my x1) / my dx + 1; }
+double Matrix_xToColumn (Matrix me, double x) { return (x - my x1) / my dx + 1; }
 
-long Matrix_xToLowColumn (I, double x) { iam (Matrix); return (long) floor (Matrix_xToColumn (me, x)); }
+long Matrix_xToLowColumn (Matrix me, double x) { return (long) floor (Matrix_xToColumn (me, x)); }
 
-long Matrix_xToHighColumn (I, double x) { iam (Matrix); return (long) ceil (Matrix_xToColumn (me, x)); }
+long Matrix_xToHighColumn (Matrix me, double x) { return (long) ceil (Matrix_xToColumn (me, x)); }
 
-long Matrix_xToNearestColumn (I, double x) { iam (Matrix); return (long) floor (Matrix_xToColumn (me, x) + 0.5); }
+long Matrix_xToNearestColumn (Matrix me, double x) { return (long) floor (Matrix_xToColumn (me, x) + 0.5); }
 
-double Matrix_yToRow (I, double y) { iam (Matrix); return (y - my y1) / my dy + 1; }
+double Matrix_yToRow (Matrix me, double y) { return (y - my y1) / my dy + 1; }
 
-long Matrix_yToLowRow (I, double y) { iam (Matrix); return (long) floor (Matrix_yToRow (me, y)); }
+long Matrix_yToLowRow (Matrix me, double y) { return (long) floor (Matrix_yToRow (me, y)); }
 
-long Matrix_yToHighRow (I, double y) { iam (Matrix); return (long) ceil (Matrix_yToRow (me, y)); }
+long Matrix_yToHighRow (Matrix me, double y) { return (long) ceil (Matrix_yToRow (me, y)); }
 
-long Matrix_yToNearestRow (I, double y) { iam (Matrix); return (long) floor (Matrix_yToRow (me, y) + 0.5); }
+long Matrix_yToNearestRow (Matrix me, double y) { return (long) floor (Matrix_yToRow (me, y) + 0.5); }
 
-long Matrix_getWindowSamplesX (I, double xmin, double xmax, long *ixmin, long *ixmax) {
-	iam (Matrix);
+long Matrix_getWindowSamplesX (Matrix me, double xmin, double xmax, long *ixmin, long *ixmax) {
 	*ixmin = 1 + (long) ceil  ((xmin - my x1) / my dx);
 	*ixmax = 1 + (long) floor ((xmax - my x1) / my dx);
 	if (*ixmin < 1) *ixmin = 1;
@@ -184,8 +178,7 @@ long Matrix_getWindowSamplesX (I, double xmin, double xmax, long *ixmin, long *i
 	return *ixmax - *ixmin + 1;
 }
 
-long Matrix_getWindowSamplesY (I, double ymin, double ymax, long *iymin, long *iymax) {
-	iam (Matrix);
+long Matrix_getWindowSamplesY (Matrix me, double ymin, double ymax, long *iymin, long *iymax) {
 	*iymin = 1 + (long) ceil  ((ymin - my y1) / my dy);
 	*iymax = 1 + (long) floor ((ymax - my y1) / my dy);
 	if (*iymin < 1) *iymin = 1;
@@ -194,10 +187,9 @@ long Matrix_getWindowSamplesY (I, double ymin, double ymax, long *iymin, long *i
 	return *iymax - *iymin + 1;
 }
 
-long Matrix_getWindowExtrema (I, long ixmin, long ixmax, long iymin, long iymax,
+long Matrix_getWindowExtrema (Matrix me, long ixmin, long ixmax, long iymin, long iymax,
 	double *minimum, double *maximum)
 {
-	iam (Matrix);
 	if (ixmin == 0) ixmin = 1;
 	if (ixmax == 0) ixmax = my nx;
 	if (iymin == 0) iymin = 1;
@@ -213,8 +205,7 @@ long Matrix_getWindowExtrema (I, long ixmin, long ixmax, long iymin, long iymax,
 	return (ixmax - ixmin + 1) * (iymax - iymin + 1);
 }
 
-double Matrix_getValueAtXY (I, double x, double y) {
-	iam (Matrix);
+double Matrix_getValueAtXY (Matrix me, double x, double y) {
 	long bottomRow, leftCol, topRow, rightCol;
 	double drow, dcol;
 	double row_real = (y - my y1) / my dy + 1.0;
@@ -252,8 +243,7 @@ double Matrix_getValueAtXY (I, double x, double y) {
 		drow * dcol * my z [topRow] [rightCol];
 }
 
-double Matrix_getSum (I) {
-	iam (Matrix);
+double Matrix_getSum (Matrix me) {
 	double sum = 0.0;
 	for (long row = 1; row <= my ny; row ++)
 		for (long col = 1; col <= my nx; col ++)
@@ -261,8 +251,7 @@ double Matrix_getSum (I) {
 	return sum;
 }
 
-double Matrix_getNorm (I) {
-	iam (Matrix);
+double Matrix_getNorm (Matrix me) {
 	double sum = 0.0;
 	for (long row = 1; row <= my ny; row ++)
 		for (long col = 1; col <= my nx; col ++)
@@ -270,10 +259,9 @@ double Matrix_getNorm (I) {
 	return sqrt (sum);
 }
 
-void Matrix_drawRows (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_drawRows (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum)
 {
-	iam (Matrix);
 	if (xmax <= xmin) { xmin = my xmin; xmax = my xmax; }
 	if (ymax <= ymin) { ymin = my ymin; ymax = my ymax; }
 	long ixmin, ixmax, iymin, iymax;
@@ -296,10 +284,9 @@ void Matrix_drawRows (I, Graphics g, double xmin, double xmax, double ymin, doub
 		Graphics_setWindow (g, xmin, xmax, my y1 + (iymin - 1.5) * my dy, my y1 + (iymax - 0.5) * my dy);
 }
 
-void Matrix_drawOneContour (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_drawOneContour (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double height)
 {
-	iam (Matrix);
 	bool xreversed = xmin > xmax, yreversed = ymin > ymax;
 	if (xmax == xmin) { xmin = my xmin; xmax = my xmax; }
 	if (ymax == ymin) { ymin = my ymin; ymax = my ymax; }
@@ -319,10 +306,9 @@ void Matrix_drawOneContour (I, Graphics g, double xmin, double xmax, double ymin
 	Graphics_unsetInner (g);
 }
 
-void Matrix_drawContours (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_drawContours (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum)
 {
-	iam (Matrix);
 	double border [1 + 8];
 	if (xmax == xmin) { xmin = my xmin; xmax = my xmax; }
 	if (ymax == ymin) { ymin = my ymin; ymax = my ymax; }
@@ -345,10 +331,9 @@ void Matrix_drawContours (I, Graphics g, double xmin, double xmax, double ymin,
 	Graphics_unsetInner (g);
 }
 
-void Matrix_paintContours (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintContours (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum)
 {
-	iam (Matrix);
 	double border [1 + 30];
 	if (xmax <= xmin) { xmin = my xmin; xmax = my xmax; }
 	if (ymax <= ymin) { ymin = my ymin; ymax = my ymax; }
@@ -371,10 +356,9 @@ void Matrix_paintContours (I, Graphics g, double xmin, double xmax, double ymin,
 	Graphics_unsetInner (g);
 }
 
-static void cellArrayOrImage (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+static void cellArrayOrImage (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum, int interpolate)
 {
-	iam (Matrix);
 	if (xmax <= xmin) { xmin = my xmin; xmax = my xmax; }
 	if (ymax <= ymin) { ymin = my ymin; ymax = my ymax; }
 	long ixmin, ixmax, iymin, iymax;
@@ -390,34 +374,33 @@ static void cellArrayOrImage (I, Graphics g, double xmin, double xmax, double ym
 	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
 	if (interpolate)
 		Graphics_image (g, my z,
-			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),
+			ixmin, ixmax, my f_indexToX (ixmin - 0.5), my f_indexToX (ixmax + 0.5),
+			iymin, iymax, my f_indexToY (iymin - 0.5), my f_indexToY (iymax + 0.5),
 			minimum, maximum);
 	else
 		Graphics_cellArray (g, my z,
-			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),
+			ixmin, ixmax, my f_indexToX (ixmin - 0.5), my f_indexToX (ixmax + 0.5),
+			iymin, iymax, my f_indexToY (iymin - 0.5), my f_indexToY (iymax + 0.5),
 			minimum, maximum);
 	Graphics_rectangle (g, xmin, xmax, ymin, ymax);
 	Graphics_unsetInner (g);
 }
 
-void Matrix_paintImage (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintImage (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum)
 {
-	cellArrayOrImage (void_me, g, xmin, xmax, ymin, ymax, minimum, maximum, TRUE);
+	cellArrayOrImage (me, g, xmin, xmax, ymin, ymax, minimum, maximum, TRUE);
 }
 
-void Matrix_paintCells (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintCells (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum)
 {
-	cellArrayOrImage (void_me, g, xmin, xmax, ymin, ymax, minimum, maximum, FALSE);
+	cellArrayOrImage (me, g, xmin, xmax, ymin, ymax, minimum, maximum, FALSE);
 }
 
-void Matrix_paintSurface (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintSurface (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum, double elevation, double azimuth)
 {
-	iam (Matrix);
 	if (xmax <= xmin) { xmin = my xmin; xmax = my xmax; }
 	if (ymax <= ymin) { ymin = my ymin; ymax = my ymax; }
 	long ixmin, ixmax, iymin, iymax;
@@ -435,8 +418,7 @@ void Matrix_paintSurface (I, Graphics g, double xmin, double xmax, double ymin,
 	Graphics_unsetInner (g);
 }
 
-void Matrix_movie (I, Graphics g) {
-	iam (Matrix);
+void Matrix_movie (Matrix me, Graphics g) {
 	autoNUMvector <double> column (1, my ny);
 	double minimum = 0.0, maximum = 1.0;
 	Matrix_getWindowExtrema (me, 1, my nx, 1, my ny, & minimum, & maximum);
@@ -563,8 +545,7 @@ Matrix Matrix_readFromRawTextFile (MelderFile file) {   // BUG: not Unicode-comp
 	}
 }
 
-void Matrix_eigen (I, Matrix *out_eigenvectors, Matrix *out_eigenvalues) {
-	iam (Matrix);
+void Matrix_eigen (Matrix me, Matrix *out_eigenvectors, Matrix *out_eigenvalues) {
 	*out_eigenvectors = NULL;
 	*out_eigenvalues = NULL;
 	try {
@@ -587,8 +568,7 @@ void Matrix_eigen (I, Matrix *out_eigenvectors, Matrix *out_eigenvalues) {
 	}
 }
 
-Matrix Matrix_power (I, long power) {
-	iam (Matrix);
+Matrix Matrix_power (Matrix me, long power) {
 	try {
 		if (my nx != my ny)
 			Melder_throw ("Matrix not square.");
@@ -684,8 +664,7 @@ void Matrix_formula_part (Matrix me, double xmin, double xmax, double ymin, doub
 	}
 }
 
-void Matrix_scaleAbsoluteExtremum (I, double scale) {
-	iam (Matrix);
+void Matrix_scaleAbsoluteExtremum (Matrix me, double scale) {
 	double extremum = 0.0;
 	for (long i = 1; i <= my ny; i ++) {
 		for (long j = 1; j <= my nx; j ++) {
@@ -704,8 +683,7 @@ void Matrix_scaleAbsoluteExtremum (I, double scale) {
 	}
 }
 
-Matrix TableOfReal_to_Matrix (I) {
-	iam (TableOfReal);
+Matrix TableOfReal_to_Matrix (TableOfReal me) {
 	try {
 		autoMatrix thee = Matrix_createSimple (my numberOfRows, my numberOfColumns);
 		for (long i = 1; i <= my numberOfRows; i ++)
@@ -717,8 +695,7 @@ Matrix TableOfReal_to_Matrix (I) {
 	}
 }
 
-TableOfReal Matrix_to_TableOfReal (I) {
-	iam (Matrix);
+TableOfReal Matrix_to_TableOfReal (Matrix me) {
 	try {
 		autoTableOfReal thee = TableOfReal_create (my ny, my nx);
 		for (long i = 1; i <= my ny; i ++)
diff --git a/fon/Matrix.h b/fon/Matrix.h
index 6907596..85b4475 100644
--- a/fon/Matrix.h
+++ b/fon/Matrix.h
@@ -2,7 +2,7 @@
 #define _Matrix_h_
 /* Matrix.h
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -21,8 +21,8 @@
 
 #include "SampledXY.h"
 #include "Graphics.h"
-#include "Table.h"
-#include "TableOfReal.h"
+#include "../stat/Table.h"
+#include "../stat/TableOfReal.h"
 #include "Interpreter_decl.h"
 
 #include "Matrix_def.h"
@@ -118,7 +118,7 @@ Matrix Matrix_createSimple (long numberOfRows, long numberOfColumns);
 			Out of input.
 */
 
-long Matrix_getWindowSamplesX (I, double xmin, double xmax, long *ixmin, long *ixmax);
+long Matrix_getWindowSamplesX (Matrix me, double xmin, double xmax, long *ixmin, long *ixmax);
 /*
 	Function:
 		return the number of samples with x values in [xmin, xmax].
@@ -131,39 +131,39 @@ long Matrix_getWindowSamplesX (I, double xmin, double xmax, long *ixmin, long *i
 		if (result != 0) result == *ixmax - *ixmin + 1;
 */
 
-double Matrix_getValueAtXY (I, double x, double y);
+double Matrix_getValueAtXY (Matrix me, 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.
 */
 
-double Matrix_getSum (I);
-double Matrix_getNorm (I);
+double Matrix_getSum (Matrix me);
+double Matrix_getNorm (Matrix me);
 
-double Matrix_columnToX (I, double column);   /* Return my x1 + (column - 1) * my dx.	 */
+double Matrix_columnToX (Matrix me, double column);   /* Return my x1 + (column - 1) * my dx.	 */
 
-double Matrix_rowToY (I, double row);   /* Return my y1 + (row - 1) * my dy. */
+double Matrix_rowToY (Matrix me, double row);   /* Return my y1 + (row - 1) * my dy. */
 
-double Matrix_xToColumn (I, double x);   /* Return (x - xmin) / my dx + 1. */
+double Matrix_xToColumn (Matrix me, double x);   /* Return (x - xmin) / my dx + 1. */
 
-long Matrix_xToLowColumn (I, double x);   /* Return floor (Matrix_xToColumn (me, x)). */
+long Matrix_xToLowColumn (Matrix me, double x);   /* Return floor (Matrix_xToColumn (me, x)). */
 
-long Matrix_xToHighColumn (I, double x);   /* Return ceil (Matrix_xToColumn (me, x)). */
+long Matrix_xToHighColumn (Matrix me, double x);   /* Return ceil (Matrix_xToColumn (me, x)). */
 
-long Matrix_xToNearestColumn (I, double x);   /* Return floor (Matrix_xToColumn (me, x) + 0.5). */
+long Matrix_xToNearestColumn (Matrix me, double x);   /* Return floor (Matrix_xToColumn (me, x) + 0.5). */
 
-double Matrix_yToRow (I, double y);   /* Return (y - ymin) / my dy + 1. */
+double Matrix_yToRow (Matrix me, double y);   /* Return (y - ymin) / my dy + 1. */
 
-long Matrix_yToLowRow (I, double y);   /* Return floor (Matrix_yToRow (me, y)). */
+long Matrix_yToLowRow (Matrix me, double y);   /* Return floor (Matrix_yToRow (me, y)). */
 
-long Matrix_yToHighRow (I, double x);   /* Return ceil (Matrix_yToRow (me, y)). */
+long Matrix_yToHighRow (Matrix me, double x);   /* Return ceil (Matrix_yToRow (me, y)). */
 
-long Matrix_yToNearestRow (I, double y);   /* Return floor (Matrix_yToRow (me, y) + 0.5). */
+long Matrix_yToNearestRow (Matrix me, double y);   /* Return floor (Matrix_yToRow (me, y) + 0.5). */
 
-long Matrix_getWindowSamplesY (I, double ymin, double ymax, long *iymin, long *iymax);
+long Matrix_getWindowSamplesY (Matrix me, double ymin, double ymax, long *iymin, long *iymax);
 
-long Matrix_getWindowExtrema (I, long ixmin, long ixmax, long iymin, long iymax,
+long Matrix_getWindowExtrema (Matrix me, long ixmin, long ixmax, long iymin, long iymax,
 	double *minimum, double *maximum);
 /*
 	Function:
@@ -206,7 +206,7 @@ void Matrix_formula_part (Matrix me, double xmin, double xmax, double ymin, doub
 	by the minimum and maximum values of the samples inside the window.
 */
 
-void Matrix_drawRows (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_drawRows (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum);
 /*
 	Every row is plotted as a function of x,
@@ -214,55 +214,55 @@ void Matrix_drawRows (I, Graphics g, double xmin, double xmax, double ymin, doub
 	The rows are stacked from bottom to top.
 */
 
-void Matrix_drawOneContour (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_drawOneContour (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double height);
 
-void Matrix_drawContours (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_drawContours (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum);
 /* A contour altitude plot with curves at multiple heights. */
 
-void Matrix_paintContours (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintContours (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum);
 /* A contour plot with multiple shades of grey and white (low) and black (high) paint. */
 
-void Matrix_paintImage (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintImage (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum);
 /*
 	Two-dimensional interpolation of greys.
 	The larger the value of the sample, the darker the greys.
 */
 
-void Matrix_paintCells (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintCells (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum);
 /*
 	Every sample is drawn as a grey rectangle.
 	The larger the value of the sample, the darker the rectangle.
 */
 
-void Matrix_paintSurface (I, Graphics g, double xmin, double xmax, double ymin, double ymax,
+void Matrix_paintSurface (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
 	double minimum, double maximum, double elevation, double azimuth);
 /*
 	3D surface plot. Every space between adjacent four samples is drawn as a tetragon filled with a grey value.
 	'elevation' may be 30 degrees, 'azimuth' may be 45 degrees.
 */
 
-void Matrix_movie (I, Graphics g);
+void Matrix_movie (Matrix me, Graphics g);
 
 Matrix Matrix_readFromRawTextFile (MelderFile file);
 Matrix Matrix_readAP (MelderFile file);
 Matrix Matrix_appendRows (Matrix me, Matrix thee, ClassInfo klas);
 
-void Matrix_eigen (I, Matrix *eigenvectors, Matrix *eigenvalues);
-Matrix Matrix_power (I, long power);
+void Matrix_eigen (Matrix me, Matrix *eigenvectors, Matrix *eigenvalues);
+Matrix Matrix_power (Matrix me, long power);
 
-void Matrix_scaleAbsoluteExtremum (I, double scale);
+void Matrix_scaleAbsoluteExtremum (Matrix me, double scale);
 
 Matrix Table_to_Matrix (Table me);
 void Matrix_writeToMatrixTextFile (Matrix me, MelderFile file);
 void Matrix_writeToHeaderlessSpreadsheetFile (Matrix me, MelderFile file);
 
-Matrix TableOfReal_to_Matrix (I);
-TableOfReal Matrix_to_TableOfReal (I);
+Matrix TableOfReal_to_Matrix (TableOfReal me);
+TableOfReal Matrix_to_TableOfReal (Matrix me);
 
 /* End of file Matrix.h */
 #endif
diff --git a/fon/Movie.cpp b/fon/Movie.cpp
index 9461c6b..e64ded0 100644
--- a/fon/Movie.cpp
+++ b/fon/Movie.cpp
@@ -53,12 +53,12 @@ void structMovie :: v_info ()
 	MelderInfo_writeLine (L"   First frame centred at: ", Melder_double (x1), L" seconds");
 }
 
-void structMovie :: f_init (Sound sound, const wchar_t *folderName, Strings fileNames)
+void Movie_init (Movie me, Sound sound, const wchar_t *folderName, Strings fileNames)
 {
-	Sampled_init (this, sound -> xmin, sound -> xmax, fileNames ? fileNames -> numberOfStrings : 0, 0.04, 0.0);
-	d_sound = sound;
-	d_folderName = Melder_wcsdup (folderName);
-	d_fileNames = fileNames;
+	Sampled_init (me, sound -> xmin, sound -> xmax, fileNames ? fileNames -> numberOfStrings : 0, 0.04, 0.0);
+	my d_sound = sound;
+	my d_folderName = Melder_wcsdup (folderName);
+	my d_fileNames = fileNames;
 }
 
 Movie Movie_openFromSoundFile (MelderFile file)
@@ -76,35 +76,35 @@ Movie Movie_openFromSoundFile (MelderFile file)
 		autoStrings strings = Strings_createAsFileList (Melder_wcscat (fileNameHead.string, L"*.png"));
 		struct structMelderDir folder;
 		MelderFile_getParentDir (file, & folder);
-		my f_init (sound.transfer(), Melder_dirToPath (& folder), strings.transfer());
+		Movie_init (me.peek(), sound.transfer(), Melder_dirToPath (& folder), strings.transfer());
 		return me.transfer();
 	} catch (MelderError) {
 		Melder_throw ("Movie object not read from file ", file, ".");
 	}
 }
 
-void structMovie :: f_paintOneImageInside (Graphics graphics, long frameNumber, double a_xmin, double a_xmax, double a_ymin, double a_ymax)
+void Movie_paintOneImageInside (Movie me, Graphics graphics, long frameNumber, double xmin, double xmax, double ymin, double ymax)
 {
 	try {
 		if (frameNumber < 1) Melder_throw ("Specified frame number is ", frameNumber, " but should be at least 1.");
-		if (frameNumber > nx) Melder_throw ("Specified frame number is ", frameNumber, " but there are only ", nx, "frames.");
-		Melder_assert (d_fileNames != 0);
-		Melder_assert (d_fileNames -> numberOfStrings == nx);
+		if (frameNumber > my nx) Melder_throw ("Specified frame number is ", frameNumber, " but there are only ", my nx, "frames.");
+		Melder_assert (my d_fileNames != 0);
+		Melder_assert (my d_fileNames -> numberOfStrings == my nx);
 		struct structMelderDir folder;
-		Melder_pathToDir (d_folderName, & folder);
+		Melder_pathToDir (my d_folderName, & folder);
 		struct structMelderFile file;
-		MelderDir_getFile (& folder, d_fileNames -> strings [frameNumber], & file);
-		Graphics_imageFromFile (graphics, Melder_fileToPath (& file), a_xmin, a_xmax, a_ymin, a_ymax);
+		MelderDir_getFile (& folder, my d_fileNames -> strings [frameNumber], & file);
+		Graphics_imageFromFile (graphics, Melder_fileToPath (& file), xmin, xmax, ymin, ymax);
 	} catch (MelderError) {
-		Melder_throw (this, ": image ", frameNumber, " not painted.");
+		Melder_throw (me, ": image ", frameNumber, " not painted.");
 	}
 }
 
-void structMovie :: f_paintOneImage (Graphics graphics, long frameNumber, double a_xmin, double a_xmax, double a_ymin, double a_ymax) {
+void Movie_paintOneImage (Movie me, Graphics graphics, long frameNumber, double xmin, double xmax, double ymin, double ymax) {
 	try {
 		Graphics_setInner (graphics);
 		Graphics_setWindow (graphics, 0.0, 1.0, 0.0, 1.0);
-		f_paintOneImageInside (graphics, frameNumber, a_xmin, a_xmax, a_ymin, a_ymax);
+		Movie_paintOneImageInside (me, graphics, frameNumber, xmin, xmax, ymin, ymax);
 		Graphics_unsetInner (graphics);
 	} catch (MelderError) {
 		Graphics_unsetInner (graphics);   // TODO: should be auto
@@ -112,9 +112,10 @@ void structMovie :: f_paintOneImage (Graphics graphics, long frameNumber, double
 	}
 }
 
-void structMovie :: f_play (Graphics g, double tmin, double tmax, int (*callback) (void *closure, int phase, double tmin, double tmax, double t), void *closure)
+void Movie_play (Movie me, Graphics g, double tmin, double tmax, int (*callback) (void *closure, int phase, double tmin, double tmax, double t), void *closure)
 {
-	Sound_playPart (d_sound, tmin, tmax, callback, closure);
+	(void) g;
+	Sound_playPart (my d_sound, tmin, tmax, callback, closure);
 }
 
 /* End of file Movie.cpp */
diff --git a/fon/Movie.h b/fon/Movie.h
index 69d696e..3346733 100644
--- a/fon/Movie.h
+++ b/fon/Movie.h
@@ -2,7 +2,7 @@
 #define _Movie_h_
 /* Movie.h
  *
- * Copyright (C) 2011 Paul Boersma
+ * Copyright (C) 2011,2014 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
@@ -29,5 +29,13 @@ oo_CLASS_CREATE (Movie, Sampled);
 
 Movie Movie_openFromSoundFile (MelderFile file);
 
+void Movie_init (Movie me, Sound sound, const wchar_t *folderName, Strings fileNames);
+
+void Movie_paintOneImageInside (Movie me, Graphics graphics, long frameNumber, double xmin, double xmax, double ymin, double ymax);
+
+void Movie_paintOneImage (Movie me, Graphics graphics, long frameNumber, double xmin, double xmax, double ymin, double ymax);
+
+void Movie_play (Movie me, Graphics graphics, double tmin, double tmax, int (*callback) (void *closure, int phase, double tmin, double tmax, double t), void *closure);
+
 /* End of file Movie.h */
 #endif
diff --git a/fon/MovieWindow.cpp b/fon/MovieWindow.cpp
index 820210e..470a98e 100644
--- a/fon/MovieWindow.cpp
+++ b/fon/MovieWindow.cpp
@@ -1,6 +1,6 @@
 /* MovieWindow.cpp
  *
- * Copyright (C) 2011-2012,2013 Paul Boersma
+ * Copyright (C) 2011-2012,2013,2014 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
@@ -25,109 +25,112 @@ Thing_implement (MovieWindow, TimeSoundAnalysisEditor, 0);
 /********** MENU COMMANDS **********/
 
 void structMovieWindow :: v_createMenuItems_view (EditorMenu menu) {
-	MovieWindow_Parent :: v_createMenuItems_view (menu);
+	our MovieWindow_Parent :: v_createMenuItems_view (menu);
 	//EditorMenu_addCommand (menu, L"-- view/realtier --", 0, 0);
 	//EditorMenu_addCommand (menu, v_setRangeTitle (), 0, menu_cb_setRange);
 }
 
 void structMovieWindow :: v_createMenus () {
-	MovieWindow_Parent :: v_createMenus ();
+	our MovieWindow_Parent :: v_createMenus ();
 	//EditorMenu menu = Editor_addMenu (this, L"Movie", 0);
 	//EditorMenu_addCommand (menu, L"Add point at cursor", 'T', menu_cb_addPointAtCursor);
-	v_createMenus_analysis ();   // insert some of the ancestor's menus *after* the Movie menus
+	our v_createMenus_analysis ();   // insert some of the ancestor's menus *after* the Movie menus
 }
 
 /********** DRAWING AREA **********/
 
-double structMovieWindow :: h_getSoundBottomPosition () {
-	Movie movie = (Movie) data;
-	bool showAnalysis = (p_spectrogram_show || p_pitch_show || p_intensity_show || p_formant_show) && movie -> d_sound;
+/**
+ * @returns a value between 0.0 and 1.0; depends on whether the Sound and/or analyses are visible
+ */
+static double _MovieWindow_getSoundBottomPosition (MovieWindow me) {
+	Movie movie = (Movie) my data;
+	bool showAnalysis = (my p_spectrogram_show || my p_pitch_show || my p_intensity_show || my p_formant_show) && movie -> d_sound;
 	return movie -> d_sound ? (showAnalysis ? 0.7 : 0.3) : 1.0;
 }
 
 void structMovieWindow :: v_draw () {
-	Movie movie = (Movie) data;
-	bool showAnalysis = (p_spectrogram_show || p_pitch_show || p_intensity_show || p_formant_show) && movie -> d_sound;
-	double soundY = h_getSoundBottomPosition ();
+	Movie movie = (Movie) our data;
+	bool showAnalysis = (our p_spectrogram_show || our p_pitch_show || our p_intensity_show || our p_formant_show) && movie -> d_sound;
+	double soundY = _MovieWindow_getSoundBottomPosition (this);
 	if (movie -> d_sound) {
-		Graphics_Viewport viewport = Graphics_insetViewport (d_graphics, 0, 1, soundY, 1.0);
-		Graphics_setColour (d_graphics, Graphics_WHITE);
-		Graphics_setWindow (d_graphics, 0, 1, 0, 1);
-		Graphics_fillRectangle (d_graphics, 0, 1, 0, 1);
-		f_drawSound (-1.0, 1.0);
-		Graphics_flushWs (d_graphics);
-		Graphics_resetViewport (d_graphics, viewport);
+		Graphics_Viewport viewport = Graphics_insetViewport (our d_graphics, 0, 1, soundY, 1.0);
+		Graphics_setColour (our d_graphics, Graphics_WHITE);
+		Graphics_setWindow (our d_graphics, 0, 1, 0, 1);
+		Graphics_fillRectangle (our d_graphics, 0, 1, 0, 1);
+		our f_drawSound (-1.0, 1.0);
+		Graphics_flushWs (our d_graphics);
+		Graphics_resetViewport (our d_graphics, viewport);
 	}
 	if (true) {
 		Graphics_Viewport viewport = Graphics_insetViewport (d_graphics, 0.0, 1.0, 0.0, 0.3);
-		Graphics_setColour (d_graphics, Graphics_WHITE);
-		Graphics_setWindow (d_graphics, 0, 1, 0, 1);
-		Graphics_fillRectangle (d_graphics, 0, 1, 0, 1);
-		Graphics_setColour (d_graphics, Graphics_BLACK);
-		Graphics_setWindow (d_graphics, d_startWindow, d_endWindow, 0.0, 1.0);
-		long firstFrame = round (Sampled_xToIndex (movie, d_startWindow));
-		long lastFrame = round (Sampled_xToIndex (movie, d_endWindow));
+		Graphics_setColour (our d_graphics, Graphics_WHITE);
+		Graphics_setWindow (our d_graphics, 0, 1, 0, 1);
+		Graphics_fillRectangle (our d_graphics, 0, 1, 0, 1);
+		Graphics_setColour (our d_graphics, Graphics_BLACK);
+		Graphics_setWindow (our d_graphics, our d_startWindow, our d_endWindow, 0.0, 1.0);
+		long firstFrame = movie -> f_xToNearestIndex (our d_startWindow);
+		long lastFrame = movie -> f_xToNearestIndex (our d_endWindow);
 		if (firstFrame < 1) firstFrame = 1;
 		if (lastFrame > movie -> nx) lastFrame = movie -> nx;
 		for (long iframe = firstFrame; iframe <= lastFrame; iframe ++) {
-			double time = Sampled_indexToX (movie, iframe);
+			double time = movie -> f_indexToX (iframe);
 			double timeLeft = time - 0.5 * movie -> dx, timeRight = time + 0.5 * movie -> dx;
-			if (timeLeft < d_startWindow) timeLeft = d_startWindow;
-			if (timeRight > d_endWindow) timeRight = d_endWindow;
-			movie -> f_paintOneImageInside (d_graphics, iframe, timeLeft, timeRight, 0.0, 1.0);
+			if (timeLeft < our d_startWindow) timeLeft = our d_startWindow;
+			if (timeRight > our d_endWindow) timeRight = our d_endWindow;
+			Movie_paintOneImageInside (movie, our d_graphics, iframe, timeLeft, timeRight, 0.0, 1.0);
 		}
-		Graphics_flushWs (d_graphics);
-		Graphics_resetViewport (d_graphics, viewport);
+		Graphics_flushWs (our d_graphics);
+		Graphics_resetViewport (our d_graphics, viewport);
 	}
 	if (showAnalysis) {
-		Graphics_Viewport viewport = Graphics_insetViewport (d_graphics, 0.0, 1.0, 0.3, soundY);
-		v_draw_analysis ();
-		Graphics_flushWs (d_graphics);
-		Graphics_resetViewport (d_graphics, viewport);
+		Graphics_Viewport viewport = Graphics_insetViewport (our d_graphics, 0.0, 1.0, 0.3, soundY);
+		our v_draw_analysis ();
+		Graphics_flushWs (our d_graphics);
+		Graphics_resetViewport (our d_graphics, viewport);
 		/* Draw pulses. */
-		if (p_pulses_show) {
-			viewport = Graphics_insetViewport (d_graphics, 0.0, 1.0, soundY, 1.0);
-			v_draw_analysis_pulses ();
-			f_drawSound (-1.0, 1.0);   // second time, partially across the pulses
-			Graphics_flushWs (d_graphics);
-			Graphics_resetViewport (d_graphics, viewport);
+		if (our p_pulses_show) {
+			viewport = Graphics_insetViewport (our d_graphics, 0.0, 1.0, soundY, 1.0);
+			our v_draw_analysis_pulses ();
+			our f_drawSound (-1.0, 1.0);   // second time, partially across the pulses
+			Graphics_flushWs (our d_graphics);
+			Graphics_resetViewport (our d_graphics, viewport);
 		}
 	}
-	v_updateMenuItems_file ();
+	our v_updateMenuItems_file ();
 }
 
 void structMovieWindow :: v_highlightSelection (double left, double right, double bottom, double top) {
-	if (p_spectrogram_show)
-		Graphics_highlight (d_graphics, left, right, 0.3 * bottom + 0.7 * top, top);
+	if (our p_spectrogram_show)
+		Graphics_highlight (our d_graphics, left, right, 0.3 * bottom + 0.7 * top, top);
 	else
-		Graphics_highlight (d_graphics, left, right, 0.7 * bottom + 0.3 * top, top);
+		Graphics_highlight (our d_graphics, left, right, 0.7 * bottom + 0.3 * top, top);
 }
 
 void structMovieWindow :: v_unhighlightSelection (double left, double right, double bottom, double top) {
-	if (p_spectrogram_show)
-		Graphics_highlight (d_graphics, left, right, 0.3 * bottom + 0.7 * top, top);
+	if (our p_spectrogram_show)
+		Graphics_highlight (our d_graphics, left, right, 0.3 * bottom + 0.7 * top, top);
 	else
-		Graphics_highlight (d_graphics, left, right, 0.7 * bottom + 0.3 * top, top);
+		Graphics_highlight (our d_graphics, left, right, 0.7 * bottom + 0.3 * top, top);
 }
 
 int structMovieWindow :: v_click (double xWC, double yWC, bool shiftKeyPressed) {
-	return MovieWindow_Parent :: v_click (xWC, yWC, shiftKeyPressed);
+	return our MovieWindow_Parent :: v_click (xWC, yWC, shiftKeyPressed);
 }
 
-void structMovieWindow :: v_play (double a_tmin, double a_tmax) {
+void structMovieWindow :: v_play (double tmin, double tmax) {
 	Movie movie = (Movie) data;
-	movie -> f_play (d_graphics, a_tmin, a_tmax, theFunctionEditor_playCallback, this);
+	Movie_play (movie, our d_graphics, tmin, tmax, theFunctionEditor_playCallback, this);
 }
 
-void structMovieWindow :: f_init (const wchar_t *title, Movie movie) {
+void MovieWindow_init (MovieWindow me, const wchar_t *title, Movie movie) {
 	Melder_assert (movie != NULL);
-	structTimeSoundAnalysisEditor :: f_init (title, movie, movie -> d_sound, false);
+	my structTimeSoundAnalysisEditor :: f_init (title, movie, movie -> d_sound, false);
 }
 
 MovieWindow MovieWindow_create (const wchar_t *title, Movie movie) {
 	try {
 		autoMovieWindow me = Thing_new (MovieWindow);
-		my f_init (title, movie);
+		MovieWindow_init (me.peek(), title, movie);
 		return me.transfer();
 	} catch (MelderError) {
 		Melder_throw ("Movie window not created.");
diff --git a/fon/MovieWindow.h b/fon/MovieWindow.h
index 1d276da..223276d 100644
--- a/fon/MovieWindow.h
+++ b/fon/MovieWindow.h
@@ -2,7 +2,7 @@
 #define _MovieWindow_h_
 /* MovieWindow.h
  *
- * Copyright (C) 2011,2012 Paul Boersma
+ * Copyright (C) 2011,2012,2014 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
@@ -23,9 +23,6 @@
 #include "Movie.h"
 
 Thing_define (MovieWindow, TimeSoundAnalysisEditor) {
-	// functions:
-		public:
-			void f_init (const wchar_t *title, Movie data);
 	// overridden methods:
 		protected:
 			virtual void v_createMenus ();
@@ -35,11 +32,10 @@ Thing_define (MovieWindow, TimeSoundAnalysisEditor) {
 			virtual void v_createMenuItems_view (EditorMenu menu);
 			virtual void v_highlightSelection (double left, double right, double bottom, double top);
 			virtual void v_unhighlightSelection (double left, double right, double bottom, double top);
-	// helper functions:
-		private:
-			double h_getSoundBottomPosition ();   // between 0.0 and 1.0; depends on whether the Sound and/or analyses are visible
 };
 
+void MovieWindow_init (MovieWindow me, const wchar_t *title, Movie movie);
+
 MovieWindow MovieWindow_create (const wchar_t *title, Movie movie);
 
 /* End of file MovieWindow.h */
diff --git a/fon/Movie_def.h b/fon/Movie_def.h
index 6ff0335..62e64df 100644
--- a/fon/Movie_def.h
+++ b/fon/Movie_def.h
@@ -26,11 +26,6 @@ oo_DEFINE_CLASS (Movie, Sampled)
 	oo_OBJECT (Strings, 0, d_fileNames)
 
 	#if oo_DECLARING
-		// functions:
-			void f_init (Sound sound, const wchar_t *folderName, Strings fileNames);
-			void f_paintOneImageInside (Graphics graphics, long frameNumber, double a_xmin, double a_xmax, double a_ymin, double a_ymax);
-			void f_paintOneImage (Graphics graphics, long frameNumber, double a_xmin, double a_xmax, double a_ymin, double a_ymax);
-			void f_play (Graphics graphics, double tmin, double tmax, int (*callback) (void *closure, int phase, double tmin, double tmax, double t), void *closure);
 		// overridden methods:
 			virtual void v_info ();
 	#endif
diff --git a/fon/ParamCurve.cpp b/fon/ParamCurve.cpp
index 466f005..a1dfbe5 100644
--- a/fon/ParamCurve.cpp
+++ b/fon/ParamCurve.cpp
@@ -1,6 +1,6 @@
 /* ParamCurve.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -92,7 +92,7 @@ void structParamCurve :: v_readBinary (FILE *f) {
 	Data_readBinary (y, f);
 	Thing_version = saveVersion;
 	xmin = x -> xmin > y -> xmin ? x -> xmin : y -> xmin;
-	xmax = x -> xmax < y -> xmax ? x -> xmax : y -> xmax; 
+	xmax = x -> xmax < y -> xmax ? x -> xmax : y -> xmax;
 }
 
 void ParamCurve_init (ParamCurve me, Sound x, Sound y) {
@@ -101,7 +101,7 @@ void ParamCurve_init (ParamCurve me, Sound x, Sound y) {
 	my x = Data_copy (x);
 	my y = Data_copy (y);
 	my xmin = x -> xmin > y -> xmin ? x -> xmin : y -> xmin;
-	my xmax = x -> xmax < y -> xmax ? x -> xmax : y -> xmax; 
+	my xmax = x -> xmax < y -> xmax ? x -> xmax : y -> xmax;
 }
 
 ParamCurve ParamCurve_create (Sound x, Sound y) {
@@ -137,9 +137,9 @@ void ParamCurve_draw (ParamCurve me, Graphics g, double t1, double t2, double dt
 		autoNUMvector <double> y (1, numberOfPoints);
 		for (long i = 1; i <= numberOfPoints; i ++) {
 			double t = i == numberOfPoints ? t2 : t1 + (i - 1) * dt;
-			double index = Sampled_xToIndex (my x, t);
+			double index = my x -> f_xToIndex (t);
 			x [i] = NUM_interpolate_sinc (my x -> z [1], my x -> nx, index, 50);
-			index = Sampled_xToIndex (my y, t);
+			index = my y -> f_xToIndex (t);
 			y [i] = NUM_interpolate_sinc (my y -> z [1], my y -> nx, index, 50);
 		}
 		Graphics_setWindow (g, x1, x2, y1, y2);
diff --git a/fon/Photo.cpp b/fon/Photo.cpp
index bb38587..4df5196 100644
--- a/fon/Photo.cpp
+++ b/fon/Photo.cpp
@@ -1,6 +1,6 @@
 /* Photo.cpp
  *
- * Copyright (C) 2013 Paul Boersma
+ * Copyright (C) 2013,2014 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
@@ -20,10 +20,14 @@
 #include "Photo.h"
 #include "NUM2.h"
 #include "Formula.h"
-#if defined (macintosh)
+#if defined (_WIN32)
+	#include <GraphicsP.h>
+#elif defined (macintosh)
 	#include "macport_on.h"
 	#include <Cocoa/Cocoa.h>
 	#include "macport_off.h"
+#elif defined (linux)
+	#include <cairo/cairo.h>
 #endif
 
 #include "oo_DESTROY.h"
@@ -48,28 +52,28 @@
 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));
+	our structData :: v_info ();
+	MelderInfo_writeLine (L"xmin: ", Melder_double (our xmin));
+	MelderInfo_writeLine (L"xmax: ", Melder_double (our xmax));
+	MelderInfo_writeLine (L"Number of columns: ", Melder_integer (our nx));
+	MelderInfo_writeLine (L"dx: ", Melder_double (our dx), L" (-> sampling rate ", Melder_double (1.0 / our dx), L" )");
+	MelderInfo_writeLine (L"x1: ", Melder_double (our x1));
+	MelderInfo_writeLine (L"ymin: ", Melder_double (our ymin));
+	MelderInfo_writeLine (L"ymax: ", Melder_double (our ymax));
+	MelderInfo_writeLine (L"Number of rows: ", Melder_integer (our ny));
+	MelderInfo_writeLine (L"dy: ", Melder_double (our dy), L" (-> sampling rate ", Melder_double (1.0 / our dy), L" )");
+	MelderInfo_writeLine (L"y1: ", Melder_double (our 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)
+void Photo_init (Photo me,
+	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);
+	my structSampledXY :: f_init (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	my d_red =          Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	my d_green =        Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	my d_blue =         Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	my d_transparency = Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
 }
 
 Photo Photo_create
@@ -78,43 +82,95 @@ Photo Photo_create
 {
 	try {
 		autoPhoto me = Thing_new (Photo);
-		my f_init (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+		Photo_init (me.peek(), xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
 		return me.transfer();
 	} catch (MelderError) {
-		Melder_throw ("Matrix object not created.");
+		Melder_throw ("Photo 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);
+		Photo_init (me.peek(), 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.");
+		Melder_throw ("Photo 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) {
+	try {
+		#if defined (linux)
+			cairo_surface_t *surface = cairo_image_surface_create_from_png (Melder_peekWcsToUtf8 (file -> path));
+			//if (cairo_surface_status)
+			//	Melder_throw ("Error opening PNG file.");
+			long width = cairo_image_surface_get_width (surface);
+			long height = cairo_image_surface_get_height (surface);
+			if (width == 0 || height == 0) {
+				cairo_surface_destroy (surface);
+				Melder_throw ("Error reading PNG file.");
+			}
+			unsigned char *imageData = cairo_image_surface_get_data (surface);
+			long bytesPerRow = cairo_image_surface_get_stride (surface);
+			cairo_format_t format = cairo_image_surface_get_format (surface);
+			autoPhoto me = Photo_createSimple (height, width);
+			if (format == CAIRO_FORMAT_ARGB32) {
+				for (long irow = 1; irow <= height; irow ++) {
+					uint8_t *rowAddress = imageData + bytesPerRow * (height - irow);
+					for (long icol = 1; icol <= width; icol ++) {
+						my d_blue  -> z [irow] [icol] = (* rowAddress ++) / 255.0;
+						my d_green -> z [irow] [icol] = (* rowAddress ++) / 255.0;
+						my d_red   -> z [irow] [icol] = (* rowAddress ++) / 255.0;
+						my d_transparency -> z [irow] [icol] = 1.0 - (* rowAddress ++) / 255.0;
+					}
+				}
+			} else if (format == CAIRO_FORMAT_RGB24) {
+				for (long irow = 1; irow <= height; irow ++) {
+					uint8_t *rowAddress = imageData + bytesPerRow * (height - irow);
+					for (long icol = 1; icol <= width; icol ++) {
+						my d_blue  -> z [irow] [icol] = (* rowAddress ++) / 255.0;
+						my d_green -> z [irow] [icol] = (* rowAddress ++) / 255.0;
+						my d_red   -> z [irow] [icol] = (* rowAddress ++) / 255.0;
+						my d_transparency -> z [irow] [icol] = 0.0; rowAddress ++;
+					}
+				}
+			} else {
+				cairo_surface_destroy (surface);
+				Melder_throw ("Unsupported PNG format ", format, ".");
+			}
+			cairo_surface_destroy (surface);
+			return me.transfer();
+		#elif defined (_WIN32)
+			Gdiplus::Bitmap gdiplusBitmap (file -> path);
+			long width = gdiplusBitmap. GetWidth ();
+			long height = gdiplusBitmap. GetHeight ();
+			if (width == 0 || height == 0)
+				Melder_throw ("Error reading PNG file.");
+			autoPhoto me = Photo_createSimple (height, width);
+			for (long irow = 1; irow <= height; irow ++) {
+				for (long icol = 1; icol <= width; icol ++) {
+					Gdiplus::Color gdiplusColour;
+					gdiplusBitmap. GetPixel (icol - 1, height - irow, & gdiplusColour);
+					my d_red -> z [irow] [icol] = (gdiplusColour. GetRed ()) / 255.0;
+					my d_green -> z [irow] [icol] = (gdiplusColour. GetGreen ()) / 255.0;
+					my d_blue -> z [irow] [icol] = (gdiplusColour. GetBlue ()) / 255.0;
+					my d_transparency -> z [irow] [icol] = 1.0 - (gdiplusColour. GetAlpha ()) / 255.0;
+				}
+			}
+			return me.transfer();
+		#elif defined (macintosh)
+			autoPhoto me = NULL;
+			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)
+				Melder_throw ("Cannot open picture file ", file, ".");
 			CGImageRef image = CGImageSourceCreateImageAtIndex (imageSource, 0, NULL);
 			CFRelease (imageSource);
 			if (image != NULL) {
@@ -136,18 +192,20 @@ Photo Photo_readFromImageFile (MelderFile file) {
 				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_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_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();
+			return me.transfer();
+		#endif
+	} catch (MelderError) {
+		Melder_throw ("Picture file ", file, " not opened as Photo.");
+	}
 }
 
 #if defined (macintosh)
@@ -160,22 +218,93 @@ Photo Photo_readFromImageFile (MelderFile file) {
 	}
 #endif
 
+#ifdef linux
+	void structPhoto :: _lin_saveAsImageFile (MelderFile file, const wchar_t *which) {
+		cairo_format_t format = CAIRO_FORMAT_ARGB32;
+		long bytesPerRow = cairo_format_stride_for_width (format, our nx);   // likely to be our nx * 4;
+		long numberOfRows = our 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 (our d_blue         -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = round (our d_green        -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = round (our d_red          -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = 255 - round (our d_transparency -> z [irow] [icol] * 255.0);
+			}
+		}
+		cairo_surface_t *surface = cairo_image_surface_create_for_data (imageData,
+			format, our nx, our ny, bytesPerRow);
+		cairo_surface_write_to_png (surface, Melder_peekWcsToUtf8 (file -> path));
+		cairo_surface_destroy (surface);
+	}
+#endif
+
+#ifdef _WIN32
+void structPhoto::_win_saveAsImageFile (MelderFile file, const wchar_t *mimeType) {
+	Gdiplus::Bitmap gdiplusBitmap (nx, ny, PixelFormat32bppARGB);
+	for (long irow = 1; irow <= ny; irow ++) {
+		for (long icol = 1; icol <= nx; icol ++) {
+			Gdiplus::Color gdiplusColour (
+				255 - round (our d_transparency -> z [irow] [icol] * 255.0),
+				round (our d_red   -> z [irow] [icol] * 255.0),
+				round (our d_green -> z [irow] [icol] * 255.0),
+				round (our d_blue  -> z [irow] [icol] * 255.0));
+			gdiplusBitmap. SetPixel (icol - 1, ny - irow, gdiplusColour);
+		}
+	}
+	/*
+	 * The 'mimeType' parameter specifies a "class encoder". Look it up.
+	 */
+	UINT numberOfImageEncoders, sizeOfImageEncoderArray;
+	Gdiplus::GetImageEncodersSize (& numberOfImageEncoders, & sizeOfImageEncoderArray);
+	if (sizeOfImageEncoderArray == 0)
+		Melder_throw ("Cannot find image encoders.");
+	Gdiplus::ImageCodecInfo *imageEncoderInfos = Melder_malloc (Gdiplus::ImageCodecInfo, sizeOfImageEncoderArray);
+	Gdiplus::GetImageEncoders (numberOfImageEncoders, sizeOfImageEncoderArray, imageEncoderInfos);
+	for (int iencoder = 0; iencoder < numberOfImageEncoders; iencoder ++) {
+		trace ("Supported MIME type: %ls", imageEncoderInfos [iencoder]. MimeType);
+		if (Melder_wcsequ (imageEncoderInfos [iencoder]. MimeType, mimeType)) {
+			Gdiplus::EncoderParameters *p = NULL;
+			Gdiplus::EncoderParameters encoderParameters;
+			if (Melder_wcsequ (mimeType, L"image/jpeg")) {
+				encoderParameters. Count = 1;
+				GUID guid = { 0x1D5BE4B5, 0xFA4A, 0x452D, { 0x9C, 0xDD, 0x5D, 0xB3, 0x51, 0x05, 0xE7, 0xEB }};  // EncoderQuality
+				encoderParameters. Parameter [0]. Guid = guid;
+				encoderParameters. Parameter [0]. Type = Gdiplus::EncoderParameterValueTypeLong;
+				encoderParameters. Parameter [0]. NumberOfValues = 1;
+				ULONG quality = 100;
+				encoderParameters. Parameter [0]. Value = & quality;
+				p = & encoderParameters;
+			}
+			gdiplusBitmap. Save (file -> path, & imageEncoderInfos [iencoder]. Clsid, p);
+			Melder_free (imageEncoderInfos);
+			return;
+		}
+	}
+	Melder_throw ("Unknown MIME type ", mimeType, ".");
+}
+#endif
+
 #ifdef macintosh
-void structPhoto :: _mac_saveAsImageFile (MelderFile file, const void *which) {
-		long bytesPerRow = this -> nx * 4;
-		long numberOfRows = this -> ny;
+	void structPhoto :: _mac_saveAsImageFile (MelderFile file, const void *which) {
+		long bytesPerRow = our nx * 4;
+		long numberOfRows = our 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 ++ = round (our d_red          -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = round (our d_green        -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = round (our 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);
+		static CGColorSpaceRef colourSpace = NULL;
+		if (colourSpace == NULL) {
+			colourSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);   // used to be kCGColorSpaceUserRGB
+			Melder_assert (colourSpace != NULL);
+		}
 		CGDataProviderRef dataProvider = CGDataProviderCreateWithData (NULL,
 			imageData,
 			bytesPerRow * numberOfRows,
@@ -198,88 +327,123 @@ void structPhoto :: _mac_saveAsImageFile (MelderFile file, const void *which) {
 		CFRelease (destination);
 		CGColorSpaceRelease (colourSpace);
 		CGImageRelease (image);
-}
+	}
 #endif
 
 void structPhoto :: f_saveAsPNG (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/png");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypePNG);
+	#elif defined (linux)
+		_lin_saveAsImageFile (file, L"image/png");
 	#endif
 }
 
 void structPhoto :: f_saveAsTIFF (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/tiff");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypeTIFF);
+	#else
+		(void) file;
 	#endif
 }
 
 void structPhoto :: f_saveAsGIF (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/gif");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypeGIF);
+	#else
+		(void) file;
 	#endif
 }
 
 void structPhoto :: f_saveAsWindowsBitmapFile (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/bmp");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypeBMP);
+	#else
+		(void) file;
 	#endif
 }
 
 void structPhoto :: f_saveAsJPEG (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/jpeg");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypeJPEG);
+	#else
+		(void) file;
 	#endif
 }
 
 void structPhoto :: f_saveAsJPEG2000 (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/jpeg2000");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypeJPEG2000);
+	#else
+		(void) file;
 	#endif
 }
 
 void structPhoto :: f_saveAsAppleIconFile (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/ICNS");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypeAppleICNS);
+	#else
+		(void) file;
 	#endif
 }
 
 void structPhoto :: f_saveAsWindowsIconFile (MelderFile file) {
-	#ifdef macintosh
+	#if defined (_WIN32)
+		_win_saveAsImageFile (file, L"image/icon");
+	#elif defined (macintosh)
 		_mac_saveAsImageFile (file, kUTTypeICO);
+	#else
+		(void) file;
 	#endif
 }
 
-void structPhoto :: f_replaceRed (Matrix red) {
-	autoMatrix copy = Data_copy (red);
-	forget (d_red);
-	d_red = copy.transfer();
+void structPhoto :: f_replaceRed (Matrix a_red) {
+	autoMatrix copy = Data_copy (a_red);
+	forget (our d_red);
+	our 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_replaceGreen (Matrix a_green) {
+	autoMatrix copy = Data_copy (a_green);
+	forget (our d_green);
+	our 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_replaceBlue (Matrix a_blue) {
+	autoMatrix copy = Data_copy (a_blue);
+	forget (our d_blue);
+	our d_blue = copy.transfer();
 }
 
-void structPhoto :: f_replaceTransparency (Matrix transparency) {
-	autoMatrix copy = Data_copy (transparency);
-	forget (d_transparency);
-	d_transparency = copy.transfer();
+void structPhoto :: f_replaceTransparency (Matrix a_transparency) {
+	autoMatrix copy = Data_copy (a_transparency);
+	forget (our d_transparency);
+	our d_transparency = copy.transfer();
 }
 
-static void cellArrayOrImage (Photo me, Graphics g, double xmin, double xmax, double ymin, double ymax, bool interpolate) {
+static void _Photo_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;
+	if (ixmin > ixmax || iymin > iymax) {
+		Melder_fatal ("ixmin %ld ixmax %ld iymin %ld iymax %ld", ixmin, ixmax, iymin, iymax);
+		return;
+	}
 	Graphics_setInner (g);
 	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
 	autoNUMmatrix <double_rgbt> z (iymin, iymax, ixmin, ixmax);
@@ -293,22 +457,22 @@ static void cellArrayOrImage (Photo me, Graphics g, double xmin, double xmax, do
 	}
 	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);
+			ixmin, ixmax, my f_indexToX (ixmin - 0.5), my f_indexToX (ixmax + 0.5),
+			iymin, iymax, my f_indexToY (iymin - 0.5), my f_indexToY (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);
+			ixmin, ixmax, my f_indexToX (ixmin - 0.5), my f_indexToX (ixmax + 0.5),
+			iymin, iymax, my f_indexToY (iymin - 0.5), my f_indexToY (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);
+	_Photo_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);
+	_Photo_cellArrayOrImage (this, g, xmin, xmax, ymin, ymax, false);
 }
 
 /* End of file Photo.cpp */
diff --git a/fon/Photo.h b/fon/Photo.h
index f81f1a6..caeecf0 100644
--- a/fon/Photo.h
+++ b/fon/Photo.h
@@ -2,7 +2,7 @@
 #define _Photo_h_
 /* Photo.h
  *
- * Copyright (C) 2013 Paul Boersma
+ * Copyright (C) 2013,2014 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
@@ -24,6 +24,9 @@
 #include "Photo_def.h"
 oo_CLASS_CREATE (Photo, SampledXY);
 
+void Photo_init (Photo me,
+	double xmin, double xmax, long nx, double dx, double x1,
+	double ymin, double ymax, long ny, double dy, double y1);
 
 Photo Photo_create
 	(double xmin, double xmax, long nx, double dx, double x1,
diff --git a/fon/Photo_def.h b/fon/Photo_def.h
index 14e8217..ae029eb 100644
--- a/fon/Photo_def.h
+++ b/fon/Photo_def.h
@@ -1,6 +1,6 @@
 /* Photo_def.h
  *
- * Copyright (C) 2013 Paul Boersma
+ * Copyright (C) 2013,2014 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
@@ -37,8 +37,6 @@ oo_DEFINE_CLASS (Photo, SampledXY)
 			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; }
 		// 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);
 			/*
@@ -69,8 +67,12 @@ oo_DEFINE_CLASS (Photo, SampledXY)
 			void f_saveAsAppleIconFile     (MelderFile file);
 			void f_saveAsWindowsIconFile   (MelderFile file);
 		// helpers:
-			#if defined (macintosh)
+			#if defined (_WIN32)
+				void _win_saveAsImageFile (MelderFile file, const wchar_t *which);
+			#elif defined (macintosh)
 				void _mac_saveAsImageFile (MelderFile file, const void *which);
+			#elif defined (linux)
+				void _lin_saveAsImageFile (MelderFile file, const wchar_t *which);
 			#endif
 	#endif
 
diff --git a/fon/Pitch.cpp b/fon/Pitch.cpp
index 3011fba..101afa3 100644
--- a/fon/Pitch.cpp
+++ b/fon/Pitch.cpp
@@ -1,6 +1,6 @@
 /* Pitch.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -240,15 +240,15 @@ static long Pitch_getMeanAbsoluteSlope (Pitch me,
 	double *out_hertz, double *out_mel, double *out_semitones, double *out_erb, double *out_withoutOctaveJumps)
 {
 	long firstVoicedFrame = 0, lastVoicedFrame = 0, nVoiced = 0, i;
-	double *frequencies = NUMvector <double> (1, my nx);
+	autoNUMvector <double> frequencies (1, my nx);
 	for (i = 1; i <= my nx; i ++) {
 		double frequency = my frame [i]. candidate [1]. frequency;
 		frequencies [i] = frequency > 0.0 && frequency < my ceiling ? frequency : 0.0;
 		if (frequencies [i]) nVoiced ++;
 	}
-	for (i = 1; i <= my nx; i ++)   /* Look for first voiced frame. */
+	for (i = 1; i <= my nx; i ++)   // look for first voiced frame
 		if (frequencies [i] != 0.0) { firstVoicedFrame = i; break; }
-	for (i = my nx; i >= 1; i --)   /* Look for last voiced frame. */
+	for (i = my nx; i >= 1; i --)   // look for last voiced frame
 		if (frequencies [i] != 0.0) { lastVoicedFrame = i; break; }
 	if (nVoiced > 1) {
 		int ilast = firstVoicedFrame;
@@ -260,7 +260,7 @@ static long Pitch_getMeanAbsoluteSlope (Pitch me,
 			slopeMel += fabs (MEL (frequencies [i]) - MEL (flast));
 			slopeSemitones += localStepSemitones;
 			slopeErb += fabs (ERB (frequencies [i]) - ERB (flast));
-			while (localStepSemitones >= 12.0) localStepSemitones -= 12.0;   /* Kill octave jumps. */
+			while (localStepSemitones >= 12.0) localStepSemitones -= 12.0;   // kill octave jumps
 			if (localStepSemitones > 6.0) localStepSemitones = 12.0 - localStepSemitones;
 			slopeRobust += localStepSemitones;
 			ilast = i;
@@ -278,7 +278,6 @@ static long Pitch_getMeanAbsoluteSlope (Pitch me,
 		if (out_erb) *out_erb = NUMundefined;
 		if (out_withoutOctaveJumps) *out_withoutOctaveJumps = NUMundefined;
 	}
-	NUMvector_free (frequencies, 1);
 	return nVoiced;
 }
 
@@ -304,7 +303,7 @@ long Pitch_getMeanAbsSlope_noOctave (Pitch me, double *slope) {
 
 void structPitch :: v_info () {
 	long nVoiced;
-	double *frequencies = Sampled_getSortedValues (this, Pitch_LEVEL_FREQUENCY, kPitch_unit_HERTZ, & nVoiced);
+	autoNUMvector <double> frequencies (Sampled_getSortedValues (this, Pitch_LEVEL_FREQUENCY, kPitch_unit_HERTZ, & nVoiced), 1);
 	structData :: v_info ();
 	MelderInfo_writeLine (L"Time domain:");
 	MelderInfo_writeLine (L"   Start time: ", Melder_double (xmin), L" seconds");
@@ -316,13 +315,13 @@ void structPitch :: v_info () {
 	MelderInfo_writeLine (L"   First frame centred at: ", Melder_double (x1), L" seconds");
 	MelderInfo_writeLine (L"Ceiling at: ", Melder_double (ceiling), L" Hz");
 
-	if (nVoiced >= 1) {   /* Quantiles. */
+	if (nVoiced >= 1) {   // quantiles
 		double quantile10, quantile16, quantile50, quantile84, quantile90;
-		quantile10 = NUMquantile (nVoiced, frequencies, 0.10);
-		quantile16 = NUMquantile (nVoiced, frequencies, 0.16);
-		quantile50 = NUMquantile (nVoiced, frequencies, 0.50);   /* Median. */
-		quantile84 = NUMquantile (nVoiced, frequencies, 0.84);
-		quantile90 = NUMquantile (nVoiced, frequencies, 0.90);
+		quantile10 = NUMquantile (nVoiced, frequencies.peek(), 0.10);
+		quantile16 = NUMquantile (nVoiced, frequencies.peek(), 0.16);
+		quantile50 = NUMquantile (nVoiced, frequencies.peek(), 0.50);   // median
+		quantile84 = NUMquantile (nVoiced, frequencies.peek(), 0.84);
+		quantile90 = NUMquantile (nVoiced, frequencies.peek(), 0.90);
 		MelderInfo_writeLine (L"\nEstimated quantiles:");
 		MelderInfo_write (L"   10% = ", Melder_single (quantile10), L" Hz = ", Melder_single (MEL (quantile10)), L" Mel = ");
 		MelderInfo_writeLine (Melder_single (SEMITONES (quantile10)), L" semitones above 100 Hz = ", Melder_single (ERB (quantile10)), L" ERB");
@@ -345,7 +344,7 @@ void structPitch :: v_info () {
 			MelderInfo_writeLine (Melder_half ((SEMITONES (quantile90) - SEMITONES (quantile10)) * corr), L" semitones = ", Melder_half ((ERB (quantile90) - ERB (quantile10)) * corr), L" ERB");
 		}
 	}
-	if (nVoiced >= 1) {   /* Extrema, range, mean and standard deviation. */
+	if (nVoiced >= 1) {   // extrema, range, mean and standard deviation
 		double minimum = Pitch_getMinimum (this, xmin, xmax, kPitch_unit_HERTZ, FALSE);
 		double maximum = Pitch_getMaximum (this, xmin, xmax, kPitch_unit_HERTZ, FALSE);
 		double meanHertz, meanMel, meanSemitones, meanErb;
@@ -370,8 +369,7 @@ void structPitch :: v_info () {
 			MelderInfo_writeLine (Melder_half (stdevSemitones), L" semitones = ", Melder_half (stdevErb), L" ERB");
 		}
 	}
-	NUMvector_free (frequencies, 1);
-	if (nVoiced > 1) {   /* Variability: mean absolute slope. */
+	if (nVoiced > 1) {   // variability: mean absolute slope
 		double slopeHertz, slopeMel, slopeSemitones, slopeErb, slopeWithoutOctaveJumps;
 		Pitch_getMeanAbsoluteSlope (this, & slopeHertz, & slopeMel, & slopeSemitones, & slopeErb, & slopeWithoutOctaveJumps);
 		MelderInfo_write (L"\nMean absolute slope: ", Melder_half (slopeHertz), L" Hz/s = ", Melder_half (slopeMel), L" Mel/s = ");
@@ -568,26 +566,26 @@ void Pitch_pathFinder (Pitch me, double silenceThreshold, double voicingThreshol
 	}
 }
 
-void Pitch_drawInside (Pitch me, Graphics g, double xmin, double xmax, double fmin, double fmax, int speckle, int unit) {
+void Pitch_drawInside (Pitch me, Graphics g, double xmin, double xmax, double fmin, double fmax, bool speckle, int unit) {
 	Sampled_drawInside (me, g, xmin, xmax, fmin, fmax, speckle, Pitch_LEVEL_FREQUENCY, unit);
 }
 
-void Pitch_draw (Pitch me, Graphics g, double tmin, double tmax, double fmin, double fmax, int garnish, int speckle, int unit) {
+void Pitch_draw (Pitch me, Graphics g, double tmin, double tmax, double fmin, double fmax, bool garnish, bool speckle, int unit) {
 	Graphics_setInner (g);
 	Pitch_drawInside (me, g, tmin, tmax, fmin, fmax, speckle, unit);
 	Graphics_unsetInner (g);
 	if (garnish) {
 		Graphics_drawInnerBox (g);
-		Graphics_textBottom (g, TRUE, L"Time (s)");
-		Graphics_marksBottom (g, 2, TRUE, TRUE, FALSE);
+		Graphics_textBottom (g, true, L"Time (s)");
+		Graphics_marksBottom (g, 2, true, true, false);
 		static MelderString buffer = { 0 };
 		MelderString_empty (& buffer);
 		MelderString_append (& buffer, L"Pitch (", Function_getUnitText (me, Pitch_LEVEL_FREQUENCY, unit, Function_UNIT_TEXT_GRAPHICAL), L")");
 		Graphics_textLeft (g, TRUE, buffer.string);
 		if (Function_isUnitLogarithmic (me, Pitch_LEVEL_FREQUENCY, unit)) {
-			Graphics_marksLeftLogarithmic (g, 6, TRUE, TRUE, FALSE);
+			Graphics_marksLeftLogarithmic (g, 6, true, true, false);
 		} else {
-			Graphics_marksLeft (g, 2, TRUE, TRUE, FALSE);
+			Graphics_marksLeft (g, 2, true, true, false);
 		}
 	}
 }
@@ -602,7 +600,7 @@ void Pitch_difference (Pitch me, Pitch thee) {
 		double myf = my frame [i]. candidate [1]. frequency, thyf = thy frame [i]. candidate [1]. frequency;
 		int myUnvoiced = myf == 0 || myf > my ceiling;
 		int thyUnvoiced = thyf == 0 || thyf > thy ceiling;
-		double t = Sampled_indexToX (me, i);
+		double t = my f_indexToX (i);
 		if (myUnvoiced && ! thyUnvoiced) {
 			Melder_casual ("Frame %ld time %f: unvoiced to voiced.", i, t);
 			nuvtov ++;
diff --git a/fon/Pitch.h b/fon/Pitch.h
index 7a978c0..6b9d042 100644
--- a/fon/Pitch.h
+++ b/fon/Pitch.h
@@ -2,7 +2,7 @@
 #define _Pitch_h_
 /* Pitch.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -126,12 +126,12 @@ void Pitch_pathFinder (Pitch me, double silenceThreshold, double voicingThreshol
 	double ceiling, int pullFormants);
 
 /* Drawing methods. */
-#define Pitch_speckle_NO  FALSE
-#define Pitch_speckle_YES  TRUE
+#define Pitch_speckle_NO  false
+#define Pitch_speckle_YES  true
 void Pitch_drawInside (Pitch me, Graphics g, double tmin, double tmax, double fmin, double fmax,
-	int speckle, int yscale);
-void Pitch_draw (Pitch me, Graphics g, double tmin, double tmax, double fmin, double fmax, int garnish,
-	int speckle, int yscale);
+	bool speckle, int yscale);
+void Pitch_draw (Pitch me, Graphics g, double tmin, double tmax, double fmin, double fmax, bool garnish,
+	bool speckle, int yscale);
 /*
 	draw a pitch contour into a Graphics.
 	If tmax <= tmin, draw whole time domain.
diff --git a/fon/PitchEditor.cpp b/fon/PitchEditor.cpp
index c7b33b8..34bd32d 100644
--- a/fon/PitchEditor.cpp
+++ b/fon/PitchEditor.cpp
@@ -1,6 +1,6 @@
 /* PitchEditor.cpp
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2014 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
@@ -226,7 +226,7 @@ void structPitchEditor :: v_draw () {
 
 		for (it = it1; it <= it2; it ++) {
 			Pitch_Frame frame = & pitch -> frame [it];
-			double t = Sampled_indexToX (pitch, it);
+			double t = pitch -> f_indexToX (it);
 			double f = frame -> candidate [1]. frequency;
 			if (f > 0.0 && f < pitch -> ceiling) {
 				Graphics_setColour (d_graphics, Graphics_MAGENTA);
@@ -259,7 +259,7 @@ void structPitchEditor :: v_draw () {
 		Graphics_setTextAlignment (d_graphics, Graphics_CENTRE, Graphics_HALF);
 		for (it = it1; it <= it2; it ++) {
 			Pitch_Frame frame = & pitch -> frame [it];
-			double t = Sampled_indexToX (pitch, it);
+			double t = pitch -> f_indexToX (it);
 			int strength = (int) floor (10 * frame -> intensity + 0.5);   // map 0.0-1.0 to 0-9
 			if (strength > 9) strength = 9;
 			Graphics_text1 (d_graphics, t, 0.5, Melder_integer (strength));
@@ -284,7 +284,7 @@ void structPitchEditor :: v_draw () {
 		Graphics_text (d_graphics, d_endWindow, 0.5, L"Unv");
 		for (it = it1; it <= it2; it ++) {
 			Pitch_Frame frame = & pitch -> frame [it];
-			double t = Sampled_indexToX (pitch, it), tleft = t - 0.5 * pitch -> dx, tright = t + 0.5 * pitch -> dx;
+			double t = pitch -> f_indexToX (it), tleft = t - 0.5 * pitch -> dx, tright = t + 0.5 * pitch -> dx;
 			double f = frame -> candidate [1]. frequency;
 			if ((f > 0.0 && f < pitch -> ceiling) || tright <= d_startWindow || tleft >= d_endWindow) continue;
 			if (tleft < d_startWindow) tleft = d_startWindow;
@@ -315,7 +315,7 @@ int structPitchEditor :: v_click (double xWC, double yWC, bool dummy) {
 	if (ibestFrame > pitch -> nx) ibestFrame = pitch -> nx;
 	bestFrame = & pitch -> frame [ibestFrame];
 
-	tmid = Sampled_indexToX (pitch, ibestFrame);
+	tmid = pitch -> f_indexToX (ibestFrame);
 	for (cand = 1; cand <= bestFrame -> nCandidates; cand ++) {
 		double df = frequency - bestFrame -> candidate [cand]. frequency;
 		if (fabs (df) < minimumDf) {
diff --git a/fon/Pitch_Intensity.cpp b/fon/Pitch_Intensity.cpp
index d9f123b..831a60b 100644
--- a/fon/Pitch_Intensity.cpp
+++ b/fon/Pitch_Intensity.cpp
@@ -1,6 +1,6 @@
 /* Pitch_Intensity.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -51,13 +51,13 @@ void Pitch_Intensity_draw (Pitch pitch, Intensity intensity, Graphics g,
 	long previousI = 0;
 	double previousX = NUMundefined, previousY = NUMundefined;
 	for (long i = 1; i <= pitch -> nx; i ++) {
-		double t = Sampled_indexToX (pitch, i);
+		double t = pitch -> f_indexToX (i);
 		double x = pitch -> frame [i]. candidate [1]. frequency;
 		double y = Sampled_getValueAtX (intensity, t, Pitch_LEVEL_FREQUENCY, kPitch_unit_HERTZ, TRUE);
 		if (x == 0) {
 			continue;   /* Voiceless. */
 		}
-		if (connect & 1) Graphics_fillCircle_mm (g, x, y, 1.0);
+		if (connect & 1) Graphics_speckle (g, x, y);
 		if ((connect & 2) && NUMdefined (previousX)) {
 			if (previousI >= 1 && previousI < i - 1) {
 				Graphics_setLineType (g, Graphics_DOTTED);
diff --git a/fon/Pitch_to_PitchTier.cpp b/fon/Pitch_to_PitchTier.cpp
index d0ac3a0..fb8b0d3 100644
--- a/fon/Pitch_to_PitchTier.cpp
+++ b/fon/Pitch_to_PitchTier.cpp
@@ -1,6 +1,6 @@
 /* Pitch_to_PitchTier.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -36,7 +36,7 @@ PitchTier Pitch_to_PitchTier (Pitch me) {
 			 * Count only voiced frames.
 			 */
 			if (frequency > 0.0 && frequency < my ceiling) {
-				double time = Sampled_indexToX (me, i);
+				double time = my f_indexToX (i);
 				RealTier_addPoint (thee.peek(), time, frequency);
 			}
 		}
@@ -68,7 +68,7 @@ static void Pitch_line (Pitch me, Graphics g, double tmin, double fleft, double
 		} else if (nonPeriodicLineType != 2) {
 			Graphics_setLineWidth (g, 2 * lineWidth);
 		}
-		tleft = Sampled_indexToX (me, i) - 0.5 * my dx, tright = tleft + my dx;
+		tleft = my f_indexToX (i) - 0.5 * my dx, tright = tleft + my dx;
 		if (tleft < tmin) tleft = tmin;
 		if (tright > tmax) tright = tmax;
 		Graphics_line (g, tleft, fleft + (tleft - tmin) * slope,
@@ -99,7 +99,7 @@ void PitchTier_Pitch_draw (PitchTier me, Pitch uv, Graphics g,
 	} else for (i = imin; i <= imax; i ++) {
 		RealPoint point = (RealPoint) my points -> item [i];
 		double t = point -> number, f = point -> value;
-		Graphics_fillCircle_mm (g, t, f, 1);
+		Graphics_speckle (g, t, f);
 		if (i == 1)
 			Pitch_line (uv, g, tmin, f, t, f, nonPeriodicLineType);
 		else if (i == imin)
@@ -131,7 +131,7 @@ Pitch Pitch_PitchTier_to_Pitch (Pitch me, PitchTier tier) {
 			Pitch_Frame frame = & thy frame [iframe];
 			Pitch_Candidate cand = & frame -> candidate [1];
 			if (cand -> frequency > 0.0 && cand -> frequency <= my ceiling)
-				cand -> frequency = RealTier_getValueAtTime (tier, Sampled_indexToX (me, iframe));
+				cand -> frequency = RealTier_getValueAtTime (tier, my f_indexToX (iframe));
 			cand -> strength = 0.9;
 			frame -> nCandidates = 1;
 		}
diff --git a/fon/Pitch_to_PointProcess.cpp b/fon/Pitch_to_PointProcess.cpp
index ffafbec..4b125cc 100644
--- a/fon/Pitch_to_PointProcess.cpp
+++ b/fon/Pitch_to_PointProcess.cpp
@@ -1,6 +1,6 @@
 /* Pitch_to_PointProcess.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -63,8 +63,8 @@ static int Pitch_getVoicedIntervalAfter (Pitch me, double after, double *tleft,
 		if (! Pitch_isVoiced_i (me, iright)) break;
 	iright --;
 
-	*tleft = Sampled_indexToX (me, ileft) - 0.5 * my dx;   /* The whole frame is considered voiced. */
-	*tright = Sampled_indexToX (me, iright) + 0.5 * my dx;
+	*tleft = my f_indexToX (ileft) - 0.5 * my dx;   /* The whole frame is considered voiced. */
+	*tright = my f_indexToX (iright) + 0.5 * my dx;
 	if (*tleft >= my xmax - 0.5 * my dx) return 0;
 	if (*tleft < my xmin) *tleft = my xmin;
 	if (*tright > my xmax) *tright = my xmax;
diff --git a/fon/PointEditor.cpp b/fon/PointEditor.cpp
index 6458f0d..d76ee92 100644
--- a/fon/PointEditor.cpp
+++ b/fon/PointEditor.cpp
@@ -1,6 +1,6 @@
 /* PointEditor.cpp
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2014 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
@@ -189,7 +189,7 @@ void structPointEditor :: v_draw () {
 			Graphics_line (d_graphics, d_startWindow, 0.0, d_endWindow, 0.0);
 			Graphics_setLineType (d_graphics, Graphics_DRAWN);      
 			Graphics_function (d_graphics, sound -> z [1], first, last,
-				Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
+				sound -> f_indexToX (first), sound -> f_indexToX (last));
 		}
 	}
 	Graphics_setColour (d_graphics, Graphics_BLUE);
diff --git a/fon/PointProcess.cpp b/fon/PointProcess.cpp
index 4843fee..e38b459 100644
--- a/fon/PointProcess.cpp
+++ b/fon/PointProcess.cpp
@@ -319,9 +319,9 @@ PointProcess PointProcesses_difference (PointProcess me, PointProcess thee) {
 void PointProcess_fill (PointProcess me, double tmin, double tmax, double period) {
 	try {
 		if (tmax <= tmin) tmin = my xmin, tmax = my xmax;   // autowindowing
-		long n = floor ((tmax - tmin) / period), i;
+		long n = floor ((tmax - tmin) / period);
 		double t = 0.5 * (tmin + tmax - n * period);
-		for (i = 1, t = 0.5 * (tmin + tmax - n * period); i <= n; i ++, t += period) {
+		for (long i = 1; i <= n; i ++, t += period) {
 			PointProcess_addPoint (me, t);
 		}
 	} catch (MelderError) {
diff --git a/fon/PointProcess_and_Sound.cpp b/fon/PointProcess_and_Sound.cpp
index 051e4bd..2d04df6 100644
--- a/fon/PointProcess_and_Sound.cpp
+++ b/fon/PointProcess_and_Sound.cpp
@@ -1,6 +1,6 @@
 /* PointProcess_and_Sound.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -53,7 +53,7 @@ Sound PointProcess_to_Sound_pulseTrain
 			long begin = mid - interpolationDepth, end = mid + interpolationDepth;
 			if (begin < 1) begin = 1;
 			if (end > thy nx) end = thy nx;
-			angle = NUMpi * (Sampled_indexToX (thee.peek(), begin) - t) / thy dx;
+			angle = NUMpi * (thy f_indexToX (begin) - t) / thy dx;
 			halfampsinangle = 0.5 * amplitude * sin (angle);
 			for (long j = begin; j <= end; j ++) {
 				if (fabs (angle) < 1e-6)
diff --git a/fon/Polygon.cpp b/fon/Polygon.cpp
index 4486493..0d0f6ce 100644
--- a/fon/Polygon.cpp
+++ b/fon/Polygon.cpp
@@ -1,6 +1,6 @@
 /* Polygon.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -37,28 +37,28 @@
 Thing_implement (Polygon, Data, 1);
 
 void structPolygon :: v_info () {
-	structData :: v_info ();
-	MelderInfo_writeLine (L"Number of points: ", Melder_integer (numberOfPoints));
+	our structData :: v_info ();
+	MelderInfo_writeLine (L"Number of points: ", Melder_integer (our numberOfPoints));
 	MelderInfo_writeLine (L"Perimeter: ", Melder_single (Polygon_perimeter (this)));
 }
   
 void structPolygon :: v_writeText (MelderFile file) {
-	texputi4 (file, numberOfPoints, L"numberOfPoints", 0,0,0,0,0);
-	for (long i = 1; i <= numberOfPoints; i ++) {
-		texputr4 (file, x [i], L"x [", Melder_integer (i), L"]", 0,0,0);
-		texputr4 (file, y [i], L"y [", Melder_integer (i), L"]", 0,0,0);
+	texputi4 (file, our numberOfPoints, L"numberOfPoints", 0,0,0,0,0);
+	for (long i = 1; i <= our numberOfPoints; i ++) {
+		texputr4 (file, our x [i], L"x [", Melder_integer (i), L"]", 0,0,0);
+		texputr4 (file, our y [i], L"y [", Melder_integer (i), L"]", 0,0,0);
 	}
 }
 
 void structPolygon :: v_readText (MelderReadText text) {
-	numberOfPoints = texgeti4 (text);
-	if (numberOfPoints < 1)
-		Melder_throw ("Cannot read a Polygon with only ", numberOfPoints, " points.");
-	x = NUMvector <double> (1, numberOfPoints);
-	y = NUMvector <double> (1, numberOfPoints);
-	for (long i = 1; i <= numberOfPoints; i ++) {
-		x [i] = texgetr4 (text);
-		y [i] = texgetr4 (text);
+	our numberOfPoints = texgeti4 (text);
+	if (our numberOfPoints < 1)
+		Melder_throw ("Cannot read a Polygon with only ", our numberOfPoints, " points.");
+	our x = NUMvector <double> (1, our numberOfPoints);
+	our y = NUMvector <double> (1, our numberOfPoints);
+	for (long i = 1; i <= our numberOfPoints; i ++) {
+		our x [i] = texgetr4 (text);
+		our y [i] = texgetr4 (text);
 	}
 }
 
diff --git a/fon/Praat_tests.cpp b/fon/Praat_tests.cpp
index fbce97e..f266d55 100644
--- a/fon/Praat_tests.cpp
+++ b/fon/Praat_tests.cpp
@@ -9,10 +9,13 @@
 
 #include "enums_getText.h"
 #include "Praat_tests_enums.h"
+#include "enums_getValue.h"
+#include "Praat_tests_enums.h"
+
 
 int Praat_tests (int itest, wchar_t *arg1, wchar_t *arg2, wchar_t *arg3, wchar_t *arg4) {
 	long i, n = wcstol (arg1, NULL, 10);
-	double x;
+	double x, t;
 	(void) arg1;
 	(void) arg2;
 	(void) arg3;
@@ -20,15 +23,15 @@ int Praat_tests (int itest, wchar_t *arg1, wchar_t *arg2, wchar_t *arg3, wchar_t
 	Melder_clearInfo ();
 	Melder_stopwatch ();
 	switch (itest) {
-		case kPraatTests_CHECK_RANDOM_1009_2009: {
-			NUMrandomRestart (310952);
-			for (i = 1; i <= 1009 * 2009 - 100 + 1; i ++)
-				x = NUMrandomFraction ();
-			MelderInfo_writeLine (Melder_double (x));
-		} break;
 		case kPraatTests_TIME_RANDOM_FRACTION: {
 			for (i = 1; i <= n; i ++)
 				(void) NUMrandomFraction ();
+			t = Melder_stopwatch ();
+		} break;
+		case kPraatTests_TIME_RANDOM_GAUSS: {
+			for (i = 1; i <= n; i ++)
+				(void) NUMrandomGauss (0.0, 1.0);
+			t = Melder_stopwatch ();
 		} break;
 		case kPraatTests_TIME_SORT: {
 			long m = wcstol (arg2, NULL, 10);
@@ -38,10 +41,25 @@ int Praat_tests (int itest, wchar_t *arg1, wchar_t *arg2, wchar_t *arg3, wchar_t
 			Melder_stopwatch ();
 			for (i = 1; i <= n; i ++)
 				NUMsort_l (m, array);
+			t = Melder_stopwatch ();
 			NUMvector_free (array, 1);
 		} break;
+		case kPraatTests_TIME_INTEGER: {
+			double sum = 0;
+			for (i = 1; i <= n; i ++)
+				sum += i * (i - 1) * (i - 2);
+			t = Melder_stopwatch ();
+			MelderInfo_writeLine (Melder_double (sum));
+		} break;
+		case kPraatTests_TIME_FLOAT: {
+			double sum = 0.0, fn = n;
+			for (double fi = 1.0; fi <= fn; fi = fi + 1.0)
+				sum += fi * (fi - 1.0) * (fi - 2.0);
+			t = Melder_stopwatch ();
+			MelderInfo_writeLine (Melder_double (sum));
+		} break;
 	}
-	MelderInfo_writeLine (Melder_single (Melder_stopwatch () / n * 1e9), L" nanoseconds");
+	MelderInfo_writeLine (Melder_single (t / n * 1e9), L" nanoseconds");
 	MelderInfo_close ();
 	return 1;
 }
diff --git a/fon/Praat_tests_enums.h b/fon/Praat_tests_enums.h
index 78e91b2..bd746ed 100644
--- a/fon/Praat_tests_enums.h
+++ b/fon/Praat_tests_enums.h
@@ -1,11 +1,14 @@
 /* Praat_tests_enums.h */
-/* Paul Boersma, 21 March 2009 */
+/* Paul Boersma, 16 June 2014 */
 
 enums_begin (kPraatTests, 0)
 	enums_add (kPraatTests, 0, _, L"_")
 	enums_add (kPraatTests, 1, CHECK_RANDOM_1009_2009, L"CheckRandom1009_2009")
 	enums_add (kPraatTests, 2, TIME_RANDOM_FRACTION, L"TimeRandomFraction")
-	enums_add (kPraatTests, 3, TIME_SORT, L"TimeSort")
-enums_end (kPraatTests, 3, CHECK_RANDOM_1009_2009)
+	enums_add (kPraatTests, 3, TIME_RANDOM_GAUSS, L"TimeRandomGauss")
+	enums_add (kPraatTests, 4, TIME_SORT, L"TimeSort")
+	enums_add (kPraatTests, 5, TIME_INTEGER, L"TimeInteger")
+	enums_add (kPraatTests, 6, TIME_FLOAT, L"TimeFloat")
+enums_end (kPraatTests, 6, CHECK_RANDOM_1009_2009)
 
 /* End of file Praat_tests_enums.h */
diff --git a/fon/RealTier.cpp b/fon/RealTier.cpp
index 5e3aad4..acd90d5 100644
--- a/fon/RealTier.cpp
+++ b/fon/RealTier.cpp
@@ -1,6 +1,6 @@
 /* RealTier.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -54,7 +54,7 @@ RealPoint RealPoint_create (double time, double value) {
 
 void structRealTier :: v_info () {
 	structFunction :: v_info ();
-	MelderInfo_writeLine (L"Number of points: ", Melder_integer (points -> size));
+	MelderInfo_writeLine (L"Number of points: ", Melder_integer (f_getNumberOfPoints ()));
 	MelderInfo_writeLine (L"Minimum value: ", Melder_double (RealTier_getMinimumValue (this)));
 	MelderInfo_writeLine (L"Maximum value: ", Melder_double (RealTier_getMaximumValue (this)));
 }
@@ -71,16 +71,16 @@ double structRealTier :: v_getFunction1 (long irow, double x) {
 
 void structRealTier :: v_shiftX (double xfrom, double xto) {
 	RealTier_Parent :: v_shiftX (xfrom, xto);
-	for (long i = 1; i <= points -> size; i ++) {
-		RealPoint point = (RealPoint) points -> item [i];
+	for (long i = 1; i <= f_getNumberOfPoints (); i ++) {
+		RealPoint point = f_peekPoint (i);
 		NUMshift (& point -> number, xfrom, xto);
 	}
 }
 
 void structRealTier :: v_scaleX (double xminfrom, double xmaxfrom, double xminto, double xmaxto) {
 	RealTier_Parent :: v_scaleX (xminfrom, xmaxfrom, xminto, xmaxto);
-	for (long i = 1; i <= points -> size; i ++) {
-		RealPoint point = (RealPoint) points -> item [i];
+	for (long i = 1; i <= f_getNumberOfPoints (); i ++) {
+		RealPoint point = f_peekPoint (i);
 		NUMscale (& point -> number, xminfrom, xmaxfrom, xminto, xmaxto);
 	}
 }
@@ -123,34 +123,34 @@ void RealTier_addPoint (RealTier me, double t, double value) {
 }
 
 double RealTier_getValueAtIndex (RealTier me, long i) {
-	if (i < 1 || i > my points -> size) return NUMundefined;
-	return ((RealPoint) my points -> item [i]) -> value;
+	if (i < 1 || i > my f_getNumberOfPoints ()) return NUMundefined;
+	return my f_peekPoint (i) -> value;
 }
 
 double RealTier_getValueAtTime (RealTier me, double t) {
-	long n = my points -> size;
+	long n = my f_getNumberOfPoints ();
 	if (n == 0) return NUMundefined;
-	RealPoint pointRight = (RealPoint) my points -> item [1];
-	if (t <= pointRight -> number) return pointRight -> value;   /* Constant extrapolation. */
-	RealPoint pointLeft = (RealPoint) my points -> item [n];
-	if (t >= pointLeft -> number) return pointLeft -> value;   /* Constant extrapolation. */
+	RealPoint pointRight = my f_peekPoint (1);
+	if (t <= pointRight -> number) return pointRight -> value;   // constant extrapolation
+	RealPoint pointLeft = my f_peekPoint (n);
+	if (t >= pointLeft -> number) return pointLeft -> value;   // constant extrapolation
 	Melder_assert (n >= 2);
 	long ileft = AnyTier_timeToLowIndex (me, t), iright = ileft + 1;
 	Melder_assert (ileft >= 1 && iright <= n);
-	pointLeft = (RealPoint) my points -> item [ileft];
-	pointRight = (RealPoint) my points -> item [iright];
+	pointLeft = my f_peekPoint (ileft);
+	pointRight = my f_peekPoint (iright);
 	double tleft = pointLeft -> number, fleft = pointLeft -> value;
 	double tright = pointRight -> number, fright = pointRight -> value;
-	return t == tright ? fright   /* Be very accurate. */
-		: tleft == tright ? 0.5 * (fleft + fright)   /* Unusual, but possible; no preference. */
-		: fleft + (t - tleft) * (fright - fleft) / (tright - tleft);   /* Linear interpolation. */
+	return t == tright ? fright   // be very accurate
+		: tleft == tright ? 0.5 * (fleft + fright)   // unusual, but possible; no preference
+		: fleft + (t - tleft) * (fright - fleft) / (tright - tleft);   // linear interpolation
 }
 
 double RealTier_getMaximumValue (RealTier me) {
 	double result = NUMundefined;
-	long n = my points -> size;
+	long n = my f_getNumberOfPoints ();
 	for (long i = 1; i <= n; i ++) {
-		RealPoint point = (RealPoint) my points -> item [i];
+		RealPoint point = my f_peekPoint (i);
 		if (result == NUMundefined || point -> value > result)
 			result = point -> value;
 	}
@@ -159,9 +159,9 @@ double RealTier_getMaximumValue (RealTier me) {
 
 double RealTier_getMinimumValue (RealTier me) {
 	double result = NUMundefined;
-	long n = my points -> size;
+	long n = my f_getNumberOfPoints ();
 	for (long i = 1; i <= n; i ++) {
-		RealPoint point = (RealPoint) my points -> item [i];
+		RealPoint point = my f_peekPoint (i);
 		if (result == NUMundefined || point -> value < result)
 			result = point -> value;
 	}
@@ -170,7 +170,7 @@ double RealTier_getMinimumValue (RealTier me) {
 
 double RealTier_getArea (RealTier me, double tmin, double tmax) {
 	long n = my f_getNumberOfPoints (), imin, imax;
-	RealPoint *points = (RealPoint *) my points -> item;
+	RealPoint *points = my f_peekPoints ();
 	if (n == 0) return NUMundefined;
 	if (n == 1) return (tmax - tmin) * points [1] -> value;
 	imin = AnyTier_timeToLowIndex (me, tmin);
@@ -196,17 +196,17 @@ double RealTier_getArea (RealTier me, double tmin, double tmax) {
 }
 
 double RealTier_getMean_curve (RealTier me, double tmin, double tmax) {
-	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   /* Autowindow. */
+	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   // autowindow
 	double area = RealTier_getArea (me, tmin, tmax);
 	if (area == NUMundefined) return NUMundefined;
 	return area / (tmax - tmin);
 }
 
 double RealTier_getStandardDeviation_curve (RealTier me, double tmin, double tmax) {
-	long n = my points -> size, imin, imax;
-	RealPoint *points = (RealPoint *) my points -> item;
+	long n = my f_getNumberOfPoints (), imin, imax;
+	RealPoint *points = my f_peekPoints ();
 	double mean, integral = 0.0;
-	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   /* Autowindow. */
+	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   // autowindow
 	if (n == 0) return NUMundefined;
 	if (n == 1) return 0.0;
 	imin = AnyTier_timeToLowIndex (me, tmin);
@@ -244,10 +244,10 @@ double RealTier_getStandardDeviation_curve (RealTier me, double tmin, double tma
 }
 
 double RealTier_getMean_points (RealTier me, double tmin, double tmax) {
-	long n = my points -> size, imin, imax;
+	long n = my f_getNumberOfPoints (), imin, imax;
 	double sum = 0.0;
-	RealPoint *points = (RealPoint *) my points -> item;
-	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   /* Autowindow. */
+	RealPoint *points = my f_peekPoints ();
+	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   // autowindow
 	n = AnyTier_getWindowPoints (me, tmin, tmax, & imin, & imax);
 	if (n == 0) return NUMundefined;
 	for (long i = imin; i <= imax; i ++)
@@ -256,10 +256,10 @@ double RealTier_getMean_points (RealTier me, double tmin, double tmax) {
 }
 
 double RealTier_getStandardDeviation_points (RealTier me, double tmin, double tmax) {
-	long n = my points -> size, imin, imax;
+	long n = my f_getNumberOfPoints (), imin, imax;
 	double mean, sum = 0.0;
-	RealPoint *points = (RealPoint *) my points -> item;
-	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   /* Autowindow. */
+	RealPoint *points = my f_peekPoints ();
+	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }   // autowindow
 	n = AnyTier_getWindowPoints (me, tmin, tmax, & imin, & imax);
 	if (n < 2) return NUMundefined;
 	mean = RealTier_getMean_points (me, tmin, tmax);
@@ -271,9 +271,8 @@ double RealTier_getStandardDeviation_points (RealTier me, double tmin, double tm
 }
 
 void RealTier_multiplyPart (RealTier me, double tmin, double tmax, double factor) {
-	long ipoint;
-	for (ipoint = 1; ipoint <= my points -> size; ipoint ++) {
-		RealPoint point = (RealPoint) my points -> item [ipoint];
+	for (long ipoint = 1; ipoint <= my f_getNumberOfPoints (); ipoint ++) {
+		RealPoint point = my f_peekPoint (ipoint);
 		double t = point -> number;
 		if (t >= tmin && t <= tmax) {
 			point -> value *= factor;
@@ -286,7 +285,7 @@ void RealTier_draw (RealTier me, Graphics g, double tmin, double tmax, double fm
 {
 	bool drawLines = wcsstr (method, L"lines") || wcsstr (method, L"Lines");
 	bool drawSpeckles = wcsstr (method, L"speckles") || wcsstr (method, L"Speckles");
-	long n = my points -> size, imin, imax, i;
+	long n = my f_getNumberOfPoints (), imin, imax, i;
 	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }
 	Graphics_setWindow (g, tmin, tmax, fmin, fmax);
 	Graphics_setInner (g);
@@ -298,9 +297,9 @@ void RealTier_draw (RealTier me, Graphics g, double tmin, double tmax, double fm
 		double fright = RealTier_getValueAtTime (me, tmax);
 		if (drawLines) Graphics_line (g, tmin, fleft, tmax, fright);
 	} else for (i = imin; i <= imax; i ++) {
-		RealPoint point = (RealPoint) my points -> item [i];
+		RealPoint point = my f_peekPoint (i);
 		double t = point -> number, f = point -> value;
-		if (drawSpeckles) Graphics_fillCircle_mm (g, t, f, 1);
+		if (drawSpeckles) Graphics_speckle (g, t, f);
 		if (drawLines) {
 			if (i == 1)
 				Graphics_line (g, tmin, f, t, f);
@@ -311,7 +310,7 @@ void RealTier_draw (RealTier me, Graphics g, double tmin, double tmax, double fm
 			else if (i == imax)
 				Graphics_line (g, t, f, tmax, RealTier_getValueAtTime (me, tmax));
 			else {
-				RealPoint pointRight = (RealPoint) my points -> item [i + 1];
+				RealPoint pointRight = my f_peekPoint (i + 1);
 				Graphics_line (g, t, f, pointRight -> number, pointRight -> value);
 			}
 		}
@@ -319,7 +318,7 @@ void RealTier_draw (RealTier me, Graphics g, double tmin, double tmax, double fm
 	Graphics_unsetInner (g);
 	if (garnish) {
 		Graphics_drawInnerBox (g);
-		Graphics_textBottom (g, TRUE, L"Time (s)");
+		Graphics_textBottom (g, TRUE, my v_getUnitText (0, 0, 0));
 		Graphics_marksBottom (g, 2, TRUE, TRUE, FALSE);
 		Graphics_marksLeft (g, 2, TRUE, TRUE, FALSE);
 		if (quantity) Graphics_textLeft (g, TRUE, quantity);
@@ -328,11 +327,11 @@ void RealTier_draw (RealTier me, Graphics g, double tmin, double tmax, double fm
 
 TableOfReal RealTier_downto_TableOfReal (RealTier me, const wchar_t *timeLabel, const wchar_t *valueLabel) {
 	try {
-		autoTableOfReal thee = TableOfReal_create (my points -> size, 2);
+		autoTableOfReal thee = TableOfReal_create (my f_getNumberOfPoints (), 2);
 		TableOfReal_setColumnLabel (thee.peek(), 1, timeLabel);
 		TableOfReal_setColumnLabel (thee.peek(), 2, valueLabel);
-		for (long i = 1; i <= my points -> size; i ++) {
-			RealPoint point = (RealPoint) my points -> item [i];
+		for (long i = 1; i <= my f_getNumberOfPoints (); i ++) {
+			RealPoint point = my f_peekPoint (i);
 			thy data [i] [1] = point -> number;
 			thy data [i] [2] = point -> value;
 		}
@@ -345,8 +344,8 @@ TableOfReal RealTier_downto_TableOfReal (RealTier me, const wchar_t *timeLabel,
 void RealTier_interpolateQuadratically (RealTier me, long numberOfPointsPerParabola, int logarithmically) {
 	try {
 		autoRealTier thee = Data_copy (me);
-		for (long ipoint = 1; ipoint < my points -> size; ipoint ++) {
-			RealPoint point1 = (RealPoint) my points -> item [ipoint], point2 = (RealPoint) my points -> item [ipoint + 1];
+		for (long ipoint = 1; ipoint < my f_getNumberOfPoints (); ipoint ++) {
+			RealPoint point1 = my f_peekPoint (ipoint), point2 = my f_peekPoint (ipoint + 1);
 			double time1 = point1 -> number, time2 = point2 -> number, tmid = 0.5 * (time1 + time2);
 			double value1 = point1 -> value, value2 = point2 -> value, valuemid;
 			double timeStep = (tmid - time1) / (numberOfPointsPerParabola + 1);
@@ -385,13 +384,13 @@ void RealTier_interpolateQuadratically (RealTier me, long numberOfPointsPerParab
 
 Table RealTier_downto_Table (RealTier me, const wchar_t *indexText, const wchar_t *timeText, const wchar_t *valueText) {
 	try {
-		autoTable thee = Table_createWithoutColumnNames (my points -> size,
+		autoTable thee = Table_createWithoutColumnNames (my f_getNumberOfPoints (),
 			(indexText != NULL) + (timeText != NULL) + (valueText != NULL));
 		long icol = 0;
 		if (indexText != NULL) Table_setColumnLabel (thee.peek(), ++ icol, indexText);
 		if (timeText  != NULL) Table_setColumnLabel (thee.peek(), ++ icol, timeText);
 		if (valueText != NULL) Table_setColumnLabel (thee.peek(), ++ icol, valueText);
-		for (long ipoint = 1; ipoint <= my points -> size; ipoint ++) {
+		for (long ipoint = 1; ipoint <= my f_getNumberOfPoints (); ipoint ++) {
 			RealPoint point = my f_peekPoint (ipoint);
 			icol = 0;
 			if (indexText != NULL) Table_setNumericValue (thee.peek(), ipoint, ++ icol, ipoint);
@@ -408,7 +407,7 @@ RealTier Vector_to_RealTier (Vector me, long channel, ClassInfo klas) {
 	try {
 		autoRealTier thee = RealTier_createWithClass (my xmin, my xmax, klas);
 		for (long i = 1; i <= my nx; i ++) {
-			RealTier_addPoint (thee.peek(), Sampled_indexToX (me, i), my z [channel] [i]);
+			RealTier_addPoint (thee.peek(), my f_indexToX (i), my z [channel] [i]);
 		}
 		return thee.transfer();
 	} catch (MelderError) {
@@ -468,12 +467,12 @@ void RealTier_formula (RealTier me, const wchar_t *expression, Interpreter inter
 	try {
 		Formula_compile (interpreter, me, expression, kFormula_EXPRESSION_TYPE_NUMERIC, TRUE);
 		if (thee == NULL) thee = me;
-		for (long icol = 1; icol <= my points -> size; icol ++) {
+		for (long icol = 1; icol <= my f_getNumberOfPoints (); icol ++) {
 			struct Formula_Result result;
 			Formula_run (0, icol, & result);
 			if (result. result.numericResult == NUMundefined)
 				Melder_throw ("Cannot put an undefined value into the tier.");
-			((RealPoint) thy points -> item [icol]) -> value = result. result.numericResult;
+			thy f_peekPoint (icol) -> value = result. result.numericResult;
 		}
 	} catch (MelderError) {
 		Melder_throw (me, ": formula not completed.");
@@ -481,8 +480,8 @@ void RealTier_formula (RealTier me, const wchar_t *expression, Interpreter inter
 }
 
 void RealTier_removePointsBelow (RealTier me, double level) {
-	for (long ipoint = my points -> size; ipoint > 0; ipoint --) {
-		RealPoint point = (RealPoint) my points -> item [ipoint];
+	for (long ipoint = my f_getNumberOfPoints (); ipoint > 0; ipoint --) {
+		RealPoint point = my f_peekPoint (ipoint);
 		if (point -> value < level) {
 			AnyTier_removePoint (me, ipoint);
 		}
diff --git a/fon/RealTierEditor.cpp b/fon/RealTierEditor.cpp
index 5461e62..b728755 100644
--- a/fon/RealTierEditor.cpp
+++ b/fon/RealTierEditor.cpp
@@ -1,6 +1,6 @@
 /* RealTierEditor.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -255,7 +255,7 @@ int structRealTierEditor :: v_click (double xWC, double yWC, bool shiftKeyPresse
 	/*
 	 * Perform the default action: move cursor.
 	 */
-	//my startSelection = my endSelection = xWC;
+	//d_startSelection = d_endSelection = xWC;
 	if (d_sound.data) {
 		if (yWC < 1 - SOUND_HEIGHT) {   /* Clicked in tier area? */
 			yWC /= 1 - SOUND_HEIGHT;
@@ -323,11 +323,11 @@ int structRealTierEditor :: v_click (double xWC, double yWC, bool shiftKeyPresse
 	{
 		RealPoint *points = (RealPoint *) pitch -> points -> item;
 		double newTime = points [ifirstSelected] -> number + dt;
-		if (newTime < d_tmin) return 1;   // outside domain
+		if (newTime < our tmin) return 1;   // outside domain
 		if (ifirstSelected > 1 && newTime <= points [ifirstSelected - 1] -> number)
 			return 1;   // past left neighbour
 		newTime = points [ilastSelected] -> number + dt;
-		if (newTime > d_tmax) return 1;   // outside domain
+		if (newTime > our tmax) return 1;   // outside domain
 		if (ilastSelected < pitch -> points -> size && newTime >= points [ilastSelected + 1] -> number)
 			return 1;   // past right neighbour
 	}
diff --git a/fon/RealTier_def.h b/fon/RealTier_def.h
index e5a386a..1357195 100644
--- a/fon/RealTier_def.h
+++ b/fon/RealTier_def.h
@@ -1,6 +1,6 @@
 /* RealTier_def.h
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -46,6 +46,7 @@ oo_DEFINE_CLASS (RealTier, Function)
 			virtual void v_scaleX (double xminfrom, double xmaxfrom, double xminto, double xmaxto);
 		// new functions:
 			long f_getNumberOfPoints () { return points -> size; }
+			RealPoint *f_peekPoints () { return (RealPoint *) points -> item; }
 			RealPoint f_peekPoint (long ipoint) { return (RealPoint) points -> item [ipoint]; }
 	#endif
 
diff --git a/fon/Sampled.cpp b/fon/Sampled.cpp
index 56fb9b2..93d35ee 100644
--- a/fon/Sampled.cpp
+++ b/fon/Sampled.cpp
@@ -1,6 +1,6 @@
 /* Sampled.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -52,35 +52,24 @@ void structSampled :: v_scaleX (double xminfrom, double xmaxfrom, double xminto,
 	dx *= (xmaxto - xminto) / (xmaxfrom - xminfrom);
 }
 
-double Sampled_indexToX (Sampled me, long i) {
-	return my x1 + (i - 1) * my dx;
-}
-
-double Sampled_xToIndex (Sampled me, double x) {
-	return (x - my x1) / my dx + 1;
-}
-
-long Sampled_xToLowIndex (Sampled me, double x) {
-	return (long) floor ((x - my x1) / my dx) + 1;
-}
-
-long Sampled_xToHighIndex (Sampled me, double x) {
-	return (long) ceil ((x - my x1) / my dx) + 1;
-}
-
-long Sampled_xToNearestIndex (Sampled me, double x) {
-	return (long) floor ((x - my x1) / my dx + 1.5);
-}
-
 long Sampled_getWindowSamples (Sampled me, double xmin, double xmax, long *ixmin, long *ixmax) {
 	double rixmin = 1.0 + ceil ((xmin - my x1) / my dx);
-	double rixmax = 1.0 + floor ((xmax - my x1) / my dx);
+	double rixmax = 1.0 + floor ((xmax - my x1) / my dx);   // could be above 32-bit LONG_MAX
 	*ixmin = rixmin < 1.0 ? 1 : (long) rixmin;
 	*ixmax = rixmax > (double) my nx ? my nx : (long) rixmax;
 	if (*ixmin > *ixmax) return 0;
 	return *ixmax - *ixmin + 1;
 }
 
+long structSampled :: f_getWindowSamplesX (double xmin, double xmax, long *ixmin, long *ixmax) {
+	double rixmin = 1.0 + ceil ((xmin - our x1) / our dx);
+	double rixmax = 1.0 + floor ((xmax - our x1) / our dx);   // could be above 32-bit LONG_MAX
+	*ixmin = rixmin < 1.0 ? 1 : (long) rixmin;
+	*ixmax = rixmax > (double) our nx ? our nx : (long) rixmax;
+	if (*ixmin > *ixmax) return 0;
+	return *ixmax - *ixmin + 1;
+}
+
 void Sampled_init (Sampled me, double xmin, double xmax, long nx, double dx, double x1) {
 	my xmin = xmin;
 	my xmax = xmax;
@@ -107,10 +96,10 @@ double Sampled_getValueAtSample (Sampled me, long isamp, long ilevel, int unit)
 	return my v_getValueAtSample (isamp, ilevel, unit);
 }
 
-double Sampled_getValueAtX (Sampled me, double x, long ilevel, int unit, int interpolate) {
+double Sampled_getValueAtX (Sampled me, double x, long ilevel, int unit, bool interpolate) {
 	if (x < my xmin || x > my xmax) return NUMundefined;
 	if (interpolate) {
-		double ireal = Sampled_xToIndex (me, x);
+		double ireal = my f_xToIndex (x);
 		long ileft = floor (ireal), inear, ifar;
 		double phase = ireal - ileft;
 		if (phase < 0.5) {
@@ -142,16 +131,15 @@ long Sampled_countDefinedSamples (Sampled me, long ilevel, int unit) {
 
 double * Sampled_getSortedValues (Sampled me, long ilevel, int unit, long *return_numberOfValues) {
 	long isamp, numberOfDefinedSamples = 0;
-	double *values = NUMvector <double> (1, my nx);
-	if (values == NULL) return NULL;
+	autoNUMvector <double> values (1, my nx);
 	for (isamp = 1; isamp <= my nx; isamp ++) {
 		double value = my v_getValueAtSample (isamp, ilevel, unit);
 		if (value == NUMundefined) continue;
 		values [++ numberOfDefinedSamples] = value;
 	}
-	if (numberOfDefinedSamples) NUMsort_d (numberOfDefinedSamples, values);
+	if (numberOfDefinedSamples) NUMsort_d (numberOfDefinedSamples, values.peek());
 	if (return_numberOfValues) *return_numberOfValues = numberOfDefinedSamples;
-	return values;
+	return values.transfer();
 }
 
 double Sampled_getQuantile (Sampled me, double xmin, double xmax, double quantile, long ilevel, int unit) {
@@ -179,7 +167,7 @@ double Sampled_getQuantile (Sampled me, double xmin, double xmax, double quantil
 }
 
 static void Sampled_getSumAndDefinitionRange
-	(Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate, double *return_sum, double *return_definitionRange)
+	(Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate, double *return_sum, double *return_definitionRange)
 {
 	/*
 		This function computes the area under the linearly interpolated curve between xmin and xmax.
@@ -270,7 +258,7 @@ static void Sampled_getSumAndDefinitionRange
 				}
 			}
 		} else {   /* No interpolation. */
-			double rimin = Sampled_xToIndex (me, xmin), rimax = Sampled_xToIndex (me, xmax);
+			double rimin = my f_xToIndex (xmin), rimax = my f_xToIndex (xmax);
 			if (rimax >= 0.5 && rimin < my nx + 0.5) {
 				imin = rimin < 0.5 ? 0 : (long) floor (rimin + 0.5);
 				imax = rimax >= my nx + 0.5 ? my nx + 1 : (long) floor (rimax + 0.5);
@@ -313,28 +301,28 @@ static void Sampled_getSumAndDefinitionRange
 	if (return_definitionRange) *return_definitionRange = definitionRange;
 }
 
-double Sampled_getMean (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate) {
+double Sampled_getMean (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate) {
 	double sum, definitionRange;
 	Sampled_getSumAndDefinitionRange (me, xmin, xmax, ilevel, unit, interpolate, & sum, & definitionRange);
 	return definitionRange <= 0.0 ? NUMundefined : sum / definitionRange;
 }
 
-double Sampled_getMean_standardUnit (Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, int interpolate) {
+double Sampled_getMean_standardUnit (Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, bool interpolate) {
 	return Function_convertSpecialToStandardUnit (me, Sampled_getMean (me, xmin, xmax, ilevel, averagingUnit, interpolate), ilevel, averagingUnit);
 }
 
-double Sampled_getIntegral (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate) {
+double Sampled_getIntegral (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate) {
 	double sum, definitionRange;
 	Sampled_getSumAndDefinitionRange (me, xmin, xmax, ilevel, unit, interpolate, & sum, & definitionRange);
 	return sum * my dx;
 }
 
-double Sampled_getIntegral_standardUnit (Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, int interpolate) {
+double Sampled_getIntegral_standardUnit (Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, bool interpolate) {
 	return Function_convertSpecialToStandardUnit (me, Sampled_getIntegral (me, xmin, xmax, ilevel, averagingUnit, interpolate), ilevel, averagingUnit);
 }
 
 static void Sampled_getSum2AndDefinitionRange
-	(Sampled me, double xmin, double xmax, long ilevel, int unit, double mean, int interpolate, double *return_sum2, double *return_definitionRange)
+	(Sampled me, double xmin, double xmax, long ilevel, int unit, double mean, bool interpolate, double *return_sum2, double *return_definitionRange)
 {
 	/*
 		This function computes the area under the linearly interpolated squared difference curve between xmin and xmax.
@@ -445,7 +433,7 @@ static void Sampled_getSum2AndDefinitionRange
 				}
 			}
 		} else {   // no interpolation
-			double rimin = Sampled_xToIndex (me, xmin), rimax = Sampled_xToIndex (me, xmax);
+			double rimin = my f_xToIndex (xmin), rimax = my f_xToIndex (xmax);
 			if (rimax >= 0.5 && rimin < my nx + 0.5) {
 				imin = rimin < 0.5 ? 0 : (long) floor (rimin + 0.5);
 				imax = rimax >= my nx + 0.5 ? my nx + 1 : (long) floor (rimax + 0.5);
@@ -496,7 +484,7 @@ static void Sampled_getSum2AndDefinitionRange
 	if (return_definitionRange) *return_definitionRange = definitionRange;
 }
 
-double Sampled_getStandardDeviation (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate) {
+double Sampled_getStandardDeviation (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate) {
 	double sum, sum2, definitionRange;
 	Sampled_getSumAndDefinitionRange (me, xmin, xmax, ilevel, unit, interpolate, & sum, & definitionRange);
 	if (definitionRange < 2.0) return NUMundefined;
@@ -504,11 +492,11 @@ double Sampled_getStandardDeviation (Sampled me, double xmin, double xmax, long
 	return sqrt (sum2 / (definitionRange - 1.0));
 }
 
-double Sampled_getStandardDeviation_standardUnit (Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, int interpolate) {
+double Sampled_getStandardDeviation_standardUnit (Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, bool interpolate) {
 	return Function_convertSpecialToStandardUnit (me, Sampled_getStandardDeviation (me, xmin, xmax, ilevel, averagingUnit, interpolate), ilevel, averagingUnit);
 }
 
-void Sampled_getMinimumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate,
+void Sampled_getMinimumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate,
 	double *return_minimum, double *return_xOfMinimum)
 {
 	long imin, imax, i;
@@ -571,19 +559,19 @@ end:
 	if (return_xOfMinimum) *return_xOfMinimum = xOfMinimum;
 }
 
-double Sampled_getMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate) {
+double Sampled_getMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate) {
 	double minimum;
 	Sampled_getMinimumAndX (me, xmin, xmax, ilevel, unit, interpolate, & minimum, NULL);
 	return minimum;
 }
 
-double Sampled_getXOfMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate) {
+double Sampled_getXOfMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate) {
 	double time;
 	Sampled_getMinimumAndX (me, xmin, xmax, ilevel, unit, interpolate, NULL, & time);
 	return time;
 }
 
-void Sampled_getMaximumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate,
+void Sampled_getMaximumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate,
 	double *return_maximum, double *return_xOfMaximum)
 {
 	long imin, imax, i;
@@ -646,20 +634,20 @@ end:
 	if (return_xOfMaximum) *return_xOfMaximum = xOfMaximum;
 }
 
-double Sampled_getMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate) {
+double Sampled_getMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate) {
 	double maximum;
 	Sampled_getMaximumAndX (me, xmin, xmax, ilevel, unit, interpolate, & maximum, NULL);
 	return maximum;
 }
 
-double Sampled_getXOfMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate) {
+double Sampled_getXOfMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate) {
 	double time;
 	Sampled_getMaximumAndX (me, xmin, xmax, ilevel, unit, interpolate, NULL, & time);
 	return time;
 }
 
 static void Sampled_speckleInside (Sampled me, Graphics g, double xmin, double xmax, double ymin, double ymax,
-	double speckle_mm, long ilevel, int unit)
+	long ilevel, int unit)
 {
 	Function_unidirectionalAutowindow (me, & xmin, & xmax);
 	long ixmin, ixmax;
@@ -673,20 +661,20 @@ static void Sampled_speckleInside (Sampled me, Graphics g, double xmin, double x
 	for (long ix = ixmin; ix <= ixmax; ix ++) {
 		double value = Sampled_getValueAtSample (me, ix, ilevel, unit);
 		if (NUMdefined (value)) {
-			double x = Sampled_indexToX (me, ix);
+			double x = my f_indexToX (ix);
 			if (value >= ymin && value <= ymax) {
-				Graphics_fillCircle_mm (g, x, value, speckle_mm);
+				Graphics_speckle (g, x, value);
 			}
 		}
 	}
 }
 
 void Sampled_drawInside (Sampled me, Graphics g, double xmin, double xmax, double ymin, double ymax,
-	double speckle_mm, long ilevel, int unit)
+	bool speckle, long ilevel, int unit)
 {
 	try {
-		if (speckle_mm != 0.0) {
-			Sampled_speckleInside (me, g, xmin, xmax, ymin, ymax, speckle_mm, ilevel, unit);
+		if (speckle) {
+			Sampled_speckleInside (me, g, xmin, xmax, ymin, ymax, ilevel, unit);
 			return;
 		}
 		Function_unidirectionalAutowindow (me, & xmin, & xmax);
@@ -703,11 +691,11 @@ void Sampled_drawInside (Sampled me, Graphics g, double xmin, double xmax, doubl
 		double previousValue = Sampled_getValueAtSample (me, ixmin - 1, ilevel, unit);
 		if (NUMdefined (previousValue)) {
 			startOfDefinedStretch = ixmin - 1;
-			xarray [ixmin - 1] = Sampled_indexToX (me, ixmin - 1);
+			xarray [ixmin - 1] = my f_indexToX (ixmin - 1);
 			yarray [ixmin - 1] = previousValue;
 		}
 		for (long ix = ixmin; ix <= ixmax; ix ++) {
-			double x = Sampled_indexToX (me, ix), value = Sampled_getValueAtSample (me, ix, ilevel, unit);
+			double x = my f_indexToX (ix), value = Sampled_getValueAtSample (me, ix, ilevel, unit);
 			if (NUMdefined (value)) {
 				if (NUMdefined (previousValue)) {
 					xarray [ix] = x;
@@ -736,7 +724,7 @@ void Sampled_drawInside (Sampled me, Graphics g, double xmin, double xmax, doubl
 			previousValue = value;
 		}
 		if (startOfDefinedStretch > -1) {
-			double x = Sampled_indexToX (me, ixmax + 1), value = Sampled_getValueAtSample (me, ixmax + 1, ilevel, unit);
+			double x = my f_indexToX (ixmax + 1), value = Sampled_getValueAtSample (me, ixmax + 1, ilevel, unit);
 			Melder_assert (NUMdefined (previousValue));
 			if (NUMdefined (value)) {
 				xarray [ixmax + 1] = x;
diff --git a/fon/Sampled.h b/fon/Sampled.h
index acbb71f..8446244 100644
--- a/fon/Sampled.h
+++ b/fon/Sampled.h
@@ -2,7 +2,7 @@
 #define _Sampled_h_
 /* Sampled.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -31,15 +31,12 @@ oo_CLASS_CREATE (Sampled, Function);
 /* The first sample point is at x1, the second at x1 + dx, */
 /* and the last at x1 + (nx - 1) * dx. */
 
-double Sampled_indexToX (Sampled me, long i);
-
-double Sampled_xToIndex (Sampled me, double x);
-
-long Sampled_xToLowIndex (Sampled me, double x);
-
-long Sampled_xToHighIndex (Sampled me, double x);
-
-long Sampled_xToNearestIndex (Sampled me, double x);
+static inline double Sampled_indexToX (Sampled me, long   i) { return my f_indexToX (i); }
+static inline double Sampled_indexToX (Sampled me, double i) { return my f_indexToX (i); }
+static inline double Sampled_xToIndex (Sampled me, double x) { return my f_xToIndex (x); }
+static inline long Sampled_xToLowIndex (Sampled me, double x) { return my f_xToLowIndex (x); }
+static inline long Sampled_xToHighIndex (Sampled me, double x) { return my f_xToHighIndex (x); }
+static inline long Sampled_xToNearestIndex (Sampled me, double x) { return my f_xToNearestIndex (x); }
 
 long Sampled_getWindowSamples (Sampled me, double xmin, double xmax, long *ixmin, long *ixmax);
 
@@ -78,36 +75,36 @@ void Sampled_shortTermAnalysis (Sampled me, double windowDuration, double timeSt
 */
 
 double Sampled_getValueAtSample (Sampled me, long isamp, long ilevel, int unit);
-double Sampled_getValueAtX (Sampled me, double x, long ilevel, int unit, int interpolate);
+double Sampled_getValueAtX (Sampled me, double x, long ilevel, int unit, bool interpolate);
 long Sampled_countDefinedSamples (Sampled me, long ilevel, int unit);
 double * Sampled_getSortedValues (Sampled me, long ilevel, int unit, long *numberOfValues);
 
 double Sampled_getQuantile
 	(Sampled me, double xmin, double xmax, double quantile, long ilevel, int unit);
 double Sampled_getMean
-	(Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate);
+	(Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate);
 double Sampled_getMean_standardUnit
-	(Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, int interpolate);
+	(Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, bool interpolate);
 double Sampled_getIntegral
-	(Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate);
+	(Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate);
 double Sampled_getIntegral_standardUnit
-	(Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, int interpolate);
+	(Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, bool interpolate);
 double Sampled_getStandardDeviation
-	(Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate);
+	(Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate);
 double Sampled_getStandardDeviation_standardUnit
-	(Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, int interpolate);
+	(Sampled me, double xmin, double xmax, long ilevel, int averagingUnit, bool interpolate);
 
-void Sampled_getMinimumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate,
+void Sampled_getMinimumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate,
 	double *return_minimum, double *return_xOfMinimum);
-double Sampled_getMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate);
-double Sampled_getXOfMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate);
-void Sampled_getMaximumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate,
+double Sampled_getMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate);
+double Sampled_getXOfMinimum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate);
+void Sampled_getMaximumAndX (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate,
 	double *return_maximum, double *return_xOfMaximum);
-double Sampled_getMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate);
-double Sampled_getXOfMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, int interpolate);
+double Sampled_getMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate);
+double Sampled_getXOfMaximum (Sampled me, double xmin, double xmax, long ilevel, int unit, bool interpolate);
 
 void Sampled_drawInside
-	(Sampled me, Graphics g, double xmin, double xmax, double ymin, double ymax, double speckle_mm, long ilevel, int unit);
+	(Sampled me, Graphics g, double xmin, double xmax, double ymin, double ymax, bool speckle, long ilevel, int unit);
 
 /* End of file Sampled.h */
 #endif
diff --git a/fon/SampledXY.cpp b/fon/SampledXY.cpp
index 9890dd8..bc5ae20 100644
--- a/fon/SampledXY.cpp
+++ b/fon/SampledXY.cpp
@@ -1,6 +1,6 @@
 /* SampledXY.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -41,22 +41,22 @@
 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)
+	(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;
+	Sampled_init (this, xmin_, xmax_, nx_, dx_, x1_);
+	our ymin = ymin_;
+	our ymax = ymax_;
+	our ny = ny_;
+	our dy = dy_;
+	our 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;
+long structSampledXY :: f_getWindowSamplesY (double ymin_, double ymax_, long *iymin, long *iymax) {
+	double riymin = 1.0 + ceil ((ymin_ - our y1) / our dy);
+	double riymax = 1.0 + floor ((ymax_ - our y1) / our dy);   // could be above 32-bit LONG_MAX
+	*iymin = riymin < 1.0 ? 1 : (long) riymin;
+	*iymax = riymax > (double) our ny ? our ny : (long) riymax;
 	if (*iymin > *iymax) return 0;
 	return *iymax - *iymin + 1;
 }
diff --git a/fon/SampledXY.h b/fon/SampledXY.h
index 2ee0597..53854aa 100644
--- a/fon/SampledXY.h
+++ b/fon/SampledXY.h
@@ -2,7 +2,7 @@
 #define _SampledXY_h_
 /* SampledXY.h
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -24,39 +24,5 @@
 #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/SampledXY_def.h b/fon/SampledXY_def.h
index 84ad0a5..da11903 100644
--- a/fon/SampledXY_def.h
+++ b/fon/SampledXY_def.h
@@ -1,6 +1,6 @@
 /* SampledXY_def.h
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -41,8 +41,9 @@ oo_DEFINE_CLASS (SampledXY, Sampled)
 		// 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; }
+			double f_indexToY (long   index) { return y1 + (index - 1  ) * dy; }
+			double f_indexToY (double index) { return y1 + (index - 1.0) * dy; }
+			double f_yToIndex (double y) { return (y - y1) / dy + 1.0; }
 			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)); }
diff --git a/fon/Sampled_def.h b/fon/Sampled_def.h
index 19d0a3e..3eaea82 100644
--- a/fon/Sampled_def.h
+++ b/fon/Sampled_def.h
@@ -1,6 +1,6 @@
 /* Sampled_def.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -35,6 +35,14 @@ oo_DEFINE_CLASS (Sampled, Function)
 		// new methods:
 			virtual double v_getValueAtSample (long isamp, long ilevel, int unit)
 				{ (void) isamp; (void) ilevel; (void) unit; return NUMundefined; }
+		// functions:
+			inline double f_indexToX (long   index) { return x1 + (index - 1  ) * dx; }
+			inline double f_indexToX (double index) { return x1 + (index - 1.0) * dx; }
+			double f_xToIndex (double x) { return (x - x1) / dx + 1.0; }
+			long f_xToLowIndex     (double x) { return (long) floor (f_xToIndex (x)); }
+			long f_xToHighIndex    (double x) { return (long) ceil  (f_xToIndex (x)); }
+			long f_xToNearestIndex (double x) { return (long) round (f_xToIndex (x)); }
+			long f_getWindowSamplesX (double xmin, double xmax, long *ixmin, long *ixmax);
 	#endif
 
 oo_END_CLASS (Sampled)
diff --git a/fon/Sound.cpp b/fon/Sound.cpp
index 8175246..6485952 100644
--- a/fon/Sound.cpp
+++ b/fon/Sound.cpp
@@ -1,6 +1,6 @@
 /* Sound.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -399,8 +399,8 @@ Sound Sound_resample (Sound me, double samplingFrequency, long precision) {
 			double *to = thy z [channel];
 			if (precision <= 1) {
 				for (long i = 1; i <= numberOfSamples; i ++) {
-					double x = thy x1 + (i - 1) * thy dx;   /* Sampled_indexToX (thee, i); */
-					double index = (x - my x1) / my dx + 1;   /* Sampled_xToIndex (me, x); */
+					double x = thy f_indexToX (i);
+					double index = my f_xToIndex (x);
 					long leftSample = floor (index);
 					double fraction = index - leftSample;
 					to [i] = leftSample < 1 || leftSample >= my nx ? 0.0 :
@@ -408,8 +408,8 @@ Sound Sound_resample (Sound me, double samplingFrequency, long precision) {
 				}
 			} else {
 				for (long i = 1; i <= numberOfSamples; i ++) {
-					double x = thy x1 + (i - 1) * thy dx;   /* Sampled_indexToX (thee, i); */
-					double index = (x - my x1) / my dx + 1;   /* Sampled_xToIndex (me, x); */
+					double x = thy f_indexToX (i);
+					double index = my f_xToIndex (x);
 					to [i] = NUM_interpolate_sinc (my z [channel], my nx, index, precision);
 				}
 			}
@@ -775,7 +775,7 @@ void Sound_draw (Sound me, Graphics g,
 			maximum + (channel - 1) * (maximum - minimum));
 		if (wcsstr (method, L"bars") || wcsstr (method, L"Bars")) {
 			for (long ix = ixmin; ix <= ixmax; ix ++) {
-				double x = Sampled_indexToX (me, ix);
+				double x = my f_indexToX (ix);
 				double y = my z [channel] [ix];
 				double left = x - 0.5 * my dx, right = x + 0.5 * my dx;
 				if (y > maximum) y = maximum;
@@ -787,13 +787,13 @@ void Sound_draw (Sound me, Graphics g,
 			}
 		} else if (wcsstr (method, L"poles") || wcsstr (method, L"Poles")) {
 			for (long ix = ixmin; ix <= ixmax; ix ++) {
-				double x = Sampled_indexToX (me, ix);
+				double x = my f_indexToX (ix);
 				Graphics_line (g, x, 0, x, my z [channel] [ix]);
 			}
 		} else if (wcsstr (method, L"speckles") || wcsstr (method, L"Speckles")) {
 			for (long ix = ixmin; ix <= ixmax; ix ++) {
-				double x = Sampled_indexToX (me, ix);
-				Graphics_fillCircle_mm (g, x, my z [channel] [ix], 1.0);
+				double x = my f_indexToX (ix);
+				Graphics_speckle (g, x, my z [channel] [ix]);
 			}
 		} else {
 			/*
@@ -831,7 +831,7 @@ static double interpolate (Sound me, long i1, long channel)
 /* Precondition: my z [1] [i1] != my z [1] [i1 + 1]; */
 {
 	long i2 = i1 + 1;
-	double x1 = Sampled_indexToX (me, i1), x2 = Sampled_indexToX (me, i2);
+	double x1 = my f_indexToX (i1), x2 = my f_indexToX (i2);
 	double y1 = my z [channel] [i1], y2 = my z [channel] [i2];
 	return x1 + (x2 - x1) * y1 / (y1 - y2);   /* Linear. */
 }
@@ -944,7 +944,7 @@ Sound Sound_createFromToneComplex (double startingTime, double endTime, double s
 			1 / sampleRate, startingTime + 0.5 / sampleRate);
 		double *amplitude = my z [1];
 		for (long isamp = 1; isamp <= my nx; isamp ++) {
-			double value = 0.0, t = Sampled_indexToX (me.peek(), isamp);
+			double value = 0.0, t = my f_indexToX (isamp);
 			double omegaStepT = omegaStep * t, firstOmegaT = firstOmega * t;
 			if (phase == Sound_TONE_COMPLEX_SINE)
 				for (long icomp = 1; icomp <= numberOfComponents; icomp ++)
diff --git a/fon/Sound.h b/fon/Sound.h
index c6097b8..5e59677 100644
--- a/fon/Sound.h
+++ b/fon/Sound.h
@@ -2,7 +2,7 @@
 #define _Sound_h_
 /* Sound.h
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2014 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
@@ -215,7 +215,7 @@ Sound Matrix_to_Sound_mono (Matrix me, long row);
 
 extern Sound Sound_clipboard;
 
-/********** Sound_audio.c **********/
+/********** Sound_audio.cpp **********/
 
 Sound Sound_recordFixedTime (int inputSource,
 	double gain, double balance, double samplingFrequency, double duration);
@@ -293,7 +293,7 @@ void Sound_play (Sound me,
 	int (*playCallback) (void *playClosure, int phase, double tmin, double tmax, double t), void *playClosure);
 	/* The same as Sound_playPart (me, my xmin, my xmax, playCallback, playClosure); */
 
-/********** Sound_files.c **********/
+/********** Sound_files.cpp **********/
 
 /* To avoid clipping, keep the absolute amplitude below 1.000. */
 /* All are mono or stereo PCM. */
@@ -330,7 +330,7 @@ void Sound_writeToRawSoundFile (Sound me, MelderFile file, int encoding);
 	'me' must exist
 */
 
-/********** Sound_enhance.c **********/
+/********** Sound_enhance.cpp **********/
 
 Sound Sound_lengthen_overlapAdd (Sound me, double fmin, double fmax, double factor);
 Sound Sound_deepenBandModulation (Sound me, double enhancement_dB,
diff --git a/fon/SoundEditor.cpp b/fon/SoundEditor.cpp
index 2a76f16..9f74732 100644
--- a/fon/SoundEditor.cpp
+++ b/fon/SoundEditor.cpp
@@ -1,6 +1,6 @@
 /* SoundEditor.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma, 2007 Erez Volk (FLAC support)
+ * Copyright (C) 1992-2012,2013,2014 Paul Boersma, 2007 Erez Volk (FLAC support)
  *
  * 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
@@ -105,8 +105,8 @@ static void menu_cb_Cut (EDITOR_ARGS) {
 
 			/* Start updating the markers of the FunctionEditor, respecting the invariants. */
 
-			my d_tmin = sound -> xmin;
-			my d_tmax = sound -> xmax;
+			my tmin = sound -> xmin;
+			my tmax = sound -> xmax;
 
 			/* Collapse the selection, */
 			/* so that the Cut operation can immediately be undone by a Paste. */
@@ -129,16 +129,16 @@ static void menu_cb_Cut (EDITOR_ARGS) {
 				else   /* Cut overlaps entire window: centre. */
 					my d_startWindow = my d_startSelection - 0.5 * windowLength;
 				my d_endWindow = my d_startWindow + windowLength;   // first try
-				if (my d_endWindow > my d_tmax) {
-					my d_startWindow -= my d_endWindow - my d_tmax;   // second try
-					if (my d_startWindow < my d_tmin)
-						my d_startWindow = my d_tmin;   // third try
-					my d_endWindow = my d_tmax;   // second try
-				} else if (my d_startWindow < my d_tmin) {
-					my d_endWindow -= my d_startWindow - my d_tmin;   // second try
-					if (my d_endWindow > my d_tmax)
-						my d_endWindow = my d_tmax;   // third try
-					my d_startWindow = my d_tmin;   // second try
+				if (my d_endWindow > my tmax) {
+					my d_startWindow -= my d_endWindow - my tmax;   // second try
+					if (my d_startWindow < my tmin)
+						my d_startWindow = my tmin;   // third try
+					my d_endWindow = my tmax;   // second try
+				} else if (my d_startWindow < my tmin) {
+					my d_endWindow -= my d_startWindow - my tmin;   // second try
+					if (my d_endWindow > my tmax)
+						my d_endWindow = my tmax;   // third try
+					my d_startWindow = my tmin;   // second try
 				}
 			}
 
@@ -207,8 +207,8 @@ static void menu_cb_Paste (EDITOR_ARGS) {
 
 	/* Start updating the markers of the FunctionEditor, respecting the invariants. */
 
-	my d_tmin = sound -> xmin;
-	my d_tmax = sound -> xmax;
+	my tmin = sound -> xmin;
+	my tmax = sound -> xmax;
 	my d_startSelection = leftSample * sound -> dx;
 	my d_endSelection = (leftSample + Sound_clipboard -> nx) * sound -> dx;
 
@@ -440,7 +440,7 @@ void structSoundEditor :: f_init (const wchar_t *title, Sampled data) {
 	structTimeSoundAnalysisEditor :: f_init (title, data, data, false);
 	if (d_longSound.data && d_endWindow - d_startWindow > 30.0) {
 		d_endWindow = d_startWindow + 30.0;
-		if (d_startWindow == d_tmin)
+		if (our d_startWindow == our tmin)
 			d_startSelection = d_endSelection = 0.5 * (d_startWindow + d_endWindow);
 		FunctionEditor_marksChanged (this, false);
 	}
diff --git a/fon/SoundRecorder.cpp b/fon/SoundRecorder.cpp
index 5029c28..350afab 100644
--- a/fon/SoundRecorder.cpp
+++ b/fon/SoundRecorder.cpp
@@ -1,6 +1,6 @@
 /* SoundRecorder.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -228,7 +228,9 @@ static void stopRecording (SoundRecorder me) {
 void structSoundRecorder :: v_destroy () {
 	stopRecording (this);   // must occur before freeing my buffer
 	MelderAudio_stopPlaying (MelderAudio_IMPLICIT);   // must also occur before freeing my buffer
-	#if gtk
+	#if cocoa
+		if (d_cocoaTimer) CFRunLoopTimerInvalidate (d_cocoaTimer);
+	#elif gtk
 		g_idle_remove_by_data (this);
 	#elif motif
 		if (workProcId) XtRemoveWorkProc (workProcId);
@@ -257,8 +259,8 @@ void structSoundRecorder :: v_destroy () {
 static void showMaximum (SoundRecorder me, int channel, double maximum) {
 	maximum /= 32768.0;
 	Graphics_setWindow (my graphics,
-		my numberOfChannels == 1 || channel == 1 ? -0.1 : -2.1,
-		my numberOfChannels == 1 || channel == 2 ? 1.1 : 3.1,
+		my numberOfChannels == 1 || channel == 1 ? 0.0 : -1.0,
+		my numberOfChannels == 1 || channel == 2 ? 1.0 : 2.0,
 		-0.1, 1.1);
 	Graphics_setGrey (my graphics, 0.9);
 	Graphics_fillRectangle (my graphics, 0.0, 1.0, maximum, 1.0);
@@ -344,9 +346,11 @@ static void showMeter (SoundRecorder me, short *buffer, long nsamp) {
 		Graphics_setColour (my graphics, Graphics_BLACK);
 		Graphics_fillCircle_mm (my graphics, centreOfGravity, intensity, 3.0);
 	}
+	Graphics_flushWs (my graphics);
 }
 
 static bool tooManySamplesInBufferToReturnToGui (SoundRecorder me) {
+	(void) me;
 	return false;
 }
 
@@ -355,7 +359,9 @@ static long getMyNsamp (SoundRecorder me) {
 	return nsamp;
 }
 
-#if gtk
+#if cocoa
+static void workProc (CFRunLoopTimerRef timer, void *void_me) {
+#elif gtk
 static gboolean workProc (void *void_me) {
 #else
 static bool workProc (void *void_me) {
@@ -472,15 +478,16 @@ static bool workProc (void *void_me) {
 				showMeter (me, NULL, 0);
 			}
 		}
-		#if gtk
-			return true;
-		#else
-			return false;
-		#endif
 	} catch (MelderError) {
 		Melder_flushError (NULL);
-		return false;
 	}
+	#if cocoa
+		return;
+	#elif gtk
+		return true;
+	#else
+		return false;
+	#endif
 }
 
 static int portaudioStreamCallback (
@@ -1165,7 +1172,12 @@ event. width  = my meter -> f_getWidth  ();
 event. height = my meter -> f_getHeight ();
 gui_drawingarea_cb_resize (me.peek(), & event);
 
-		#if gtk
+		#if cocoa
+			CFRunLoopTimerContext context = { 0, me.peek(), NULL, NULL, NULL };
+			my d_cocoaTimer = CFRunLoopTimerCreate (NULL, CFAbsoluteTimeGetCurrent () + 0.02,
+				0.02, 0, 0, workProc, & context);
+			CFRunLoopAddTimer (CFRunLoopGetCurrent (), my d_cocoaTimer, kCFRunLoopCommonModes);
+		#elif gtk
 			g_idle_add (workProc, me.peek());
 		#elif motif
 			my workProcId = GuiAddWorkProc (workProc, me.peek());
diff --git a/fon/SoundRecorder.h b/fon/SoundRecorder.h
index f548c61..1bdb915 100644
--- a/fon/SoundRecorder.h
+++ b/fon/SoundRecorder.h
@@ -95,7 +95,9 @@ Thing_define (SoundRecorder, Editor) {
 		const PaDeviceInfo *deviceInfos [1+SoundRecorder_IDEVICE_MAX];
 		PaDeviceIndex deviceIndices [1+SoundRecorder_IDEVICE_MAX];
 		PaStream *portaudioStream;
-		#if motif
+		#if cocoa
+			CFRunLoopTimerRef d_cocoaTimer;
+		#elif motif
 			XtWorkProcId workProcId;
 		#endif
 		#if defined (_WIN32)
diff --git a/fon/Sound_and_Spectrogram.cpp b/fon/Sound_and_Spectrogram.cpp
index a87fdf2..967e80e 100644
--- a/fon/Sound_and_Spectrogram.cpp
+++ b/fon/Sound_and_Spectrogram.cpp
@@ -1,6 +1,6 @@
 /* Sound_and_Spectrogram.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -135,8 +135,8 @@ Spectrogram Sound_to_Spectrogram (Sound me, double effectiveAnalysisWidth, doubl
 		double oneByBinWidth = 1.0 / windowssq / binWidth_samples;
 
 		for (long iframe = 1; iframe <= numberOfTimes; iframe ++) {
-			double t = Sampled_indexToX (thee.peek(), iframe);
-			long leftSample = Sampled_xToLowIndex (me, t), rightSample = leftSample + 1;
+			double t = thy f_indexToX (iframe);
+			long leftSample = my f_xToLowIndex (t), rightSample = leftSample + 1;
 			long startSample = rightSample - halfnsamp_window;
 			long endSample = leftSample + halfnsamp_window;
 			Melder_assert (startSample >= 1);
@@ -189,8 +189,8 @@ Sound Spectrogram_to_Sound (Spectrogram me, double fsamp) {
 		if (n < 0) return NULL;
 		autoSound thee = Sound_create (1, my xmin, my xmax, n, dt, 0.5 * dt);
 		for (long i = 1; i <= n; i ++) {
-			double t = Sampled_indexToX (thee.peek(), i);
-			double rframe = Sampled_xToIndex (me, t), phase, value = 0.0;
+			double t = thy f_indexToX (i);
+			double rframe = my f_xToIndex (t), phase, value = 0.0;
 			long leftFrame, rightFrame;
 			if (rframe < 1 || rframe >= my nx) continue;
 			leftFrame = floor (rframe), rightFrame = leftFrame + 1, phase = rframe - leftFrame;
diff --git a/fon/Sound_audio.cpp b/fon/Sound_audio.cpp
index f5f85bc..544c864 100644
--- a/fon/Sound_audio.cpp
+++ b/fon/Sound_audio.cpp
@@ -538,22 +538,29 @@ void Sound_playPart (Sound me, double tmin, double tmax,
 			thy closure = closure;
 			thy silenceBefore = (long) (ifsamp * MelderAudio_getOutputSilenceBefore ());
 			thy silenceAfter = (long) (ifsamp * MelderAudio_getOutputSilenceAfter ());
-			int numberOfChannels = my ny > 1 ? 2 : 1;
+			int numberOfChannels = my ny;
 			NUMvector_free (thy buffer, 1);   // just in case
 			thy buffer = NUMvector <short> (1, (i2 - i1 + 1 + thy silenceBefore + thy silenceAfter) * numberOfChannels);
 			thy i1 = i1;
 			thy i2 = i2;
 			short *to = thy buffer + thy silenceBefore * numberOfChannels;
-			if (numberOfChannels == 2) {
+			if (numberOfChannels > 2) {
 				for (long i = i1; i <= i2; i ++) {
-					long valueLeft = (long) floor (fromLeft [i] * 32768.0 + 0.5);
+					for (long chan = 1; chan <= my ny; chan ++) {
+						long value = (long) round (my z [chan] [i] * 32768.0);
+						* ++ to = value < -32768 ? -32768 : value > 32767 ? 32767 : value;
+					}
+				}
+			} else if (numberOfChannels == 2) {
+				for (long i = i1; i <= i2; i ++) {
+					long valueLeft = (long) round (fromLeft [i] * 32768.0);
 					* ++ to = valueLeft < -32768 ? -32768 : valueLeft > 32767 ? 32767 : valueLeft;
-					long valueRight = (long) floor (fromRight [i] * 32768.0 + 0.5);
+					long valueRight = (long) round (fromRight [i] * 32768.0);
 					* ++ to = valueRight < -32768 ? -32768 : valueRight > 32767 ? 32767 : valueRight;
 				}
 			} else {
 				for (long i = i1; i <= i2; i ++) {
-					long value = (long) floor (fromLeft [i] * 32768.0 + 0.5);
+					long value = (long) round (fromLeft [i] * 32768.0);
 					* ++ to = value < -32768 ? -32768 : value > 32767 ? 32767 : value;
 				}
 			}
diff --git a/fon/Sound_files.cpp b/fon/Sound_files.cpp
index ed61ca9..f471a7d 100644
--- a/fon/Sound_files.cpp
+++ b/fon/Sound_files.cpp
@@ -1,6 +1,6 @@
 /* Sound_files.cpp
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma & David Weenink
+ * Copyright (C) 1992-2011,2012,2014 Paul Boersma & David Weenink
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -321,23 +321,23 @@ void Sound_writeToKayFile (Sound me, MelderFile file) {
 
 		binputi4LE (floor (1 / my dx + 0.5), file -> filePointer);   // sampling frequency
 		binputi4LE (my nx, file -> filePointer);   // number of samples
-		int maximum = 0;
+		int maximumA = 0;
 		for (long i = 1; i <= my nx; i ++) {
 			long value = floor (my z [1] [i] * 32768 + 0.5);
-			if (value < - maximum) maximum = - value;
-			if (value > maximum) maximum = value;
+			if (value < - maximumA) maximumA = - value;
+			if (value > maximumA) maximumA = value;
 		}
-		binputi2LE (maximum, file -> filePointer);   // absolute maximum window A
+		binputi2LE (maximumA, file -> filePointer);   // absolute maximum window A
 		if (my ny == 1) {
 			binputi2LE (-1, file -> filePointer);
 		} else {
-			int maximum = 0;
+			int maximumB = 0;
 			for (long i = 1; i <= my nx; i ++) {
 				long value = floor (my z [2] [i] * 32768 + 0.5);
-				if (value < - maximum) maximum = - value;
-				if (value > maximum) maximum = value;
+				if (value < - maximumB) maximumB = - value;
+				if (value > maximumB) maximumB = value;
 			}
-			binputi2LE (maximum, file -> filePointer);   // absolute maximum window B
+			binputi2LE (maximumB, file -> filePointer);   // absolute maximum window B
 		}
 		fwrite ("SDA_", 1, 4, file -> filePointer);
 		binputi4LE (my nx * 2, file -> filePointer);   // chunk size
diff --git a/fon/Sound_to_Cochleagram.cpp b/fon/Sound_to_Cochleagram.cpp
index 821ee61..6b6ee83 100644
--- a/fon/Sound_to_Cochleagram.cpp
+++ b/fon/Sound_to_Cochleagram.cpp
@@ -1,6 +1,6 @@
 /* Sound_to_Cochleagram.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -48,7 +48,7 @@ Cochleagram Sound_to_Cochleagram (Sound me, double dt, double df, double dt_wind
 		autoCochleagram thee = Cochleagram_create (my xmin, my xmax, nFrames, dt, t1, df, nf);
 		autoSound window = Sound_createSimple (1, nsamp_window * my dx, 1.0 / my dx);
 		for (long iframe = 1; iframe <= nFrames; iframe ++) {
-			double t = Sampled_indexToX (thee.peek(), iframe);
+			double t = thy f_indexToX (iframe);
 			long leftSample = Sampled_xToLowIndex (me, t);
 			long rightSample = leftSample + 1;
 			long startSample = rightSample - halfnsamp_window;
diff --git a/fon/Sound_to_Formant.cpp b/fon/Sound_to_Formant.cpp
index 3cf4393..90adcb3 100644
--- a/fon/Sound_to_Formant.cpp
+++ b/fon/Sound_to_Formant.cpp
@@ -1,6 +1,6 @@
 /* Sound_to_Formant.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -293,8 +293,8 @@ static Formant Sound_to_Formant_any_inline (Sound me, double dt_in, int numberOf
 	}
 
 	for (long iframe = 1; iframe <= nFrames; iframe ++) {
-		double t = Sampled_indexToX (thee.peek(), iframe);
-		long leftSample = Sampled_xToLowIndex (me, t);
+		double t = thy f_indexToX (iframe);
+		long leftSample = my f_xToLowIndex (t);
 		long rightSample = leftSample + 1;
 		long startSample = rightSample - halfnsamp_window;
 		long endSample = leftSample + halfnsamp_window;
diff --git a/fon/Sound_to_Intensity.cpp b/fon/Sound_to_Intensity.cpp
index 65244af..0d21e27 100644
--- a/fon/Sound_to_Intensity.cpp
+++ b/fon/Sound_to_Intensity.cpp
@@ -71,8 +71,8 @@ static Intensity Sound_to_Intensity_ (Sound me, double minimumPitch, double time
 		}
 		autoIntensity thee = Intensity_create (my xmin, my xmax, numberOfFrames, timeStep, thyFirstTime);
 		for (long iframe = 1; iframe <= numberOfFrames; iframe ++) {
-			double midTime = Sampled_indexToX (thee.peek(), iframe);
-			long midSample = Sampled_xToNearestIndex (me, midTime);
+			double midTime = thy f_indexToX (iframe);
+			long midSample = my f_xToNearestIndex (midTime);
 			long leftSample = midSample - halfWindowSamples, rightSample = midSample + halfWindowSamples;
 			double sumxw = 0.0, sumw = 0.0, intensity;
 			if (leftSample < 1) leftSample = 1;
diff --git a/fon/Sound_to_Pitch kopie.cpp b/fon/Sound_to_Pitch kopie.cpp
new file mode 100644
index 0000000..8724584
--- /dev/null
+++ b/fon/Sound_to_Pitch kopie.cpp	
@@ -0,0 +1,566 @@
+/* Sound_to_Pitch.cpp
+ *
+ * Copyright (C) 1992-2011,2014 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.
+ */
+
+/*
+ * pb 2002/07/16 GPL
+ * pb 2002/10/11 removed some assertions
+ * pb 2003/05/20 default time step is four times oversampling
+ * pb 2003/07/02 checks on NUMrealft
+ * pb 2004/05/10 better error messages
+ * pb 2004/10/18 auto maxnCandidates
+ * pb 2004/10/18 use of constant FFT tables speeds up AC method by a factor of 1.9
+ * pb 2006/12/31 compatible with stereo sounds
+ * pb 2007/01/30 loop split for stereo speeds up CC method by a factor of 6
+ * pb 2008/01/19 double
+ * pb 2010/12/07 compatible with sounds with any number of channels
+ * pb 2011/03/08 C++
+ * pb 2014/05/23 threads
+ */
+
+#include "Sound_to_Pitch.h"
+#include "NUM2.h"
+#include <thread>
+
+#define AC_HANNING  0
+#define AC_GAUSS  1
+#define FCC_NORMAL  2
+#define FCC_ACCURATE  3
+
+static void Sound_into_PitchFrame (Sound me, Pitch_Frame pitchFrame, double t,
+	double minimumPitch, int maxnCandidates, int method, double voicingThreshold, double octaveCost,
+	NUMfft_Table fftTable, double dt_window, long nsamp_window, long halfnsamp_window,
+	long maximumLag, long nsampFFT, long nsamp_period, long halfnsamp_period,
+	long brent_ixmax, long brent_depth, double globalPeak,
+	double **frame, double *ac, double *window, double *windowR,
+	double *r, long *imax, double *localMean)
+{
+	double localPeak;
+	long leftSample = Sampled_xToLowIndex (me, t), rightSample = leftSample + 1;
+	long startSample, endSample;
+
+	for (long channel = 1; channel <= my ny; channel ++) {
+		/*
+		 * Compute the local mean; look one longest period to both sides.
+		 */
+		startSample = rightSample - nsamp_period;
+		endSample = leftSample + nsamp_period;
+		Melder_assert (startSample >= 1);
+		Melder_assert (endSample <= my nx);
+		localMean [channel] = 0.0;
+		for (long i = startSample; i <= endSample; i ++) {
+			localMean [channel] += my z [channel] [i];
+		}
+		localMean [channel] /= 2 * nsamp_period;
+
+		/*
+		 * Copy a window to a frame and subtract the local mean.
+		 * We are going to kill the DC component before windowing.
+		 */
+		startSample = rightSample - halfnsamp_window;
+		endSample = leftSample + halfnsamp_window;
+		Melder_assert (startSample >= 1);
+		Melder_assert (endSample <= my nx);
+		if (method < FCC_NORMAL) {
+			for (long j = 1, i = startSample; j <= nsamp_window; j ++)
+				frame [channel] [j] = (my z [channel] [i ++] - localMean [channel]) * window [j];
+			for (long j = nsamp_window + 1; j <= nsampFFT; j ++)
+				frame [channel] [j] = 0.0;
+		} else {
+			for (long j = 1, i = startSample; j <= nsamp_window; j ++)
+				frame [channel] [j] = my z [channel] [i ++] - localMean [channel];
+		}
+	}
+
+	/*
+	 * Compute the local peak; look half a longest period to both sides.
+	 */
+	localPeak = 0.0;
+	if ((startSample = halfnsamp_window + 1 - halfnsamp_period) < 1) startSample = 1;
+	if ((endSample = halfnsamp_window + halfnsamp_period) > nsamp_window) endSample = nsamp_window;
+	for (long channel = 1; channel <= my ny; channel ++) {
+		for (long j = startSample; j <= endSample; j ++) {
+			double value = fabs (frame [channel] [j]);
+			if (value > localPeak) localPeak = value;
+		}
+	}
+	pitchFrame->intensity = localPeak > globalPeak ? 1.0 : localPeak / globalPeak;
+
+	/*
+	 * Compute the correlation into the array 'r'.
+	 */
+	if (method >= FCC_NORMAL) {
+		double startTime = t - 0.5 * (1.0 / minimumPitch + dt_window);
+		long localSpan = maximumLag + nsamp_window, localMaximumLag, offset;
+		if ((startSample = Sampled_xToLowIndex (me, startTime)) < 1) startSample = 1;
+		if (localSpan > my nx + 1 - startSample) localSpan = my nx + 1 - startSample;
+		localMaximumLag = localSpan - nsamp_window;
+		offset = startSample - 1;
+		double sumx2 = 0;   /* Sum of squares. */
+		for (long channel = 1; channel <= my ny; channel ++) {
+			double *amp = my z [channel] + offset;
+			for (long i = 1; i <= nsamp_window; i ++) {
+				double x = amp [i] - localMean [channel];
+				sumx2 += x * x;
+			}
+		}
+		double sumy2 = sumx2;   /* At zero lag, these are still equal. */
+		r [0] = 1.0;
+		for (long i = 1; i <= localMaximumLag; i ++) {
+			double product = 0.0;
+			for (long channel = 1; channel <= my ny; channel ++) {
+				double *amp = my z [channel] + offset;
+				double y0 = amp [i] - localMean [channel];
+				double yZ = amp [i + nsamp_window] - localMean [channel];
+				sumy2 += yZ * yZ - y0 * y0;
+				for (long j = 1; j <= nsamp_window; j ++) {
+					double x = amp [j] - localMean [channel];
+					double y = amp [i + j] - localMean [channel];
+					product += x * y;
+				}
+			}
+			r [- i] = r [i] = product / sqrt (sumx2 * sumy2);
+		}
+	} else {
+
+		/*
+		 * The FFT of the autocorrelation is the power spectrum.
+		 */
+		for (long i = 1; i <= nsampFFT; i ++) {
+			ac [i] = 0.0;
+		}
+		for (long channel = 1; channel <= my ny; channel ++) {
+			NUMfft_forward (fftTable, frame [channel]);   /* Complex spectrum. */
+			ac [1] += frame [channel] [1] * frame [channel] [1];   /* DC component. */
+			for (long i = 2; i < nsampFFT; i += 2) {
+				ac [i] += frame [channel] [i] * frame [channel] [i] + frame [channel] [i+1] * frame [channel] [i+1]; /* Power spectrum. */
+			}
+			ac [nsampFFT] += frame [channel] [nsampFFT] * frame [channel] [nsampFFT];   /* Nyquist frequency. */
+		}
+		NUMfft_backward (fftTable, ac);   /* Autocorrelation. */
+
+		/*
+		 * Normalize the autocorrelation to the value with zero lag,
+		 * and divide it by the normalized autocorrelation of the window.
+		 */
+		r [0] = 1.0;
+		for (long i = 1; i <= brent_ixmax; i ++)
+			r [- i] = r [i] = ac [i + 1] / (ac [1] * windowR [i + 1]);
+	}
+
+	/*
+	 * Register the first candidate, which is always present: voicelessness.
+	 */
+	pitchFrame->nCandidates = 1;
+	pitchFrame->candidate[1].frequency = 0.0;   /* Voiceless: always present. */
+	pitchFrame->candidate[1].strength = 0.0;
+
+	/*
+	 * Shortcut: absolute silence is always voiceless.
+	 * We are done for this frame.
+	 */
+	if (localPeak == 0) return;
+
+	/*
+	 * Find the strongest maxima of the correlation of this frame, 
+	 * and register them as candidates.
+	 */
+	imax [1] = 0;
+	for (long i = 2; i < maximumLag && i < brent_ixmax; i ++)
+		if (r [i] > 0.5 * voicingThreshold && /* Not too unvoiced? */
+			r [i] > r [i-1] && r [i] >= r [i+1])   /* Maximum? */
+	{
+		int place = 0;
+
+		/*
+		 * Use parabolic interpolation for first estimate of frequency,
+		 * and sin(x)/x interpolation to compute the strength of this frequency.
+		 */
+		double dr = 0.5 * (r [i+1] - r [i-1]), d2r = 2 * r [i] - r [i-1] - r [i+1];
+		double frequencyOfMaximum = 1 / my dx / (i + dr / d2r);
+		long offset = - brent_ixmax - 1;
+		double strengthOfMaximum = /* method & 1 ? */
+			NUM_interpolate_sinc (& r [offset], brent_ixmax - offset, 1 / my dx / frequencyOfMaximum - offset, 30)
+			/* : r [i] + 0.5 * dr * dr / d2r */;
+		/* High values due to short windows are to be reflected around 1. */
+		if (strengthOfMaximum > 1.0) strengthOfMaximum = 1.0 / strengthOfMaximum;
+
+		/*
+		 * Find a place for this maximum.
+		 */
+		if (pitchFrame->nCandidates < maxnCandidates) { /* Is there still a free place? */
+			place = ++ pitchFrame->nCandidates;
+		} else {
+			/* Try the place of the weakest candidate so far. */
+			double weakest = 2;
+			for (int iweak = 2; iweak <= maxnCandidates; iweak ++) {
+				/* High frequencies are to be favoured */
+				/* if we want to analyze a perfectly periodic signal correctly. */
+				double localStrength = pitchFrame->candidate[iweak].strength - octaveCost *
+					NUMlog2 (minimumPitch / pitchFrame->candidate[iweak].frequency);
+				if (localStrength < weakest) { weakest = localStrength; place = iweak; }
+			}
+			/* If this maximum is weaker than the weakest candidate so far, give it no place. */
+			if (strengthOfMaximum - octaveCost * NUMlog2 (minimumPitch / frequencyOfMaximum) <= weakest)
+				place = 0;
+		}
+		if (place) {   /* Have we found a place for this candidate? */
+			pitchFrame->candidate[place].frequency = frequencyOfMaximum;
+			pitchFrame->candidate[place].strength = strengthOfMaximum;
+			imax [place] = i;
+		}
+	}
+
+	/*
+	 * Second pass: for extra precision, maximize sin(x)/x interpolation ('sinc').
+	 */
+	for (long i = 2; i <= pitchFrame->nCandidates; i ++) {
+		if (method != AC_HANNING || pitchFrame->candidate[i].frequency > 0.0 / my dx) {
+			double xmid, ymid;
+			long offset = - brent_ixmax - 1;
+			ymid = NUMimproveMaximum (& r [offset], brent_ixmax - offset, imax [i] - offset,
+				pitchFrame->candidate[i].frequency > 0.3 / my dx ? NUM_PEAK_INTERPOLATE_SINC700 : brent_depth, & xmid);
+			xmid += offset;
+			pitchFrame->candidate[i].frequency = 1.0 / my dx / xmid;
+			if (ymid > 1.0) ymid = 1.0 / ymid;
+			pitchFrame->candidate[i].strength = ymid;
+		}
+	}
+}
+
+static void Sound_into_Pitch (Sound me, Pitch thee, long firstFrame, long lastFrame,
+	double minimumPitch, int maxnCandidates, int method, double voicingThreshold, double octaveCost,
+	NUMfft_Table fftTable, double dt_window, long nsamp_window, long halfnsamp_window,
+	long maximumLag, long nsampFFT, long nsamp_period, long halfnsamp_period,
+	long brent_ixmax, long brent_depth, double globalPeak,
+	double **frame, double *ac, double *window, double *windowR,
+	double *r, long *imax, double *localMean,
+	bool isMainThread)
+{
+	for (long iframe = firstFrame; iframe <= lastFrame; iframe ++) {
+		Pitch_Frame pitchFrame = & thy frame [iframe];
+		double t = Sampled_indexToX (thee, iframe);
+		if (isMainThread)
+			Melder_progress (0.1 + 0.8 * (iframe - firstFrame) / (lastFrame - firstFrame),
+				L"Sound to Pitch: analysing ", Melder_integer (lastFrame), L" frames");
+		Sound_into_PitchFrame (me, pitchFrame, t,
+			minimumPitch, maxnCandidates, method, voicingThreshold, octaveCost,
+			fftTable, dt_window, nsamp_window, halfnsamp_window,
+			maximumLag, nsampFFT, nsamp_period, halfnsamp_period,
+			brent_ixmax, brent_depth, globalPeak,
+			frame, ac, window, windowR,
+			r, imax, localMean);
+	}
+}
+
+Pitch Sound_to_Pitch_any (Sound me,
+	double dt, double minimumPitch, double periodsPerWindow, int maxnCandidates,
+	int method,
+	double silenceThreshold, double voicingThreshold,
+	double octaveCost, double octaveJumpCost, double voicedUnvoicedCost, double ceiling)
+{
+	try {
+		autoNUMfft_Table fftTable, fftTable1, fftTable2, fftTable3;
+		double duration, t1;
+		double dt_window;   /* Window length in seconds. */
+		long nsamp_window, halfnsamp_window;   /* Number of samples per window. */
+		long nFrames, minimumLag, maximumLag;
+		long nsampFFT;
+		double interpolation_depth;
+		long nsamp_period, halfnsamp_period;   /* Number of samples in longest period. */
+		long brent_ixmax, brent_depth;
+		double globalPeak;
+
+		Melder_assert (maxnCandidates >= 2);
+		Melder_assert (method >= AC_HANNING && method <= FCC_ACCURATE);
+
+		if (maxnCandidates < ceiling / minimumPitch) maxnCandidates = ceiling / minimumPitch;
+
+		if (dt <= 0.0) dt = periodsPerWindow / minimumPitch / 4.0;   /* e.g. 3 periods, 75 Hz: 10 milliseconds. */
+
+		switch (method) {
+			case AC_HANNING:
+				brent_depth = NUM_PEAK_INTERPOLATE_SINC70;
+				interpolation_depth = 0.5;
+				break;
+			case AC_GAUSS:
+				periodsPerWindow *= 2;   /* Because Gaussian window is twice as long. */
+				brent_depth = NUM_PEAK_INTERPOLATE_SINC700;
+				interpolation_depth = 0.25;   /* Because Gaussian window is twice as long. */
+				break;
+			case FCC_NORMAL:
+				brent_depth = NUM_PEAK_INTERPOLATE_SINC70;
+				interpolation_depth = 1.0;
+				break;
+			case FCC_ACCURATE:
+				brent_depth = NUM_PEAK_INTERPOLATE_SINC700;
+				interpolation_depth = 1.0;
+				break;
+		}
+		duration = my dx * my nx;
+		if (minimumPitch < periodsPerWindow / duration)
+			Melder_throw ("To analyse this Sound, ", L_LEFT_DOUBLE_QUOTE, "minimum pitch", L_RIGHT_DOUBLE_QUOTE, " must not be less than ", periodsPerWindow / duration, " Hz.");
+
+		/*
+		 * Determine the number of samples in the longest period.
+		 * We need this to compute the local mean of the sound (looking one period in both directions),
+		 * and to compute the local peak of the sound (looking half a period in both directions).
+		 */
+		nsamp_period = floor (1 / my dx / minimumPitch);
+		halfnsamp_period = nsamp_period / 2 + 1;
+
+		if (ceiling > 0.5 / my dx) ceiling = 0.5 / my dx;
+
+		/*
+		 * Determine window length in seconds and in samples.
+		 */
+		dt_window = periodsPerWindow / minimumPitch;
+		nsamp_window = floor (dt_window / my dx);
+		halfnsamp_window = nsamp_window / 2 - 1;
+		if (halfnsamp_window < 2)
+			Melder_throw ("Analysis window too short.");
+		nsamp_window = halfnsamp_window * 2;
+
+		/*
+		 * Determine the minimum and maximum lags.
+		 */
+		minimumLag = floor (1 / my dx / ceiling);
+		if (minimumLag < 2) minimumLag = 2;
+		maximumLag = floor (nsamp_window / periodsPerWindow) + 2;
+		if (maximumLag > nsamp_window) maximumLag = nsamp_window;
+
+		/*
+		 * Determine the number of frames.
+		 * Fit as many frames as possible symmetrically in the total duration.
+		 * We do this even for the forward cross-correlation method,
+		 * because that allows us to compare the two methods.
+		 */
+		try {
+			Sampled_shortTermAnalysis (me, method >= FCC_NORMAL ? 1 / minimumPitch + dt_window : dt_window, dt, & nFrames, & t1);
+		} catch (MelderError) {
+			Melder_throw ("The pitch analysis would give zero pitch frames.");
+		}
+
+		/*
+		 * Create the resulting pitch contour.
+		 */
+		autoPitch thee = Pitch_create (my xmin, my xmax, nFrames, dt, t1, ceiling, maxnCandidates);
+
+		/*
+		 * Create (too much) space for candidates.
+		 */
+		for (long iframe = 1; iframe <= nFrames; iframe ++) {
+			Pitch_Frame pitchFrame = & thy frame [iframe];
+			Pitch_Frame_init (pitchFrame, maxnCandidates);
+		}
+
+		/*
+		 * Compute the global absolute peak for determination of silence threshold.
+		 */
+		globalPeak = 0.0;
+		for (long channel = 1; channel <= my ny; channel ++) {
+			double mean = 0.0;
+			for (long i = 1; i <= my nx; i ++) {
+				mean += my z [channel] [i];
+			}
+			mean /= my nx;
+			for (long i = 1; i <= my nx; i ++) {
+				double value = fabs (my z [channel] [i] - mean);
+				if (value > globalPeak) globalPeak = value;
+			}
+		}
+		if (globalPeak == 0.0) {
+			return thee.transfer();
+		}
+
+		autoNUMvector <double> window;
+		autoNUMvector <double> windowR;
+		if (method >= FCC_NORMAL) {   /* For cross-correlation analysis. */
+
+			brent_ixmax = nsamp_window * interpolation_depth;
+
+		} else {   /* For autocorrelation analysis. */
+
+			/*
+			* Compute the number of samples needed for doing FFT.
+			* To avoid edge effects, we have to append zeroes to the window.
+			* The maximum lag considered for maxima is maximumLag.
+			* The maximum lag used in interpolation is nsamp_window * interpolation_depth.
+			*/
+			nsampFFT = 1; while (nsampFFT < nsamp_window * (1 + interpolation_depth)) nsampFFT *= 2;
+
+			/*
+			* Create buffers for autocorrelation analysis.
+			*/
+			windowR.reset (1, nsampFFT);
+			window.reset (1, nsamp_window);
+			NUMfft_Table_init (& fftTable, nsampFFT);
+
+			/*
+			* A Gaussian or Hanning window is applied against phase effects.
+			* The Hanning window is 2 to 5 dB better for 3 periods/window.
+			* The Gaussian window is 25 to 29 dB better for 6 periods/window.
+			*/
+			if (method == AC_GAUSS) {   /* Gaussian window. */
+				double imid = 0.5 * (nsamp_window + 1), edge = exp (-12.0);
+				for (long i = 1; i <= nsamp_window; i ++)
+					window [i] = (exp (-48.0 * (i - imid) * (i - imid) /
+						(nsamp_window + 1) / (nsamp_window + 1)) - edge) / (1 - edge);
+			} else {   // Hanning window
+				for (long i = 1; i <= nsamp_window; i ++)
+					window [i] = 0.5 - 0.5 * cos (i * 2 * NUMpi / (nsamp_window + 1));
+			}
+
+			/*
+			* Compute the normalized autocorrelation of the window.
+			*/
+			for (long i = 1; i <= nsamp_window; i ++) windowR [i] = window [i];
+			NUMfft_forward (& fftTable, windowR.peek());
+			windowR [1] *= windowR [1];   // DC component
+			for (long i = 2; i < nsampFFT; i += 2) {
+				windowR [i] = windowR [i] * windowR [i] + windowR [i+1] * windowR [i+1];
+				windowR [i + 1] = 0.0;   // power spectrum: square and zero
+			}
+			windowR [nsampFFT] *= windowR [nsampFFT];   // Nyquist frequency
+			NUMfft_backward (& fftTable, windowR.peek());   // autocorrelation
+			for (long i = 2; i <= nsamp_window; i ++) windowR [i] /= windowR [1];   // normalize
+			windowR [1] = 1.0;   // normalize
+
+			brent_ixmax = nsamp_window * interpolation_depth;
+		}
+
+		NUMfft_Table_init (& fftTable1, nsampFFT);
+		NUMfft_Table_init (& fftTable2, nsampFFT);
+		NUMfft_Table_init (& fftTable3, nsampFFT);
+		autoNUMmatrix <double> frame, frame1, frame2, frame3;
+		autoNUMvector <double> ac, ac1, ac2, ac3;
+		if (method >= FCC_NORMAL) {   // cross-correlation
+			frame.reset (1, my ny, 1, nsamp_window);
+			frame1.reset (1, my ny, 1, nsamp_window);
+			frame2.reset (1, my ny, 1, nsamp_window);
+			frame3.reset (1, my ny, 1, nsamp_window);
+		} else {   // autocorrelation
+			frame.reset (1, my ny, 1, nsampFFT);
+			frame1.reset (1, my ny, 1, nsampFFT);
+			frame2.reset (1, my ny, 1, nsampFFT);
+			frame3.reset (1, my ny, 1, nsampFFT);
+			ac.reset (1, nsampFFT);
+			ac1.reset (1, nsampFFT);
+			ac2.reset (1, nsampFFT);
+			ac3.reset (1, nsampFFT);
+		}
+		autoNUMvector <double> r (- nsamp_window, nsamp_window), r1 (- nsamp_window, nsamp_window), r2 (- nsamp_window, nsamp_window), r3 (- nsamp_window, nsamp_window);
+		autoNUMvector <long> imax (1, maxnCandidates), imax1 (1, maxnCandidates), imax2 (1, maxnCandidates), imax3 (1, maxnCandidates);
+		autoNUMvector <double> localMean (1, my ny), localMean1 (1, my ny), localMean2 (1, my ny), localMean3 (1, my ny);
+
+		autoMelderProgress progress (L"Sound to Pitch...");
+
+		long numberOfFramesPerThread = 20;
+		int numberOfThreads = (nFrames - 1) / numberOfFramesPerThread + 1;
+		const int numberOfProcessors = 4;
+		if (numberOfThreads > numberOfProcessors) numberOfThreads = numberOfProcessors;
+		if (numberOfThreads > 16) numberOfThreads = 16;
+		numberOfThreads = 4;
+		numberOfFramesPerThread = (nFrames - 1) / numberOfThreads + 1;
+
+		std::thread thread [3];
+		long firstFrame = 1, lastFrame = numberOfFramesPerThread;
+		try {
+			thread [0] = std::thread (Sound_into_Pitch, me, thee.peek(), firstFrame, lastFrame,
+				minimumPitch, maxnCandidates, method, voicingThreshold, octaveCost,
+				& fftTable1, dt_window, nsamp_window, halfnsamp_window,
+				maximumLag, nsampFFT, nsamp_period, halfnsamp_period,
+				brent_ixmax, brent_depth, globalPeak,
+				frame1.peek(), ac1.peek(), window.peek(), windowR.peek(),
+				r1.peek(), imax1.peek(), localMean1.peek(),
+				false);
+			firstFrame = lastFrame + 1;
+			lastFrame += numberOfFramesPerThread;
+			thread [1] = std::thread (Sound_into_Pitch, me, thee.peek(), firstFrame, lastFrame,
+				minimumPitch, maxnCandidates, method, voicingThreshold, octaveCost,
+				& fftTable2, dt_window, nsamp_window, halfnsamp_window,
+				maximumLag, nsampFFT, nsamp_period, halfnsamp_period,
+				brent_ixmax, brent_depth, globalPeak,
+				frame2.peek(), ac2.peek(), window.peek(), windowR.peek(),
+				r2.peek(), imax2.peek(), localMean2.peek(),
+				false);
+			firstFrame = lastFrame + 1;
+			lastFrame += numberOfFramesPerThread;
+			thread [2] = std::thread (Sound_into_Pitch, me, thee.peek(), firstFrame, lastFrame,
+				minimumPitch, maxnCandidates, method, voicingThreshold, octaveCost,
+				& fftTable3, dt_window, nsamp_window, halfnsamp_window,
+				maximumLag, nsampFFT, nsamp_period, halfnsamp_period,
+				brent_ixmax, brent_depth, globalPeak,
+				frame3.peek(), ac3.peek(), window.peek(), windowR.peek(),
+				r3.peek(), imax3.peek(), localMean3.peek(),
+				false);
+			firstFrame = lastFrame + 1;
+			lastFrame += numberOfFramesPerThread;
+		} catch (MelderError) {
+			for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+				if (thread [ithread - 1]. joinable ())
+					thread [ithread - 1]. join ();
+			}
+			throw;
+		}
+		Sound_into_Pitch (me, thee.peek(), firstFrame, nFrames,
+			minimumPitch, maxnCandidates, method, voicingThreshold, octaveCost,
+			& fftTable, dt_window, nsamp_window, halfnsamp_window,
+			maximumLag, nsampFFT, nsamp_period, halfnsamp_period,
+			brent_ixmax, brent_depth, globalPeak,
+			frame.peek(), ac.peek(), window.peek(), windowR.peek(),
+			r.peek(), imax.peek(), localMean.peek(),
+			true);
+		for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+			thread [ithread - 1]. join ();
+		}
+
+		Melder_progress (0.95, L"Sound to Pitch: path finder");   // progress (0.95, L"Sound to Pitch: path finder");
+		Pitch_pathFinder (thee.peek(), silenceThreshold, voicingThreshold,
+			octaveCost, octaveJumpCost, voicedUnvoicedCost, ceiling, Melder_debug == 31 ? true : false);
+
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": pitch analysis not performed.");
+	}
+}
+
+Pitch Sound_to_Pitch (Sound me, double timeStep, double minimumPitch, double maximumPitch) {
+	return Sound_to_Pitch_ac (me, timeStep, minimumPitch,
+		3.0, 15, FALSE, 0.03, 0.45, 0.01, 0.35, 0.14, maximumPitch);
+}
+
+Pitch Sound_to_Pitch_ac (Sound me,
+	double dt, double minimumPitch, double periodsPerWindow, int maxnCandidates, int accurate,
+	double silenceThreshold, double voicingThreshold,
+	double octaveCost, double octaveJumpCost, double voicedUnvoicedCost, double ceiling)
+{
+	return Sound_to_Pitch_any (me, dt, minimumPitch, periodsPerWindow, maxnCandidates, accurate,
+		silenceThreshold, voicingThreshold, octaveCost, octaveJumpCost, voicedUnvoicedCost, ceiling);
+}
+
+Pitch Sound_to_Pitch_cc (Sound me,
+	double dt, double minimumPitch, double periodsPerWindow, int maxnCandidates, int accurate,
+	double silenceThreshold, double voicingThreshold,
+	double octaveCost, double octaveJumpCost, double voicedUnvoicedCost, double ceiling)
+{
+	return Sound_to_Pitch_any (me, dt, minimumPitch, periodsPerWindow, maxnCandidates, 2 + accurate,
+		silenceThreshold, voicingThreshold, octaveCost, octaveJumpCost, voicedUnvoicedCost, ceiling);
+}
+
+/* End of file Sound_to_Pitch.cpp */
diff --git a/fon/Sound_to_Pitch.cpp b/fon/Sound_to_Pitch.cpp
index 8e5f10c..60de074 100644
--- a/fon/Sound_to_Pitch.cpp
+++ b/fon/Sound_to_Pitch.cpp
@@ -1,6 +1,6 @@
 /* Sound_to_Pitch.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -30,16 +30,317 @@
  * pb 2008/01/19 double
  * pb 2010/12/07 compatible with sounds with any number of channels
  * pb 2011/03/08 C++
+ * pb 2014/05/23 threads
  */
 
 #include "Sound_to_Pitch.h"
 #include "NUM2.h"
+#include "MelderThread.h"
 
 #define AC_HANNING  0
 #define AC_GAUSS  1
 #define FCC_NORMAL  2
 #define FCC_ACCURATE  3
 
+static void Sound_into_PitchFrame (Sound me, Pitch_Frame pitchFrame, double t,
+	double minimumPitch, int maxnCandidates, int method, double voicingThreshold, double octaveCost,
+	NUMfft_Table fftTable, double dt_window, long nsamp_window, long halfnsamp_window,
+	long maximumLag, long nsampFFT, long nsamp_period, long halfnsamp_period,
+	long brent_ixmax, long brent_depth, double globalPeak,
+	double **frame, double *ac, double *window, double *windowR,
+	double *r, long *imax, double *localMean)
+{
+	double localPeak;
+	long leftSample = Sampled_xToLowIndex (me, t), rightSample = leftSample + 1;
+	long startSample, endSample;
+
+	for (long channel = 1; channel <= my ny; channel ++) {
+		/*
+		 * Compute the local mean; look one longest period to both sides.
+		 */
+		startSample = rightSample - nsamp_period;
+		endSample = leftSample + nsamp_period;
+		Melder_assert (startSample >= 1);
+		Melder_assert (endSample <= my nx);
+		localMean [channel] = 0.0;
+		for (long i = startSample; i <= endSample; i ++) {
+			localMean [channel] += my z [channel] [i];
+		}
+		localMean [channel] /= 2 * nsamp_period;
+
+		/*
+		 * Copy a window to a frame and subtract the local mean.
+		 * We are going to kill the DC component before windowing.
+		 */
+		startSample = rightSample - halfnsamp_window;
+		endSample = leftSample + halfnsamp_window;
+		Melder_assert (startSample >= 1);
+		Melder_assert (endSample <= my nx);
+		if (method < FCC_NORMAL) {
+			for (long j = 1, i = startSample; j <= nsamp_window; j ++)
+				frame [channel] [j] = (my z [channel] [i ++] - localMean [channel]) * window [j];
+			for (long j = nsamp_window + 1; j <= nsampFFT; j ++)
+				frame [channel] [j] = 0.0;
+		} else {
+			for (long j = 1, i = startSample; j <= nsamp_window; j ++)
+				frame [channel] [j] = my z [channel] [i ++] - localMean [channel];
+		}
+	}
+
+	/*
+	 * Compute the local peak; look half a longest period to both sides.
+	 */
+	localPeak = 0.0;
+	if ((startSample = halfnsamp_window + 1 - halfnsamp_period) < 1) startSample = 1;
+	if ((endSample = halfnsamp_window + halfnsamp_period) > nsamp_window) endSample = nsamp_window;
+	for (long channel = 1; channel <= my ny; channel ++) {
+		for (long j = startSample; j <= endSample; j ++) {
+			double value = fabs (frame [channel] [j]);
+			if (value > localPeak) localPeak = value;
+		}
+	}
+	pitchFrame->intensity = localPeak > globalPeak ? 1.0 : localPeak / globalPeak;
+
+	/*
+	 * Compute the correlation into the array 'r'.
+	 */
+	if (method >= FCC_NORMAL) {
+		double startTime = t - 0.5 * (1.0 / minimumPitch + dt_window);
+		long localSpan = maximumLag + nsamp_window, localMaximumLag, offset;
+		if ((startSample = Sampled_xToLowIndex (me, startTime)) < 1) startSample = 1;
+		if (localSpan > my nx + 1 - startSample) localSpan = my nx + 1 - startSample;
+		localMaximumLag = localSpan - nsamp_window;
+		offset = startSample - 1;
+		double sumx2 = 0;   /* Sum of squares. */
+		for (long channel = 1; channel <= my ny; channel ++) {
+			double *amp = my z [channel] + offset;
+			for (long i = 1; i <= nsamp_window; i ++) {
+				double x = amp [i] - localMean [channel];
+				sumx2 += x * x;
+			}
+		}
+		double sumy2 = sumx2;   /* At zero lag, these are still equal. */
+		r [0] = 1.0;
+		for (long i = 1; i <= localMaximumLag; i ++) {
+			double product = 0.0;
+			for (long channel = 1; channel <= my ny; channel ++) {
+				double *amp = my z [channel] + offset;
+				double y0 = amp [i] - localMean [channel];
+				double yZ = amp [i + nsamp_window] - localMean [channel];
+				sumy2 += yZ * yZ - y0 * y0;
+				for (long j = 1; j <= nsamp_window; j ++) {
+					double x = amp [j] - localMean [channel];
+					double y = amp [i + j] - localMean [channel];
+					product += x * y;
+				}
+			}
+			r [- i] = r [i] = product / sqrt (sumx2 * sumy2);
+		}
+	} else {
+
+		/*
+		 * The FFT of the autocorrelation is the power spectrum.
+		 */
+		for (long i = 1; i <= nsampFFT; i ++) {
+			ac [i] = 0.0;
+		}
+		for (long channel = 1; channel <= my ny; channel ++) {
+			NUMfft_forward (fftTable, frame [channel]);   /* Complex spectrum. */
+			ac [1] += frame [channel] [1] * frame [channel] [1];   /* DC component. */
+			for (long i = 2; i < nsampFFT; i += 2) {
+				ac [i] += frame [channel] [i] * frame [channel] [i] + frame [channel] [i+1] * frame [channel] [i+1]; /* Power spectrum. */
+			}
+			ac [nsampFFT] += frame [channel] [nsampFFT] * frame [channel] [nsampFFT];   /* Nyquist frequency. */
+		}
+		NUMfft_backward (fftTable, ac);   /* Autocorrelation. */
+
+		/*
+		 * Normalize the autocorrelation to the value with zero lag,
+		 * and divide it by the normalized autocorrelation of the window.
+		 */
+		r [0] = 1.0;
+		for (long i = 1; i <= brent_ixmax; i ++)
+			r [- i] = r [i] = ac [i + 1] / (ac [1] * windowR [i + 1]);
+	}
+
+	/*
+	 * Register the first candidate, which is always present: voicelessness.
+	 */
+	pitchFrame->nCandidates = 1;
+	pitchFrame->candidate[1].frequency = 0.0;   /* Voiceless: always present. */
+	pitchFrame->candidate[1].strength = 0.0;
+
+	/*
+	 * Shortcut: absolute silence is always voiceless.
+	 * We are done for this frame.
+	 */
+	if (localPeak == 0) return;
+
+	/*
+	 * Find the strongest maxima of the correlation of this frame, 
+	 * and register them as candidates.
+	 */
+	imax [1] = 0;
+	for (long i = 2; i < maximumLag && i < brent_ixmax; i ++)
+		if (r [i] > 0.5 * voicingThreshold && /* Not too unvoiced? */
+			r [i] > r [i-1] && r [i] >= r [i+1])   /* Maximum? */
+	{
+		int place = 0;
+
+		/*
+		 * Use parabolic interpolation for first estimate of frequency,
+		 * and sin(x)/x interpolation to compute the strength of this frequency.
+		 */
+		double dr = 0.5 * (r [i+1] - r [i-1]), d2r = 2 * r [i] - r [i-1] - r [i+1];
+		double frequencyOfMaximum = 1 / my dx / (i + dr / d2r);
+		long offset = - brent_ixmax - 1;
+		double strengthOfMaximum = /* method & 1 ? */
+			NUM_interpolate_sinc (& r [offset], brent_ixmax - offset, 1 / my dx / frequencyOfMaximum - offset, 30)
+			/* : r [i] + 0.5 * dr * dr / d2r */;
+		/* High values due to short windows are to be reflected around 1. */
+		if (strengthOfMaximum > 1.0) strengthOfMaximum = 1.0 / strengthOfMaximum;
+
+		/*
+		 * Find a place for this maximum.
+		 */
+		if (pitchFrame->nCandidates < maxnCandidates) { /* Is there still a free place? */
+			place = ++ pitchFrame->nCandidates;
+		} else {
+			/* Try the place of the weakest candidate so far. */
+			double weakest = 2;
+			for (int iweak = 2; iweak <= maxnCandidates; iweak ++) {
+				/* High frequencies are to be favoured */
+				/* if we want to analyze a perfectly periodic signal correctly. */
+				double localStrength = pitchFrame->candidate[iweak].strength - octaveCost *
+					NUMlog2 (minimumPitch / pitchFrame->candidate[iweak].frequency);
+				if (localStrength < weakest) { weakest = localStrength; place = iweak; }
+			}
+			/* If this maximum is weaker than the weakest candidate so far, give it no place. */
+			if (strengthOfMaximum - octaveCost * NUMlog2 (minimumPitch / frequencyOfMaximum) <= weakest)
+				place = 0;
+		}
+		if (place) {   /* Have we found a place for this candidate? */
+			pitchFrame->candidate[place].frequency = frequencyOfMaximum;
+			pitchFrame->candidate[place].strength = strengthOfMaximum;
+			imax [place] = i;
+		}
+	}
+
+	/*
+	 * Second pass: for extra precision, maximize sin(x)/x interpolation ('sinc').
+	 */
+	for (long i = 2; i <= pitchFrame->nCandidates; i ++) {
+		if (method != AC_HANNING || pitchFrame->candidate[i].frequency > 0.0 / my dx) {
+			double xmid, ymid;
+			long offset = - brent_ixmax - 1;
+			ymid = NUMimproveMaximum (& r [offset], brent_ixmax - offset, imax [i] - offset,
+				pitchFrame->candidate[i].frequency > 0.3 / my dx ? NUM_PEAK_INTERPOLATE_SINC700 : brent_depth, & xmid);
+			xmid += offset;
+			pitchFrame->candidate[i].frequency = 1.0 / my dx / xmid;
+			if (ymid > 1.0) ymid = 1.0 / ymid;
+			pitchFrame->candidate[i].strength = ymid;
+		}
+	}
+}
+
+Thing_define (Sound_into_Pitch_Args, Thing) { public:
+	Sound sound;
+	Pitch pitch;
+	long firstFrame, lastFrame;
+	double minimumPitch;
+	int maxnCandidates, method;
+	double voicingThreshold, octaveCost, dt_window;
+	long nsamp_window, halfnsamp_window, maximumLag, nsampFFT, nsamp_period, halfnsamp_period, brent_ixmax, brent_depth;
+	double globalPeak, *window, *windowR;
+	bool isMainThread;
+	volatile int *cancelled;
+};
+
+Thing_implement (Sound_into_Pitch_Args, Thing, 0);
+
+static Sound_into_Pitch_Args Sound_into_Pitch_Args_create (Sound sound, Pitch pitch,
+	long firstFrame, long lastFrame, double minimumPitch, int maxnCandidates, int method,
+	double voicingThreshold, double octaveCost,
+	double dt_window, long nsamp_window, long halfnsamp_window, long maximumLag, long nsampFFT,
+	long nsamp_period, long halfnsamp_period, long brent_ixmax, long brent_depth,
+	double globalPeak, double *window, double *windowR,
+	bool isMainThread, volatile int *cancelled)
+{
+	autoSound_into_Pitch_Args me = Thing_new (Sound_into_Pitch_Args);
+	my sound = sound;
+	my pitch = pitch;
+	my firstFrame = firstFrame;
+	my lastFrame = lastFrame;
+	my minimumPitch = minimumPitch;
+	my maxnCandidates = maxnCandidates;
+	my method = method;
+	my voicingThreshold = voicingThreshold;
+	my octaveCost = octaveCost;
+	my dt_window = dt_window;
+	my nsamp_window = nsamp_window;
+	my halfnsamp_window = halfnsamp_window;
+	my maximumLag = maximumLag;
+	my nsampFFT = nsampFFT;
+	my nsamp_period = nsamp_period;
+	my halfnsamp_period = halfnsamp_period;
+	my brent_ixmax = brent_ixmax;
+	my brent_depth = brent_depth;
+	my globalPeak = globalPeak;
+	my window = window;
+	my windowR = windowR;
+	my isMainThread = isMainThread;
+	my cancelled = cancelled;
+	return me.transfer();
+}
+
+MelderThread_MUTEX (mutex);
+bool mutex_inited;
+
+static MelderThread_RETURN_TYPE Sound_into_Pitch (Sound_into_Pitch_Args me)
+{
+	autoNUMfft_Table fftTable;
+	autoNUMmatrix <double> frame;
+	autoNUMvector <double> ac, r, localMean;
+	autoNUMvector <long> imax;
+	{// scope
+		MelderThread_LOCK (mutex);
+		if (my method >= FCC_NORMAL) {   // cross-correlation
+			frame.reset (1, my sound -> ny, 1, my nsamp_window);
+		} else {   // autocorrelation
+			NUMfft_Table_init (& fftTable, my nsampFFT);
+			frame.reset (1, my sound -> ny, 1, my nsampFFT);
+			ac.reset (1, my nsampFFT);
+		}
+		r.reset (- my nsamp_window, my nsamp_window);
+		imax.reset (1, my maxnCandidates);
+		localMean.reset (1, my sound -> ny);
+		MelderThread_UNLOCK (mutex);
+	}
+	for (long iframe = my firstFrame; iframe <= my lastFrame; iframe ++) {
+		Pitch_Frame pitchFrame = & my pitch -> frame [iframe];
+		double t = my pitch -> f_indexToX (iframe);
+		if (my isMainThread) {
+			try {
+				Melder_progress (0.1 + 0.8 * (iframe - my firstFrame) / (my lastFrame - my firstFrame),
+					L"Sound to Pitch: analysing ", Melder_integer (my lastFrame), L" frames");
+			} catch (MelderError) {
+				*my cancelled = 1;
+				throw;
+			}
+		} else if (*my cancelled) {
+			MelderThread_RETURN;
+		}
+		Sound_into_PitchFrame (my sound, pitchFrame, t,
+			my minimumPitch, my maxnCandidates, my method, my voicingThreshold, my octaveCost,
+			& fftTable, my dt_window, my nsamp_window, my halfnsamp_window,
+			my maximumLag, my nsampFFT, my nsamp_period, my halfnsamp_period,
+			my brent_ixmax, my brent_depth, my globalPeak,
+			frame.peek(), ac.peek(), my window, my windowR,
+			r.peek(), imax.peek(), localMean.peek());
+	}
+	MelderThread_RETURN;
+}
+
 Pitch Sound_to_Pitch_any (Sound me,
 	double dt, double minimumPitch, double periodsPerWindow, int maxnCandidates,
 	int method,
@@ -52,11 +353,10 @@ Pitch Sound_to_Pitch_any (Sound me,
 		double dt_window;   /* Window length in seconds. */
 		long nsamp_window, halfnsamp_window;   /* Number of samples per window. */
 		long nFrames, minimumLag, maximumLag;
-		long iframe, nsampFFT;
+		long nsampFFT;
 		double interpolation_depth;
 		long nsamp_period, halfnsamp_period;   /* Number of samples in longest period. */
 		long brent_ixmax, brent_depth;
-		double brent_accuracy;   /* Obsolete. */
 		double globalPeak;
 
 		Melder_assert (maxnCandidates >= 2);
@@ -69,23 +369,19 @@ Pitch Sound_to_Pitch_any (Sound me,
 		switch (method) {
 			case AC_HANNING:
 				brent_depth = NUM_PEAK_INTERPOLATE_SINC70;
-				brent_accuracy = 1e-7;
 				interpolation_depth = 0.5;
 				break;
 			case AC_GAUSS:
 				periodsPerWindow *= 2;   /* Because Gaussian window is twice as long. */
 				brent_depth = NUM_PEAK_INTERPOLATE_SINC700;
-				brent_accuracy = 1e-11;
 				interpolation_depth = 0.25;   /* Because Gaussian window is twice as long. */
 				break;
 			case FCC_NORMAL:
 				brent_depth = NUM_PEAK_INTERPOLATE_SINC70;
-				brent_accuracy = 1e-7;
 				interpolation_depth = 1.0;
 				break;
 			case FCC_ACCURATE:
 				brent_depth = NUM_PEAK_INTERPOLATE_SINC700;
-				brent_accuracy = 1e-11;
 				interpolation_depth = 1.0;
 				break;
 		}
@@ -139,6 +435,14 @@ Pitch Sound_to_Pitch_any (Sound me,
 		autoPitch thee = Pitch_create (my xmin, my xmax, nFrames, dt, t1, ceiling, maxnCandidates);
 
 		/*
+		 * Create (too much) space for candidates.
+		 */
+		for (long iframe = 1; iframe <= nFrames; iframe ++) {
+			Pitch_Frame pitchFrame = & thy frame [iframe];
+			Pitch_Frame_init (pitchFrame, maxnCandidates);
+		}
+
+		/*
 		 * Compute the global absolute peak for determination of silence threshold.
 		 */
 		globalPeak = 0.0;
@@ -157,17 +461,11 @@ Pitch Sound_to_Pitch_any (Sound me,
 			return thee.transfer();
 		}
 
-		autoNUMmatrix <double> frame;
-		autoNUMvector <double> ac;
 		autoNUMvector <double> window;
 		autoNUMvector <double> windowR;
 		if (method >= FCC_NORMAL) {   /* For cross-correlation analysis. */
 
-			/*
-			* Create buffer for cross-correlation analysis.
-			*/
-			frame.reset (1, my ny, 1, nsamp_window);
-
+			nsampFFT = 0;
 			brent_ixmax = nsamp_window * interpolation_depth;
 
 		} else {   /* For autocorrelation analysis. */
@@ -183,11 +481,9 @@ Pitch Sound_to_Pitch_any (Sound me,
 			/*
 			* Create buffers for autocorrelation analysis.
 			*/
-			frame.reset (1, my ny, 1, nsampFFT);
 			windowR.reset (1, nsampFFT);
 			window.reset (1, nsamp_window);
 			NUMfft_Table_init (& fftTable, nsampFFT);
-			ac.reset (1, nsampFFT);
 
 			/*
 			* A Gaussian or Hanning window is applied against phase effects.
@@ -222,213 +518,34 @@ Pitch Sound_to_Pitch_any (Sound me,
 			brent_ixmax = nsamp_window * interpolation_depth;
 		}
 
-		autoNUMvector <double> r (- nsamp_window, nsamp_window);
-		autoNUMvector <long> imax (1, maxnCandidates);
-		autoNUMvector <double> localMean (1, my ny);
-
 		autoMelderProgress progress (L"Sound to Pitch...");
 
-		for (iframe = 1; iframe <= nFrames; iframe ++) {
-			Pitch_Frame pitchFrame = & thy frame [iframe];
-			double t = Sampled_indexToX (thee.peek(), iframe), localPeak;
-			long leftSample = Sampled_xToLowIndex (me, t), rightSample = leftSample + 1;
-			long startSample, endSample;
-			Melder_progress (0.1 + (0.8 * iframe) / (nFrames + 1),
-				L"Sound to Pitch: analysis of frame ", Melder_integer (iframe), L" out of ", Melder_integer (nFrames));
-
-			for (long channel = 1; channel <= my ny; channel ++) {
-				/*
-				 * Compute the local mean; look one longest period to both sides.
-				 */
-				startSample = rightSample - nsamp_period;
-				endSample = leftSample + nsamp_period;
-				Melder_assert (startSample >= 1);
-				Melder_assert (endSample <= my nx);
-				localMean [channel] = 0.0;
-				for (long i = startSample; i <= endSample; i ++) {
-					localMean [channel] += my z [channel] [i];
-				}
-				localMean [channel] /= 2 * nsamp_period;
-
-				/*
-				 * Copy a window to a frame and subtract the local mean.
-				 * We are going to kill the DC component before windowing.
-				 */
-				startSample = rightSample - halfnsamp_window;
-				endSample = leftSample + halfnsamp_window;
-				Melder_assert (startSample >= 1);
-				Melder_assert (endSample <= my nx);
-				if (method < FCC_NORMAL) {
-					for (long j = 1, i = startSample; j <= nsamp_window; j ++)
-						frame [channel] [j] = (my z [channel] [i ++] - localMean [channel]) * window [j];
-					for (long j = nsamp_window + 1; j <= nsampFFT; j ++)
-						frame [channel] [j] = 0.0;
-				} else {
-					for (long j = 1, i = startSample; j <= nsamp_window; j ++)
-						frame [channel] [j] = my z [channel] [i ++] - localMean [channel];
-				}
-			}
-
-			/*
-			 * Compute the local peak; look half a longest period to both sides.
-			 */
-			localPeak = 0.0;
-			if ((startSample = halfnsamp_window + 1 - halfnsamp_period) < 1) startSample = 1;
-			if ((endSample = halfnsamp_window + halfnsamp_period) > nsamp_window) endSample = nsamp_window;
-			for (long channel = 1; channel <= my ny; channel ++) {
-				for (long j = startSample; j <= endSample; j ++) {
-					double value = fabs (frame [channel] [j]);
-					if (value > localPeak) localPeak = value;
-				}
-			}
-			pitchFrame->intensity = localPeak > globalPeak ? 1.0 : localPeak / globalPeak;
-
-			/*
-			 * Compute the correlation into the array 'r'.
-			 */
-			if (method >= FCC_NORMAL) {
-				double startTime = t - 0.5 * (1.0 / minimumPitch + dt_window);
-				long localSpan = maximumLag + nsamp_window, localMaximumLag, offset;
-				if ((startSample = Sampled_xToLowIndex (me, startTime)) < 1) startSample = 1;
-				if (localSpan > my nx + 1 - startSample) localSpan = my nx + 1 - startSample;
-				localMaximumLag = localSpan - nsamp_window;
-				offset = startSample - 1;
-				double sumx2 = 0;   /* Sum of squares. */
-				for (long channel = 1; channel <= my ny; channel ++) {
-					double *amp = my z [channel] + offset;
-					for (long i = 1; i <= nsamp_window; i ++) {
-						double x = amp [i] - localMean [channel];
-						sumx2 += x * x;
-					}
-				}
-				double sumy2 = sumx2;   /* At zero lag, these are still equal. */
-				r [0] = 1.0;
-				for (long i = 1; i <= localMaximumLag; i ++) {
-					double product = 0.0;
-					for (long channel = 1; channel <= my ny; channel ++) {
-						double *amp = my z [channel] + offset;
-						double y0 = amp [i] - localMean [channel];
-						double yZ = amp [i + nsamp_window] - localMean [channel];
-						sumy2 += yZ * yZ - y0 * y0;
-						for (long j = 1; j <= nsamp_window; j ++) {
-							double x = amp [j] - localMean [channel];
-							double y = amp [i + j] - localMean [channel];
-							product += x * y;
-						}
-					}
-					r [- i] = r [i] = product / sqrt (sumx2 * sumy2);
-				}
-			} else {
-
-				/*
-				 * The FFT of the autocorrelation is the power spectrum.
-				 */
-				for (long i = 1; i <= nsampFFT; i ++) {
-					ac [i] = 0.0;
-				}
-				for (long channel = 1; channel <= my ny; channel ++) {
-					NUMfft_forward (& fftTable, frame [channel]);   /* Complex spectrum. */
-					ac [1] += frame [channel] [1] * frame [channel] [1];   /* DC component. */
-					for (long i = 2; i < nsampFFT; i += 2) {
-						ac [i] += frame [channel] [i] * frame [channel] [i] + frame [channel] [i+1] * frame [channel] [i+1]; /* Power spectrum. */
-					}
-					ac [nsampFFT] += frame [channel] [nsampFFT] * frame [channel] [nsampFFT];   /* Nyquist frequency. */
-				}
-				NUMfft_backward (& fftTable, ac.peek());   /* Autocorrelation. */
-
-				/*
-				 * Normalize the autocorrelation to the value with zero lag,
-				 * and divide it by the normalized autocorrelation of the window.
-				 */
-				r [0] = 1.0;
-				for (long i = 1; i <= brent_ixmax; i ++)
-					r [- i] = r [i] = ac [i + 1] / (ac [1] * windowR [i + 1]);
-			}
-
-			/*
-			 * Create (too much) space for candidates.
-			 */
-			Pitch_Frame_init (pitchFrame, maxnCandidates);
-
-			/*
-			 * Register the first candidate, which is always present: voicelessness.
-			 */
-			pitchFrame->nCandidates = 1;
-			pitchFrame->candidate[1].frequency = 0.0;   /* Voiceless: always present. */
-			pitchFrame->candidate[1].strength = 0.0;
-
-			/*
-			 * Shortcut: absolute silence is always voiceless.
-			 * Go to next frame.
-			 */
-			if (localPeak == 0) continue;
-
-			/*
-			 * Find the strongest maxima of the correlation of this frame, 
-			 * and register them as candidates.
-			 */
-			imax [1] = 0;
-			for (long i = 2; i < maximumLag && i < brent_ixmax; i ++)
-				if (r [i] > 0.5 * voicingThreshold && /* Not too unvoiced? */
-					r [i] > r [i-1] && r [i] >= r [i+1])   /* Maximum? */
-			{
-				int place = 0;
-
-				/*
-				 * Use parabolic interpolation for first estimate of frequency,
-				 * and sin(x)/x interpolation to compute the strength of this frequency.
-				 */
-				double dr = 0.5 * (r [i+1] - r [i-1]), d2r = 2 * r [i] - r [i-1] - r [i+1];
-				double frequencyOfMaximum = 1 / my dx / (i + dr / d2r);
-				long offset = - brent_ixmax - 1;
-				double strengthOfMaximum = /* method & 1 ? */
-					NUM_interpolate_sinc (& r [offset], brent_ixmax - offset, 1 / my dx / frequencyOfMaximum - offset, 30)
-					/* : r [i] + 0.5 * dr * dr / d2r */;
-				/* High values due to short windows are to be reflected around 1. */
-				if (strengthOfMaximum > 1.0) strengthOfMaximum = 1.0 / strengthOfMaximum;
-
-				/*
-				 * Find a place for this maximum.
-				 */
-				if (pitchFrame->nCandidates < thy maxnCandidates) { /* Is there still a free place? */
-					place = ++ pitchFrame->nCandidates;
-				} else {
-					/* Try the place of the weakest candidate so far. */
-					double weakest = 2;
-					for (int iweak = 2; iweak <= thy maxnCandidates; iweak ++) {
-						/* High frequencies are to be favoured */
-						/* if we want to analyze a perfectly periodic signal correctly. */
-						double localStrength = pitchFrame->candidate[iweak].strength - octaveCost *
-							NUMlog2 (minimumPitch / pitchFrame->candidate[iweak].frequency);
-						if (localStrength < weakest) { weakest = localStrength; place = iweak; }
-					}
-					/* If this maximum is weaker than the weakest candidate so far, give it no place. */
-					if (strengthOfMaximum - octaveCost * NUMlog2 (minimumPitch / frequencyOfMaximum) <= weakest)
-						place = 0;
-				}
-				if (place) {   /* Have we found a place for this candidate? */
-					pitchFrame->candidate[place].frequency = frequencyOfMaximum;
-					pitchFrame->candidate[place].strength = strengthOfMaximum;
-					imax [place] = i;
-				}
-			}
-
-			/*
-			 * Second pass: for extra precision, maximize sin(x)/x interpolation ('sinc').
-			 */
-			for (long i = 2; i <= pitchFrame->nCandidates; i ++) {
-				if (method != AC_HANNING || pitchFrame->candidate[i].frequency > 0.0 / my dx) {
-					double xmid, ymid;
-					long offset = - brent_ixmax - 1;
-					ymid = NUMimproveMaximum (& r [offset], brent_ixmax - offset, imax [i] - offset,
-						pitchFrame->candidate[i].frequency > 0.3 / my dx ? NUM_PEAK_INTERPOLATE_SINC700 : brent_depth, & xmid);
-					xmid += offset;
-					pitchFrame->candidate[i].frequency = 1.0 / my dx / xmid;
-					if (ymid > 1.0) ymid = 1.0 / ymid;
-					pitchFrame->candidate[i].strength = ymid;
-				}
-			}
-		}   /* Next frame. */
+		long numberOfFramesPerThread = 20;
+		int numberOfThreads = (nFrames - 1) / numberOfFramesPerThread + 1;
+		const int numberOfProcessors = MelderThread_getNumberOfProcessors ();
+		trace ("%d processors", numberOfProcessors);
+		if (numberOfThreads > numberOfProcessors) numberOfThreads = numberOfProcessors;
+		if (numberOfThreads > 16) numberOfThreads = 16;
+		if (numberOfThreads < 1) numberOfThreads = 1;
+		numberOfFramesPerThread = (nFrames - 1) / numberOfThreads + 1;
+
+		if (! mutex_inited) { MelderThread_MUTEX_INIT (mutex); mutex_inited = true; }
+		autoSound_into_Pitch_Args args [16];
+		long firstFrame = 1, lastFrame = numberOfFramesPerThread;
+		volatile int cancelled = 0;
+		for (int ithread = 1; ithread <= numberOfThreads; ithread ++) {
+			if (ithread == numberOfThreads) lastFrame = nFrames;
+			args [ithread - 1].reset (Sound_into_Pitch_Args_create (me, thee.peek(),
+				firstFrame, lastFrame, minimumPitch, maxnCandidates, method,
+				voicingThreshold, octaveCost,
+				dt_window, nsamp_window, halfnsamp_window, maximumLag,
+				nsampFFT, nsamp_period, halfnsamp_period, brent_ixmax, brent_depth,
+				globalPeak, window.peek(), windowR.peek(),
+				ithread == numberOfThreads, & cancelled));
+			firstFrame = lastFrame + 1;
+			lastFrame += numberOfFramesPerThread;
+		}
+		MelderThread_run (Sound_into_Pitch, args, numberOfThreads);
 
 		Melder_progress (0.95, L"Sound to Pitch: path finder");   // progress (0.95, L"Sound to Pitch: path finder");
 		Pitch_pathFinder (thee.peek(), silenceThreshold, voicingThreshold,
diff --git a/fon/Sound_to_PointProcess.cpp b/fon/Sound_to_PointProcess.cpp
index 482dd05..0f3e76b 100644
--- a/fon/Sound_to_PointProcess.cpp
+++ b/fon/Sound_to_PointProcess.cpp
@@ -1,6 +1,6 @@
 /* Sound_to_PointProcess.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -50,14 +50,14 @@ PointProcess Sound_to_PointProcess_extrema (Sound me, long channel, int interpol
 		 * Pass 2: compute and register the extrema.
 		 */
 		for (long i = 2; i <= my nx - 1; i ++) {
-			double time, value, i_real;
+			double time, i_real;
 			if (includeMaxima && y [i] > y [i - 1] && y [i] >= y [i + 1]) {
-				value = NUMimproveMaximum (y, my nx, i, interpolation, & i_real);
+				(void) NUMimproveMaximum (y, my nx, i, interpolation, & i_real);
 				time = my x1 + (i_real - 1.0) * my dx;
 				PointProcess_addPoint (thee.peek(), time);
 			}
 			if (includeMinima && y [i] <= y [i - 1] && y [i] < y [i + 1]) {
-				value = NUMimproveMinimum (y, my nx, i, interpolation, & i_real);
+				(void) NUMimproveMinimum (y, my nx, i, interpolation, & i_real);
 				time = my x1 + (i_real - 1.0) * my dx;
 				PointProcess_addPoint (thee.peek(), time);
 			}
@@ -98,7 +98,7 @@ PointProcess Sound_to_PointProcess_zeroes (Sound me, long channel, bool includeR
 			if ((includeRaisers && y [i - 1] < 0.0 && y [i] >= 0.0) ||
 				(includeFallers && y [i - 1] >= 0.0 && y [i] < 0.0))
 			{
-				double time = Sampled_indexToX (me, i - 1) + my dx * y [i - 1] / (y [i - 1] - y [i]);   // linear
+				double time = my f_indexToX (i - 1) + my dx * y [i - 1] / (y [i - 1] - y [i]);   // linear
 				PointProcess_addPoint (thee.peek(), time);
 			}
 		}
diff --git a/fon/SpectrumTier.h b/fon/SpectrumTier.h
index 8002fc4..63c906b 100644
--- a/fon/SpectrumTier.h
+++ b/fon/SpectrumTier.h
@@ -2,7 +2,7 @@
 #define _SpectrumTier_h_
 /* SpectrumTier.h
  *
- * Copyright (C) 2007-2011 Paul Boersma
+ * Copyright (C) 2007-2011,2014 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
@@ -30,6 +30,8 @@ Thing_define (SpectrumTier, RealTier) {
 	protected:
 		virtual void v_info ();
 		virtual int v_domainQuantity () { return MelderQuantity_FREQUENCY_HERTZ; }
+		virtual const wchar_t * v_getUnitText (long ilevel, int unit, unsigned long flags)
+			{ (void) ilevel; (void) unit; (void) flags; return L"Frequency (Hz)"; }
 };
 
 SpectrumTier SpectrumTier_create (double fmin, double fmax);
diff --git a/fon/Spectrum_and_Spectrogram.cpp b/fon/Spectrum_and_Spectrogram.cpp
index ddf2b0b..f01399a 100644
--- a/fon/Spectrum_and_Spectrogram.cpp
+++ b/fon/Spectrum_and_Spectrogram.cpp
@@ -1,6 +1,6 @@
 /* Spectrum_and_Spectrogram.cpp
  *
- * Copyright (C) 1992-2011 David Weenink & Paul Boersma
+ * Copyright (C) 1992-2011,2014 David Weenink & 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
@@ -34,7 +34,7 @@ Spectrum Spectrogram_to_Spectrum (I, double tim) {
 		thy xmax = my ymax;
 		thy x1 = my y1;   // centre of first band, instead of 0 (makes it unFFTable)
 		thy dx = my dy;   // frequency step
-		long itime = Sampled_xToIndex (me, tim);
+		long itime = my f_xToIndex (tim);
 		if (itime < 1 ) itime = 1;
 		if (itime > my nx) itime = my nx;
 		for (long ifreq = 1; ifreq <= my ny; ifreq ++) {
diff --git a/fon/Spectrum_to_Excitation.cpp b/fon/Spectrum_to_Excitation.cpp
index 1aaddfd..a3fc365 100644
--- a/fon/Spectrum_to_Excitation.cpp
+++ b/fon/Spectrum_to_Excitation.cpp
@@ -1,6 +1,6 @@
 /* Spectrum_to_Excitation.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -25,8 +25,7 @@
 
 #include "Spectrum_to_Excitation.h"
 
-Excitation Spectrum_to_Excitation (I, double dbark) {
-	iam (Spectrum);
+Excitation Spectrum_to_Excitation (Spectrum me, double dbark) {
 	try {
 		long nbark = (int) floor (25.6 / dbark + 0.5);
 		double *re = my z [1], *im = my z [2]; 
@@ -67,7 +66,7 @@ Excitation Spectrum_to_Excitation (I, double dbark) {
 
 		autoExcitation thee = Excitation_create (dbark, nbark);
 		for (long i = 1; i <= nbark; i ++)
-			thy z [1] [i] = Excitation_soundPressureToPhon (sqrt (outSig [i + nbark/2]), Sampled_indexToX (thee.peek(), i));
+			thy z [1] [i] = Excitation_soundPressureToPhon (sqrt (outSig [i + nbark/2]), thy f_indexToX (i));
 
 		return thee.transfer();
 	} catch (MelderError) {
diff --git a/fon/Spectrum_to_Excitation.h b/fon/Spectrum_to_Excitation.h
index c8f2c5f..6b1b182 100644
--- a/fon/Spectrum_to_Excitation.h
+++ b/fon/Spectrum_to_Excitation.h
@@ -1,6 +1,6 @@
 /* Spectrum_to_Excitation.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -20,7 +20,7 @@
 #include "Spectrum.h"
 #include "Excitation.h"
 
-Excitation Spectrum_to_Excitation (I, double df);
+Excitation Spectrum_to_Excitation (Spectrum me, double df);
 /*
 	Postcondition:
 		filtered with 10 ^ (1.581 + 0.75 * bark - 1.75 * sqrt (1 + bark * bark)))
diff --git a/fon/TextGrid.cpp b/fon/TextGrid.cpp
index 88adc7f..36ac7f2 100644
--- a/fon/TextGrid.cpp
+++ b/fon/TextGrid.cpp
@@ -1,6 +1,6 @@
 /* TextGrid.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -177,45 +177,105 @@ IntervalTier IntervalTier_create (double tmin, double tmax) {
 }
 
 long IntervalTier_timeToLowIndex (IntervalTier me, double t) {
-	for (long i = 1; i <= my intervals -> size; i ++) {
-		TextInterval interval = (TextInterval) my intervals -> item [i];
-		if (t >= interval -> xmin && t < interval -> xmax)
-			return i;
+	long ileft = 1, iright = my intervals -> size;
+	if (iright < 1) return 0;   // empty tier
+	TextInterval leftInterval = (TextInterval) my intervals -> item [ileft];
+	if (t < leftInterval -> xmin) return 0;   // very small t
+	TextInterval rightInterval = (TextInterval) my intervals -> item [iright];
+	if (t >= rightInterval -> xmax) return 0;   // very large t
+	while (ileft < iright) {
+		long imid = (ileft + iright) / 2;
+		TextInterval midInterval = (TextInterval) my intervals -> item [imid];
+		if (t >= midInterval -> xmax) {
+			ileft = imid + 1;
+		} else {
+			iright = imid;
+		}
 	}
-	return 0;   // empty tier or very small or large t
+	return ileft;
 }
 
 long IntervalTier_timeToIndex (IntervalTier me, double t) {
-	return t == my xmax ? my intervals -> size : IntervalTier_timeToLowIndex (me, t);
+	long ileft = 1, iright = my intervals -> size;
+	if (iright < 1) return 0;   // empty tier
+	TextInterval leftInterval = (TextInterval) my intervals -> item [ileft];
+	if (t < leftInterval -> xmin) return 0;   // very small t
+	TextInterval rightInterval = (TextInterval) my intervals -> item [iright];
+	if (t > rightInterval -> xmax) return 0;   // very large t
+	while (ileft < iright) {
+		long imid = (ileft + iright) / 2;
+		TextInterval midInterval = (TextInterval) my intervals -> item [imid];
+		if (t >= midInterval -> xmax) {
+			ileft = imid + 1;
+		} else {
+			iright = imid;
+		}
+	}
+	return ileft;
 }
 
 long IntervalTier_timeToHighIndex (IntervalTier me, double t) {
-	for (long i = 1; i <= my intervals -> size; i ++) {
-		TextInterval interval = (TextInterval) my intervals -> item [i];
-		if (t > interval -> xmin && t <= interval -> xmax)
-			return i;
+	long ileft = 1, iright = my intervals -> size;
+	if (iright < 1) return 0;   // empty tier
+	TextInterval leftInterval = (TextInterval) my intervals -> item [ileft];
+	if (t <= leftInterval -> xmin) return 0;   // very small t
+	TextInterval rightInterval = (TextInterval) my intervals -> item [iright];
+	if (t > rightInterval -> xmax) return 0;   // very large t
+	while (ileft < iright) {
+		long imid = (ileft + iright) / 2;
+		TextInterval midInterval = (TextInterval) my intervals -> item [imid];
+		if (t > midInterval -> xmax) {
+			ileft = imid + 1;
+		} else {
+			iright = imid;
+		}
 	}
-	return 0;   // empty tier or very small or large t
+	return ileft;
 }
 
 long IntervalTier_hasTime (IntervalTier me, double t) {
-	for (long iinterval = 1; iinterval <= my intervals -> size; iinterval ++) {
-		TextInterval interval = (TextInterval) my intervals -> item [iinterval];
-		if (interval -> xmin == t || (iinterval == my intervals -> size && interval -> xmax == t)) {
-			return iinterval;   // time found
+	long ileft = 1, iright = my intervals -> size;
+	if (iright < 1) return 0;   // empty tier
+	TextInterval leftInterval = (TextInterval) my intervals -> item [ileft];
+	if (t < leftInterval -> xmin) return 0;   // very small t
+	TextInterval rightInterval = (TextInterval) my intervals -> item [iright];
+	if (t > rightInterval -> xmax) return 0;   // very large t
+	while (ileft < iright) {
+		long imid = (ileft + iright) / 2;
+		TextInterval midInterval = (TextInterval) my intervals -> item [imid];
+		if (t >= midInterval -> xmax) {
+			ileft = imid + 1;
+		} else {
+			iright = imid;
 		}
 	}
-	return 0;   // time not found
+	/*
+	 * We now know that t is within interval ileft.
+	 */
+	leftInterval = (TextInterval) my intervals -> item [ileft];
+	if (t == leftInterval -> xmin || t == leftInterval -> xmax) return ileft;
+	return 0;   // not found
 }
 
 long IntervalTier_hasBoundary (IntervalTier me, double t) {
-	for (long iinterval = 2; iinterval <= my intervals -> size; iinterval ++) {
-		TextInterval interval = (TextInterval) my intervals -> item [iinterval];
-		if (interval -> xmin == t) {
-			return iinterval;   // boundary found
+	long ileft = 2, iright = my intervals -> size;
+	if (iright < 2) return 0;   // tier without inner boundaries
+	TextInterval leftInterval = (TextInterval) my intervals -> item [ileft];
+	if (t < leftInterval -> xmin) return 0;   // very small t
+	TextInterval rightInterval = (TextInterval) my intervals -> item [iright];
+	if (t >= rightInterval -> xmax) return 0;   // very large t
+	while (ileft < iright) {
+		long imid = (ileft + iright) / 2;
+		TextInterval midInterval = (TextInterval) my intervals -> item [imid];
+		if (t >= midInterval -> xmax) {
+			ileft = imid + 1;
+		} else {
+			iright = imid;
 		}
 	}
-	return 0;   // boundary not found
+	leftInterval = (TextInterval) my intervals -> item [ileft];
+	if (t == leftInterval -> xmin) return ileft;
+	return 0;   // not found
 }
 
 void structTextGrid :: v_info () {
@@ -358,45 +418,43 @@ TextTier TextTier_readFromXwaves (MelderFile file) {
 	}
 }
 
-void TextGrid_checkSpecifiedTierNumberWithinRange (TextGrid me, long tierNumber) {
+Function TextGrid_checkSpecifiedTierNumberWithinRange (TextGrid me, long tierNumber) {
 	if (tierNumber < 1)
 		Melder_throw (me, ": the specified tier number is ", tierNumber, ", but should be at least 1.");
 	if (tierNumber > my tiers -> size)
 		Melder_throw (me, ": the specified tier number (", tierNumber, ") exceeds my number of tiers (", my tiers -> size, ").");	
+	return my tier (tierNumber);
 }
 
 IntervalTier TextGrid_checkSpecifiedTierIsIntervalTier (TextGrid me, long tierNumber) {
-	TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
-	Function tier = (Function) my tiers -> item [tierNumber];
+	Function tier = TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
 	if (tier -> classInfo != classIntervalTier)
 		Melder_throw ("Tier ", tierNumber, " is not an interval tier.");
-	return (IntervalTier) tier;
+	return static_cast <IntervalTier> (tier);
 }
 
 TextTier TextGrid_checkSpecifiedTierIsPointTier (TextGrid me, long tierNumber) {
-	TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
-	Function tier = (Function) my tiers -> item [tierNumber];
+	Function tier = TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
 	if (tier -> classInfo != classTextTier)
 		Melder_throw ("Tier ", tierNumber, " is not a point tier.");
-	return (TextTier) tier;
+	return static_cast <TextTier> (tier);
 }
 
 long TextGrid_countLabels (TextGrid me, long tierNumber, const wchar_t *text) {
 	try {
-		TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
+		Function anyTier = TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
 		long count = 0;
-		Function anyTier = (Function) my tiers -> item [tierNumber];
 		if (anyTier -> classInfo == classIntervalTier) {
 			IntervalTier tier = (IntervalTier) anyTier;
-			for (long i = 1; i <= tier -> intervals -> size; i ++) {
-				TextInterval segment = (TextInterval) tier -> intervals -> item [i];
+			for (long i = 1; i <= tier -> numberOfIntervals (); i ++) {
+				TextInterval segment = tier -> interval (i);
 				if (segment -> text && wcsequ (segment -> text, text))
 					count ++;
 			}
 		} else {
 			TextTier tier = (TextTier) anyTier;
-			for (long i = 1; i <= tier -> points -> size; i ++) {
-				TextPoint point = (TextPoint) tier -> points -> item [i];
+			for (long i = 1; i <= tier -> numberOfPoints (); i ++) {
+				TextPoint point = tier -> point (i);
 				if (point -> mark && wcsequ (point -> mark, text))
 					count ++;
 			}
@@ -435,25 +493,6 @@ TextGrid TextGrid_merge (Collection textGrids) {
 	}
 }
 
-static TextGrid _Label_to_TextGrid (Label me, double tmin, double tmax) {
-	autoTextGrid thee = TextGrid_createWithoutTiers (tmin, tmax);
-	for (long itier = 1; itier <= my size; itier ++) {
-		Tier tier = (Tier) my item [itier];
-		autoIntervalTier intervalTier = IntervalTier_create (tmin, tmax);
-		Collection_addItem (thy tiers, intervalTier.transfer());
-		Collection_removeItem (intervalTier -> intervals, 1);
-		for (long iinterval = 1; iinterval <= tier -> size; iinterval ++) {
-			Autosegment autosegment = (Autosegment) tier -> item [iinterval];
-			autoTextInterval textInterval = TextInterval_create (
-				iinterval == 1 ? tmin : autosegment -> xmin,
-				iinterval == tier -> size ? tmax : autosegment -> xmax,
-				autosegment -> name);
-			Collection_addItem (intervalTier -> intervals, textInterval.transfer());
-		}
-	}
-	return thee.transfer();
-}
-
 TextGrid TextGrid_extractPart (TextGrid me, double tmin, double tmax, int preserveTimes) {
 	try {
 		autoTextGrid thee = (TextGrid) Data_copy (me);
@@ -494,6 +533,25 @@ TextGrid TextGrid_extractPart (TextGrid me, double tmin, double tmax, int preser
 	}
 }
 
+static TextGrid _Label_to_TextGrid (Label me, double tmin, double tmax) {
+	autoTextGrid thee = TextGrid_createWithoutTiers (tmin, tmax);
+	for (long itier = 1; itier <= my size; itier ++) {
+		Tier tier = (Tier) my item [itier];
+		autoIntervalTier intervalTier = IntervalTier_create (tmin, tmax);
+		Collection_addItem (thy tiers, intervalTier.transfer());
+		Collection_removeItem (intervalTier -> intervals, 1);
+		for (long iinterval = 1; iinterval <= tier -> size; iinterval ++) {
+			Autosegment autosegment = (Autosegment) tier -> item [iinterval];
+			autoTextInterval textInterval = TextInterval_create (
+				iinterval == 1 ? tmin : autosegment -> xmin,
+				iinterval == tier -> size ? tmax : autosegment -> xmax,
+				autosegment -> name);
+			Collection_addItem (intervalTier -> intervals, textInterval.transfer());
+		}
+	}
+	return thee.transfer();
+}
+
 TextGrid Label_to_TextGrid (Label me, double duration) {
 	try {
 		double tmin = 0.0, tmax = duration;
@@ -605,8 +663,8 @@ PointProcess TextGrid_getStartingPoints (TextGrid me, long tierNumber, int which
 	try {
 		IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
 		autoPointProcess thee = PointProcess_create (my xmin, my xmax, 10);
-		for (long iinterval = 1; iinterval <= tier -> intervals -> size; iinterval ++) {
-			TextInterval interval = tier -> f_item (iinterval);
+		for (long iinterval = 1; iinterval <= tier -> numberOfIntervals (); iinterval ++) {
+			TextInterval interval = tier -> interval (iinterval);
 			if (Melder_stringMatchesCriterion (interval -> text, which_Melder_STRING, criterion)) {
 				PointProcess_addPoint (thee.peek(), interval -> xmin);
 			}
@@ -621,8 +679,8 @@ PointProcess TextGrid_getEndPoints (TextGrid me, long tierNumber, int which_Meld
 	try {
 		IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
 		autoPointProcess thee = PointProcess_create (my xmin, my xmax, 10);
-		for (long iinterval = 1; iinterval <= tier -> intervals -> size; iinterval ++) {
-			TextInterval interval = tier -> f_item (iinterval);
+		for (long iinterval = 1; iinterval <= tier -> numberOfIntervals (); iinterval ++) {
+			TextInterval interval = tier -> interval (iinterval);
 			if (Melder_stringMatchesCriterion (interval -> text, which_Melder_STRING, criterion)) {
 				PointProcess_addPoint (thee.peek(), interval -> xmax);
 			}
@@ -637,8 +695,8 @@ PointProcess TextGrid_getCentrePoints (TextGrid me, long tierNumber, int which_M
 	try {
 		IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
 		autoPointProcess thee = PointProcess_create (my xmin, my xmax, 10);
-		for (long iinterval = 1; iinterval <= tier -> intervals -> size; iinterval ++) {
-			TextInterval interval = tier -> f_item (iinterval);
+		for (long iinterval = 1; iinterval <= tier -> numberOfIntervals (); iinterval ++) {
+			TextInterval interval = tier -> interval (iinterval);
 			if (Melder_stringMatchesCriterion (interval -> text, which_Melder_STRING, criterion)) {
 				PointProcess_addPoint (thee.peek(), 0.5 * (interval -> xmin + interval -> xmax));
 			}
@@ -653,8 +711,8 @@ PointProcess TextGrid_getPoints (TextGrid me, long tierNumber, int which_Melder_
 	try {
 		TextTier tier = TextGrid_checkSpecifiedTierIsPointTier (me, tierNumber);
 		autoPointProcess thee = PointProcess_create (my xmin, my xmax, 10);
-		for (long ipoint = 1; ipoint <= tier -> points -> size; ipoint ++) {
-			TextPoint point = (TextPoint) tier -> points -> item [ipoint];
+		for (long ipoint = 1; ipoint <= tier -> numberOfPoints (); ipoint ++) {
+			TextPoint point = tier -> point (ipoint);
 			if (Melder_stringMatchesCriterion (point -> mark, which_Melder_STRING, criterion)) {
 				PointProcess_addPoint (thee.peek(), point -> number);
 			}
@@ -665,6 +723,50 @@ PointProcess TextGrid_getPoints (TextGrid me, long tierNumber, int which_Melder_
 	}
 }
 
+PointProcess TextGrid_getPoints_preceded (TextGrid me, long tierNumber,
+	int which_Melder_STRING, const wchar_t *criterion,
+	int which_Melder_STRING_precededBy, const wchar_t *criterion_precededBy)
+{
+	try {
+		TextTier tier = TextGrid_checkSpecifiedTierIsPointTier (me, tierNumber);
+		autoPointProcess thee = PointProcess_create (my xmin, my xmax, 10);
+		for (long ipoint = 1; ipoint <= tier -> numberOfPoints (); ipoint ++) {
+			TextPoint point = tier -> point (ipoint);
+			if (Melder_stringMatchesCriterion (point -> mark, which_Melder_STRING, criterion)) {
+				TextPoint preceding = ipoint <= 1 ? NULL : (TextPoint) tier -> points -> item [ipoint - 1];
+				if (Melder_stringMatchesCriterion (preceding -> mark, which_Melder_STRING_precededBy, criterion_precededBy)) {
+					PointProcess_addPoint (thee.peek(), point -> number);
+				}
+			}
+		}
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": points not converted to PointProcess.");
+	}
+}
+
+PointProcess TextGrid_getPoints_followed (TextGrid me, long tierNumber,
+	int which_Melder_STRING, const wchar_t *criterion,
+	int which_Melder_STRING_followedBy, const wchar_t *criterion_followedBy)
+{
+	try {
+		TextTier tier = TextGrid_checkSpecifiedTierIsPointTier (me, tierNumber);
+		autoPointProcess thee = PointProcess_create (my xmin, my xmax, 10);
+		for (long ipoint = 1; ipoint <= tier -> numberOfPoints (); ipoint ++) {
+			TextPoint point = tier -> point (ipoint);
+			if (Melder_stringMatchesCriterion (point -> mark, which_Melder_STRING, criterion)) {
+				TextPoint following = ipoint >= tier -> points -> size ? NULL : (TextPoint) tier -> points -> item [ipoint + 1];
+				if (Melder_stringMatchesCriterion (following -> mark, which_Melder_STRING_followedBy, criterion_followedBy)) {
+					PointProcess_addPoint (thee.peek(), point -> number);
+				}
+			}
+		}
+		return thee.transfer();
+	} catch (MelderError) {
+		Melder_throw (me, ": points not converted to PointProcess.");
+	}
+}
+
 PointProcess IntervalTier_PointProcess_startToCentre (IntervalTier tier, PointProcess point, double phase) {
 	try {
 		autoPointProcess thee = PointProcess_create (tier -> xmin, tier -> xmax, 10);
@@ -672,7 +774,7 @@ PointProcess IntervalTier_PointProcess_startToCentre (IntervalTier tier, PointPr
 			double t = point -> t [i];
 			long index = IntervalTier_timeToLowIndex (tier, t);
 			if (index) {
-				TextInterval interval = (TextInterval) tier -> intervals -> item [index];
+				TextInterval interval = tier -> interval (index);
 				if (interval -> xmin == t)
 					PointProcess_addPoint (thee.peek(), (1 - phase) * interval -> xmin + phase * interval -> xmax);
 			}
@@ -690,7 +792,7 @@ PointProcess IntervalTier_PointProcess_endToCentre (IntervalTier tier, PointProc
 			double t = point -> t [i];
 			long index = IntervalTier_timeToHighIndex (tier, t);
 			if (index) {
-				TextInterval interval = (TextInterval) tier -> intervals -> item [index];
+				TextInterval interval = tier -> interval (index);
 				if (interval -> xmax == t)
 					PointProcess_addPoint (thee.peek(), (1 - phase) * interval -> xmin + phase * interval -> xmax);
 			}
@@ -704,8 +806,8 @@ PointProcess IntervalTier_PointProcess_endToCentre (IntervalTier tier, PointProc
 TableOfReal IntervalTier_downto_TableOfReal (IntervalTier me, const wchar_t *label) {
 	try {
 		long n = 0;
-		for (long i = 1; i <= my intervals -> size; i ++) {
-			TextInterval interval = (TextInterval) my intervals -> item [i];
+		for (long i = 1; i <= my numberOfIntervals (); i ++) {
+			TextInterval interval = my interval (i);
 			if (label == NULL || (label [0] == '\0' && ! interval -> text) || (interval -> text && wcsequ (interval -> text, label)))
 				n ++;
 		}
@@ -714,8 +816,8 @@ TableOfReal IntervalTier_downto_TableOfReal (IntervalTier me, const wchar_t *lab
 		TableOfReal_setColumnLabel (thee.peek(), 2, L"End");
 		TableOfReal_setColumnLabel (thee.peek(), 3, L"Duration");
 		n = 0;
-		for (long i = 1; i <= my intervals -> size; i ++) {
-			TextInterval interval = (TextInterval) my intervals -> item [i];
+		for (long i = 1; i <= my numberOfIntervals (); i ++) {
+			TextInterval interval = my interval (i);
 			if (label == NULL || (label [0] == '\0' && ! interval -> text) || (interval -> text && wcsequ (interval -> text, label))) {
 				n ++;
 				TableOfReal_setRowLabel (thee.peek(), n, interval -> text ? interval -> text : L"");
@@ -745,8 +847,8 @@ TableOfReal TextTier_downto_TableOfReal (TextTier me, const wchar_t *label) {
 		autoTableOfReal thee = TableOfReal_create (n, 1);
 		TableOfReal_setColumnLabel (thee.peek(), 1, L"Time");
 		n = 0;
-		for (long i = 1; i <= my points -> size; i ++) {
-			TextPoint point = (TextPoint) my points -> item [i];
+		for (long i = 1; i <= my numberOfPoints (); i ++) {
+			TextPoint point = my point (i);
 			if (label == NULL || (label [0] == '\0' && ! point -> mark) || (point -> mark && wcsequ (point -> mark, label))) {
 				n ++;
 				TableOfReal_setRowLabel (thee.peek(), n, point -> mark ? point -> mark : L"");
@@ -935,22 +1037,21 @@ static void genericize (wchar_t **pstring, wchar_t *buffer) {
 
 void TextGrid_genericize (TextGrid me) {
 	try {
-		long ntier = my tiers -> size;
 		autostring buffer = Melder_calloc (wchar_t, TextGrid_maximumLabelLength (me) * 3 + 1);
-		for (long itier = 1; itier <= ntier; itier ++) {
-			Function anyTier = (Function) my tiers -> item [itier];
+		for (long itier = 1; itier <= my numberOfTiers (); itier ++) {
+			Function anyTier = my tier (itier);
 			if (anyTier -> classInfo == classIntervalTier) {
-				IntervalTier tier = (IntervalTier) anyTier;
-				long ninterval = tier -> intervals -> size;
-				for (long iinterval = 1; iinterval <= ninterval; iinterval ++) {
-					TextInterval interval = (TextInterval) tier -> intervals -> item [iinterval];
+				IntervalTier tier = static_cast <IntervalTier> (anyTier);
+				long n = tier -> numberOfIntervals ();
+				for (long i = 1; i <= n; i ++) {
+					TextInterval interval = tier -> interval (i);
 					genericize (& interval -> text, buffer.peek());
 				}
 			} else {
 				TextTier tier = (TextTier) anyTier;
 				long n = tier -> points -> size;
-				for (long ipoint = 1; ipoint <= n; ipoint ++) {
-					TextPoint point = (TextPoint) tier -> points -> item [ipoint];
+				for (long i = 1; i <= n; i ++) {
+					TextPoint point = tier -> point (i);
 					genericize (& point -> mark, buffer.peek());
 				}
 			}
@@ -962,25 +1063,24 @@ void TextGrid_genericize (TextGrid me) {
 
 void TextGrid_nativize (TextGrid me) {
 	try {
-		long ntier = my tiers -> size;
 		autostring buffer = Melder_calloc (wchar_t, TextGrid_maximumLabelLength (me) + 1);
-		for (long itier = 1; itier <= ntier; itier ++) {
-			Function anyTier = (Function) my tiers -> item [itier];
+		for (long itier = 1; itier <= my numberOfTiers (); itier ++) {
+			Function anyTier = my tier (itier);
 			if (anyTier -> classInfo == classIntervalTier) {
-				IntervalTier tier = (IntervalTier) anyTier;
-				long ninterval = tier -> intervals -> size;
-				for (long iinterval = 1; iinterval <= ninterval; iinterval ++) {
-					TextInterval interval = (TextInterval) tier -> intervals -> item [iinterval];
+				IntervalTier tier = static_cast <IntervalTier> (anyTier);
+				long n = tier -> numberOfIntervals ();
+				for (long i = 1; i <= n; i ++) {
+					TextInterval interval = tier -> interval (i);
 					if (interval -> text) {
 						Longchar_nativizeW (interval -> text, buffer.peek(), FALSE);
 						wcscpy (interval -> text, buffer.peek());
 					}
 				}
 			} else {
-				TextTier tier = (TextTier) anyTier;
-				long n = tier -> points -> size;
-				for (long ipoint = 1; ipoint <= n; ipoint ++) {
-					TextPoint point = (TextPoint) tier -> points -> item [ipoint];
+				TextTier tier = static_cast <TextTier> (anyTier);
+				long n = tier -> numberOfPoints ();
+				for (long i = 1; i <= n; i ++) {
+					TextPoint point = tier -> point (i);
 					if (point -> mark) {
 						Longchar_nativizeW (point -> mark, buffer.peek(), FALSE);
 						wcscpy (point -> mark, buffer.peek());
@@ -1002,29 +1102,29 @@ void TextPoint_removeText (TextPoint me) {
 }
 
 void IntervalTier_removeText (IntervalTier me) {
-	long ninterval = my intervals -> size;
+	long ninterval = my numberOfIntervals ();
 	for (long iinterval = 1; iinterval <= ninterval; iinterval ++)
-		TextInterval_removeText ((TextInterval) my intervals -> item [iinterval]);
+		TextInterval_removeText (my interval (iinterval));
 }
 
 void TextTier_removeText (TextTier me) {
-	long npoint = my points -> size;
+	long npoint = my numberOfPoints ();
 	for (long ipoint = 1; ipoint <= npoint; ipoint ++)
-		TextPoint_removeText ((TextPoint) my points -> item [ipoint]);
+		TextPoint_removeText (my point (ipoint));
 }
 
 void TextGrid_insertBoundary (TextGrid me, int tierNumber, double t) {
 	try {
-		TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
-		IntervalTier intervalTier = (IntervalTier) my tiers -> item [tierNumber];
-		if (intervalTier -> classInfo != classIntervalTier)
+		Function anyTier = TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
+		if (anyTier -> classInfo != classIntervalTier)
 			Melder_throw ("Cannot add a boundary on tier ", tierNumber, ", because that tier is not an interval tier.");
+		IntervalTier intervalTier = static_cast <IntervalTier> (anyTier);
 		if (IntervalTier_hasTime (intervalTier, t))
 			Melder_throw ("Cannot add a boundary at ", Melder_fixed (t, 6), " seconds, because there is already a boundary there.");
 		long intervalNumber = IntervalTier_timeToIndex (intervalTier, t);
 		if (intervalNumber == 0)
 			Melder_throw ("Cannot add a boundary at ", Melder_fixed (t, 6), " seconds, because this is outside the time domain of the intervals.");
-		TextInterval interval = (TextInterval) intervalTier -> intervals -> item [intervalNumber];
+		TextInterval interval = intervalTier -> interval (intervalNumber);
 		/*
 		 * Move the text to the left of the boundary.
 		 */
@@ -1036,12 +1136,12 @@ void TextGrid_insertBoundary (TextGrid me, int tierNumber, double t) {
 	}
 }
 
-void IntervalTier_removeLeftBoundary (IntervalTier me, long iinterval) {
+void IntervalTier_removeLeftBoundary (IntervalTier me, long intervalNumber) {
 	try {
-		Melder_assert (iinterval > 1);
-		Melder_assert (iinterval <= my intervals -> size);
-		TextInterval left = (TextInterval) my intervals -> item [iinterval - 1];
-		TextInterval right = (TextInterval) my intervals -> item [iinterval];
+		Melder_assert (intervalNumber > 1);
+		Melder_assert (intervalNumber <= my numberOfIntervals ());
+		TextInterval left = my interval (intervalNumber - 1);
+		TextInterval right = my interval (intervalNumber);
 		/*
 		 * Move the text to the left of the boundary.
 		 */
@@ -1056,7 +1156,7 @@ void IntervalTier_removeLeftBoundary (IntervalTier me, long iinterval) {
 			MelderString_append (& buffer, left -> text, right -> text);
 			TextInterval_setText (left, buffer.string);
 		}
-		Collection_removeItem (my intervals, iinterval);   // remove right interval
+		Collection_removeItem (my intervals, intervalNumber);   // remove right interval
 	} catch (MelderError) {
 		Melder_throw (me, ": left boundary not removed.");
 	}
@@ -1064,32 +1164,26 @@ void IntervalTier_removeLeftBoundary (IntervalTier me, long iinterval) {
 
 void TextGrid_removeBoundaryAtTime (TextGrid me, int tierNumber, double t) {
 	try {
-		TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
-		IntervalTier intervalTier = (IntervalTier) my tiers -> item [tierNumber];
-		if (intervalTier -> classInfo != classIntervalTier)
-			Melder_throw ("Tier ", tierNumber, " is not an interval tier.");
+		IntervalTier intervalTier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
 		if (! IntervalTier_hasTime (intervalTier, t))
 			Melder_throw ("There is no boundary at ", t, " seconds.");
-		long iinterval = IntervalTier_timeToIndex (intervalTier, t);
-		if (iinterval == 0)
+		long intervalNumber = IntervalTier_timeToIndex (intervalTier, t);
+		if (intervalNumber == 0)
 			Melder_throw ("The time of ", t, " seconds is outside the time domain of the intervals.");
-		if (iinterval == 1)
+		if (intervalNumber == 1)
 			Melder_throw ("The time of ", t, " seconds is at the left edge of the tier.");
-		IntervalTier_removeLeftBoundary (intervalTier, iinterval);
+		IntervalTier_removeLeftBoundary (intervalTier, intervalNumber);
 	} catch (MelderError) {
 		Melder_throw (me, ": boundary not removed.");
 	}
 }
 
-void TextGrid_setIntervalText (TextGrid me, int tierNumber, long iinterval, const wchar_t *text) {
+void TextGrid_setIntervalText (TextGrid me, int tierNumber, long intervalNumber, const wchar_t *text) {
 	try {
-		TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
-		IntervalTier intervalTier = (IntervalTier) my tiers -> item [tierNumber];
-		if (intervalTier -> classInfo != classIntervalTier)
-			Melder_throw ("Tier ", tierNumber, " is not an interval tier.");
-		if (iinterval < 1 || iinterval > intervalTier -> intervals -> size)
-			Melder_throw ("Interval ", iinterval, " does not exist on tier ", tierNumber, ".");
-		TextInterval interval = (TextInterval) intervalTier -> intervals -> item [iinterval];
+		IntervalTier intervalTier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
+		if (intervalNumber < 1 || intervalNumber > intervalTier -> numberOfIntervals ())
+			Melder_throw ("Interval ", intervalNumber, " does not exist on tier ", tierNumber, ".");
+		TextInterval interval = intervalTier -> interval (intervalNumber);
 		TextInterval_setText (interval, text);
 	} catch (MelderError) {
 		Melder_throw (me, ": interval text not set.");
@@ -1098,10 +1192,7 @@ void TextGrid_setIntervalText (TextGrid me, int tierNumber, long iinterval, cons
 
 void TextGrid_insertPoint (TextGrid me, int tierNumber, double t, const wchar_t *mark) {
 	try {
-		TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
-		TextTier textTier = (TextTier) my tiers -> item [tierNumber];
-		if (textTier -> classInfo != classTextTier)
-			Melder_throw ("Tier ", tierNumber, " is not a point tier.");
+		TextTier textTier = TextGrid_checkSpecifiedTierIsPointTier (me, tierNumber);
 		if (AnyTier_hasPoint (textTier, t))
 			Melder_throw ("There is already a point at ", t, " seconds.");
 		autoTextPoint newPoint = TextPoint_create (t, mark);
@@ -1116,15 +1207,27 @@ void TextTier_removePoint (TextTier me, long ipoint) {
 	Collection_removeItem (my points, ipoint);
 }
 
-void TextGrid_setPointText (TextGrid me, int tierNumber, long ipoint, const wchar_t *text) {
+void structTextTier :: removePoints (int which_Melder_STRING, const wchar_t *criterion) {
+	for (long i = our numberOfPoints (); i > 0; i --)
+		if (Melder_stringMatchesCriterion (our point (i) -> mark, which_Melder_STRING, criterion))
+			Collection_removeItem (our points, i);
+}
+
+void structTextGrid :: removePoints (long tierNumber, int which_Melder_STRING, const wchar_t *criterion) {
 	try {
-		TextGrid_checkSpecifiedTierNumberWithinRange (me, tierNumber);
-		TextTier textTier = (TextTier) my tiers -> item [tierNumber];
-		if (textTier -> classInfo != classTextTier)
-			Melder_throw ("Tier ", tierNumber, " is not a point tier.");
-		if (ipoint < 1 || ipoint > textTier -> points -> size)
-			Melder_throw ("Point ", ipoint, " does not exist on tier ", tierNumber, ".");
-		TextPoint point = (TextPoint) textTier -> points -> item [ipoint];
+		TextTier tier = TextGrid_checkSpecifiedTierIsPointTier (this, tierNumber);
+		tier -> removePoints (which_Melder_STRING, criterion);
+	} catch (MelderError) {
+		Melder_throw (this, ": points not removed.");
+	}
+}
+
+void TextGrid_setPointText (TextGrid me, int tierNumber, long pointNumber, const wchar_t *text) {
+	try {
+		TextTier textTier = TextGrid_checkSpecifiedTierIsPointTier (me, tierNumber);
+		if (pointNumber < 1 || pointNumber > textTier -> numberOfPoints ())
+			Melder_throw ("Point ", pointNumber, " does not exist on tier ", tierNumber, ".");
+		TextPoint point = textTier -> point (pointNumber);
 		TextPoint_setText (point, text);
 	} catch (MelderError) {
 		Melder_throw (me, ": point text not set.");
@@ -1224,14 +1327,14 @@ TextGrid TextGrid_readFromChronologicalTextFile (MelderFile file) {
 					throw;
 				}
 			}
-			TextGrid_checkSpecifiedTierNumberWithinRange (me.peek(), tierNumber);
-			if (((Function) my tiers -> item [tierNumber]) -> classInfo == classIntervalTier) {
-				IntervalTier tier = (IntervalTier) my tiers -> item [tierNumber];
+			Function anyTier = TextGrid_checkSpecifiedTierNumberWithinRange (me.peek(), tierNumber);
+			if (anyTier -> classInfo == classIntervalTier) {
+				IntervalTier tier = static_cast <IntervalTier> (anyTier);
 				autoTextInterval interval = Thing_new (TextInterval);
 				interval -> v_readText (text.peek());
 				Collection_addItem (tier -> intervals, interval.transfer());   // not earlier: sorting depends on contents of interval
 			} else {
-				TextTier tier = (TextTier) my tiers -> item [tierNumber];
+				TextTier tier = static_cast <TextTier> (anyTier);
 				autoTextPoint point = Thing_new (TextPoint);
 				point -> v_readText (text.peek());
 				Collection_addItem (tier -> points, point.transfer());   // not earlier: sorting depends on contents of point
diff --git a/fon/TextGrid.h b/fon/TextGrid.h
index 4ee3af1..51c64c7 100644
--- a/fon/TextGrid.h
+++ b/fon/TextGrid.h
@@ -2,7 +2,7 @@
 #define _TextGrid_h_
 /* TextGrid.h
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -72,8 +72,14 @@ PointProcess TextGrid_getStartingPoints (TextGrid me, long itier, int which_Meld
 PointProcess TextGrid_getEndPoints (TextGrid me, long itier, int which_Melder_STRING, const wchar_t *criterion);
 PointProcess TextGrid_getCentrePoints (TextGrid me, long itier, int which_Melder_STRING, const wchar_t *criterion);
 PointProcess TextGrid_getPoints (TextGrid me, long itier, int which_Melder_STRING, const wchar_t *criterion);
-
-void TextGrid_checkSpecifiedTierNumberWithinRange (TextGrid me, long tierNumber);
+PointProcess TextGrid_getPoints_preceded (TextGrid me, long tierNumber,
+	int which_Melder_STRING, const wchar_t *criterion,
+	int which_Melder_STRING_precededBy, const wchar_t *criterion_precededBy);
+PointProcess TextGrid_getPoints_followed (TextGrid me, long tierNumber,
+	int which_Melder_STRING, const wchar_t *criterion,
+	int which_Melder_STRING_followedBy, const wchar_t *criterion_followedBy);
+
+Function TextGrid_checkSpecifiedTierNumberWithinRange (TextGrid me, long tierNumber);
 IntervalTier TextGrid_checkSpecifiedTierIsIntervalTier (TextGrid me, long tierNumber);
 TextTier TextGrid_checkSpecifiedTierIsPointTier (TextGrid me, long tierNumber);
 
diff --git a/fon/TextGridEditor.cpp b/fon/TextGridEditor.cpp
index 831ad65..76e511c 100644
--- a/fon/TextGridEditor.cpp
+++ b/fon/TextGridEditor.cpp
@@ -1,6 +1,6 @@
 /* TextGridEditor.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -86,7 +86,7 @@ static void _TextGridEditor_timeToInterval (TextGridEditor me, double t, int iti
 		long iinterval = IntervalTier_timeToIndex (intervalTier, t);
 		TextInterval interval;
 		if (iinterval == 0) {
-			if (t < my d_tmin) {
+			if (t < my tmin) {
 				iinterval = 1;
 			} else {
 				iinterval = intervalTier -> intervals -> size;
@@ -98,16 +98,16 @@ static void _TextGridEditor_timeToInterval (TextGridEditor me, double t, int iti
 	} else {
 		long n = textTier -> points -> size;
 		if (n == 0) {
-			*tmin = my d_tmin;
-			*tmax = my d_tmax;
+			*tmin = my tmin;
+			*tmax = my tmax;
 		} else {
 			long ipointleft = AnyTier_timeToLowIndex (textTier, t);
-			*tmin = ipointleft == 0 ? my d_tmin : ((TextPoint) textTier -> points -> item [ipointleft]) -> number;
-			*tmax = ipointleft == n ? my d_tmax : ((TextPoint) textTier -> points -> item [ipointleft + 1]) -> number;
+			*tmin = ipointleft == 0 ? my tmin : ((TextPoint) textTier -> points -> item [ipointleft]) -> number;
+			*tmax = ipointleft == n ? my tmax : ((TextPoint) textTier -> points -> item [ipointleft + 1]) -> number;
 		}
 	}
-	if (*tmin < my d_tmin) *tmin = my d_tmin;   // clip by FunctionEditor's time domain
-	if (*tmax > my d_tmax) *tmax = my d_tmax;
+	if (*tmin < my tmin) *tmin = my tmin;   // clip by FunctionEditor's time domain
+	if (*tmax > my tmax) *tmax = my tmax;
 }
 
 static void checkTierSelection (TextGridEditor me, const wchar_t *verbPhrase) {
@@ -582,10 +582,11 @@ static void insertBoundaryOrPoint (TextGridEditor me, int itier, double t1, doub
 			 */
 			long left, right;
 			wchar_t *text = my text -> f_getStringAndSelectionPosition (& left, & right);
+			bool wholeTextIsSelected = right - left == wcslen (text);
 			rightNewInterval = TextInterval_create (t2, interval -> xmax, text + right);
 			text [right] = '\0';
 			midNewInterval = TextInterval_create (t1, t2, text + left);
-			text [left] = '\0';
+			if (! wholeTextIsSelected || t1 != t2) text [left] = '\0';
 			TextInterval_setText (interval, text);
 			Melder_free (text);
 		} else {
@@ -1339,7 +1340,7 @@ static void gui_text_cb_change (I, GuiTextEvent event) {
 
 void structTextGridEditor :: v_createChildren () {
 	TextGridEditor_Parent :: v_createChildren ();
-	text -> f_setChangeCallback (gui_text_cb_change, this);
+	if (text) text -> f_setChangeCallback (gui_text_cb_change, this);
 }
 
 void structTextGridEditor :: v_dataChanged () {
@@ -1435,7 +1436,7 @@ static void do_drawIntervalTier (TextGridEditor me, IntervalTier tier, int itier
 	for (iinterval = 1; iinterval <= ninterval; iinterval ++) {
 		TextInterval interval = (TextInterval) tier -> intervals -> item [iinterval];
 		double tmin = interval -> xmin, tmax = interval -> xmax;
-		if (tmin < my d_tmin) tmin = my d_tmin; if (tmax > my d_tmax) tmax = my d_tmax;
+		if (tmin < my tmin) tmin = my tmin; if (tmax > my tmax) tmax = my tmax;
 		if (tmin >= tmax) continue;
 		bool intervalIsSelected = selectedInterval == iinterval;
 
@@ -1700,7 +1701,7 @@ static void do_dragBoundary (TextGridEditor me, double xbegin, int iClickedTier,
 	TextGrid grid = (TextGrid) my data;
 	int itier, numberOfTiers = grid -> tiers -> size, itierDrop;
 	double xWC = xbegin, yWC;
-	double leftDraggingBoundary = my d_tmin, rightDraggingBoundary = my d_tmax;   // initial dragging range
+	double leftDraggingBoundary = my tmin, rightDraggingBoundary = my tmax;   // initial dragging range
 	int selectedTier [100];
 	double soundY = _TextGridEditor_computeSoundY (me);
 
@@ -2102,12 +2103,14 @@ void structTextGridEditor :: v_updateText () {
 		}
 	}
 	//Melder_casual ("v_updateText in editor %ld %ls %d", this, name, (int) suppressRedraw);
-	suppressRedraw = TRUE;   // prevent valueChangedCallback from redrawing
-	trace ("setting new text %ls", newText);
-	text -> f_setString (newText);
-	long cursor = wcslen (newText);   // at end
-	text -> f_setSelection (cursor, cursor);
-	suppressRedraw = FALSE;
+	if (text) {
+		suppressRedraw = TRUE;   // prevent valueChangedCallback from redrawing
+		trace ("setting new text %ls", newText);
+		text -> f_setString (newText);
+		long cursor = wcslen (newText);   // at end
+		text -> f_setSelection (cursor, cursor);
+		suppressRedraw = FALSE;
+	}
 }
 
 void structTextGridEditor :: v_prefs_addFields (EditorCommand cmd) {
@@ -2156,9 +2159,8 @@ void structTextGridEditor :: v_createMenuItems_view_timeDomain (EditorMenu menu)
 
 void structTextGridEditor :: v_highlightSelection (double left, double right, double bottom, double top) {
 	if (v_hasAnalysis () && p_spectrogram_show && (d_longSound.data || d_sound.data)) {
-		TextGrid grid = (TextGrid) data;
 		double soundY = _TextGridEditor_computeSoundY (this), soundY2 = 0.5 * (1.0 + soundY);
-		Graphics_highlight (d_graphics, left, right, bottom, soundY * top + (1 - soundY) * bottom);
+		//Graphics_highlight (d_graphics, left, right, bottom, soundY * top + (1 - soundY) * bottom);
 		Graphics_highlight (d_graphics, left, right, soundY2 * top + (1 - soundY2) * bottom, top);
 	} else {
 		Graphics_highlight (d_graphics, left, right, bottom, top);
@@ -2167,9 +2169,8 @@ void structTextGridEditor :: v_highlightSelection (double left, double right, do
 
 void structTextGridEditor :: v_unhighlightSelection (double left, double right, double bottom, double top) {
 	if (v_hasAnalysis () && p_spectrogram_show && (d_longSound.data || d_sound.data)) {
-		TextGrid grid = (TextGrid) data;
 		double soundY = _TextGridEditor_computeSoundY (this), soundY2 = 0.5 * (1.0 + soundY);
-		Graphics_unhighlight (d_graphics, left, right, bottom, soundY * top + (1 - soundY) * bottom);
+		//Graphics_unhighlight (d_graphics, left, right, bottom, soundY * top + (1 - soundY) * bottom);
 		Graphics_unhighlight (d_graphics, left, right, soundY2 * top + (1 - soundY2) * bottom, top);
 	} else {
 		Graphics_unhighlight (d_graphics, left, right, bottom, top);
@@ -2197,28 +2198,34 @@ void structTextGridEditor :: v_updateMenuItems_file () {
 
 /********** EXPORTED **********/
 
-void structTextGridEditor :: f_init (const wchar_t *title, TextGrid grid, Sampled a_sound, bool a_ownSound, SpellingChecker a_spellingChecker)
+void TextGridEditor_init (TextGridEditor me, const wchar_t *title, TextGrid grid, Sampled sound, bool ownSound, SpellingChecker spellingChecker, const char *callbackSocket)
 {
-	this -> spellingChecker = a_spellingChecker;   // set in time
+	my spellingChecker = spellingChecker;   // set in time
 
-	structTimeSoundAnalysisEditor :: f_init (title, grid, a_sound, a_ownSound);
+	my structTimeSoundAnalysisEditor :: f_init (title, grid, sound, ownSound);
 
-	this -> selectedTier = 1;
-	v_updateText ();   // to reflect changed tier selection
-	if (d_endWindow - d_startWindow > 30.0) {
-		d_endWindow = d_startWindow + 30.0;
-		if (d_startWindow == d_tmin)
-			d_startSelection = d_endSelection = 0.5 * (d_startWindow + d_endWindow);
-		FunctionEditor_marksChanged (this, false);
-	}
-	if (a_spellingChecker != NULL)
-		this -> text -> f_setSelection (0, 0);
-}
-
-TextGridEditor TextGridEditor_create (const wchar_t *title, TextGrid grid, Sampled sound, bool ownSound, SpellingChecker spellingChecker) {
+	my selectedTier = 1;
+	my v_updateText ();   // to reflect changed tier selection
+	if (my d_endWindow - my d_startWindow > 30.0) {
+		my d_endWindow = my d_startWindow + 30.0;
+		if (my d_startWindow == my tmin)
+			my d_startSelection = my d_endSelection = 0.5 * (my d_startWindow + my d_endWindow);
+		FunctionEditor_marksChanged (me, false);
+	}
+	if (spellingChecker != NULL)
+		my text -> f_setSelection (0, 0);
+	if (sound && sound -> xmin == 0.0 && grid -> xmin != 0.0 && grid -> xmax > sound -> xmax)
+		Melder_warning ("The time domain of the TextGrid (starting at ",
+			Melder_fixed (grid -> xmin, 6), " seconds) does not overlap with that of the sound "
+			"(which starts at 0 seconds).\nIf you want to repair this, you can select the TextGrid "
+			"and choose “Shift times to...” from the Modify menu "
+			"to shift the starting time of the TextGrid to zero.");
+}
+
+TextGridEditor TextGridEditor_create (const wchar_t *title, TextGrid grid, Sampled sound, bool ownSound, SpellingChecker spellingChecker, const char *callbackSocket) {
 	try {
 		autoTextGridEditor me = Thing_new (TextGridEditor);
-		my f_init (title, grid, sound, ownSound, spellingChecker);
+		TextGridEditor_init (me.peek(), title, grid, sound, ownSound, spellingChecker, callbackSocket);
 		return me.transfer();
 	} catch (MelderError) {
 		Melder_throw ("TextGrid window not created.");
diff --git a/fon/TextGridEditor.h b/fon/TextGridEditor.h
index d11a673..208803c 100644
--- a/fon/TextGridEditor.h
+++ b/fon/TextGridEditor.h
@@ -2,7 +2,7 @@
 #define _TextGridEditor_h_
 /* TextGridEditor.h
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2014 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
@@ -34,12 +34,6 @@ Thing_define (TextGridEditor, TimeSoundAnalysisEditor) {
 		bool suppressRedraw;
 		wchar_t *findString;
 		GuiMenuItem extractSelectedTextGridPreserveTimesButton, extractSelectedTextGridTimeFromZeroButton;
-	// functions:
-	public:
-		void f_init (const wchar_t *title, TextGrid grid,
-			Sampled sound,   // either a Sound or a LongSound, or null
-			bool ownSound,
-			SpellingChecker spellingChecker);
 	// overridden methods:
 		virtual void v_info ();
 		virtual void v_createChildren ();
@@ -70,10 +64,13 @@ Thing_define (TextGridEditor, TimeSoundAnalysisEditor) {
 	#include "TextGridEditor_prefs.h"
 };
 
+void TextGridEditor_init (TextGridEditor me, const wchar_t *title, TextGrid grid, Sampled sound, bool ownSound, SpellingChecker spellingChecker, const char *callbackSocket);
+
 TextGridEditor TextGridEditor_create (const wchar_t *title, TextGrid grid,
 	Sampled sound,   // either a Sound or a LongSound, or null
 	bool ownSound,
-	SpellingChecker spellingChecker);
+	SpellingChecker spellingChecker,
+	const char *callbackSocket);
 
 /* End of file TextGridEditor.h */
 #endif
diff --git a/fon/TextGrid_Sound.cpp b/fon/TextGrid_Sound.cpp
index fb22c3a..c01bf4a 100644
--- a/fon/TextGrid_Sound.cpp
+++ b/fon/TextGrid_Sound.cpp
@@ -1,6 +1,6 @@
 /* TextGrid_Sound.cpp
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -40,7 +40,7 @@ static void IntervalTier_insertIntervalDestructively (IntervalTier me, double tm
 		long intervalNumber = IntervalTier_timeToIndex (me, tmin);
 		if (intervalNumber == 0)
 			Melder_throw ("Cannot add a boundary at ", Melder_fixed (tmin, 6), " seconds, because this is outside the time domain of the intervals.");
-		Thing_cast (TextInterval, interval, my intervals -> item [intervalNumber]);
+		TextInterval interval = my interval (intervalNumber);
 		/*
 		 * Move the text to the left of the boundary.
 		 */
@@ -55,7 +55,7 @@ static void IntervalTier_insertIntervalDestructively (IntervalTier me, double tm
 		long intervalNumber = IntervalTier_timeToIndex (me, tmax);
 		if (intervalNumber == 0)
 			Melder_throw ("Cannot add a boundary at ", Melder_fixed (tmin, 6), " seconds, because this is outside the time domain of the intervals.");
-		Thing_cast (TextInterval, interval, my intervals -> item [intervalNumber]);
+		TextInterval interval = my interval (intervalNumber);
 		/*
 		 * Move the text to the right of the boundary.
 		 */
@@ -70,7 +70,7 @@ static void IntervalTier_insertIntervalDestructively (IntervalTier me, double tm
 	 */
 	trace ("Empty interval %ld down to %ld.", lastIntervalNumber, firstIntervalNumber);
 	for (long iinterval = lastIntervalNumber; iinterval >= firstIntervalNumber; iinterval --) {
-		Thing_cast (TextInterval, interval, my intervals -> item [iinterval]);
+		TextInterval interval = my interval (iinterval);
 		if (interval -> xmin > tmin && interval -> xmin < tmax) {
 			Melder_assert (iinterval > 1);
 			TextInterval previous = (TextInterval) my intervals -> item [iinterval - 1];
@@ -87,7 +87,7 @@ static void IntervalTier_insertIntervalDestructively (IntervalTier me, double tm
 static double IntervalTier_boundaryTimeClosestTo (IntervalTier me, double tmin, double tmax) {
 	long intervalNumber = IntervalTier_timeToLowIndex (me, tmax);
 	if (intervalNumber != 0) {
-		TextInterval interval = static_cast <TextInterval> (my intervals -> item [intervalNumber]);
+		TextInterval interval = my interval (intervalNumber);
 		if (interval -> xmin > tmin && interval -> xmin < tmax) {
 			return interval -> xmin;
 		}
@@ -98,18 +98,18 @@ static double IntervalTier_boundaryTimeClosestTo (IntervalTier me, double tmin,
 static void IntervalTier_removeEmptyIntervals (IntervalTier me, IntervalTier boss) {
 	IntervalTier_removeBoundariesBetweenIdenticallyLabeledIntervals (me, L"");
 	if (my intervals -> size < 2) return;
-	Thing_cast (TextInterval, firstInterval, my intervals -> item [1]);
+	TextInterval firstInterval = my interval (1);
 	if (Melder_wcsequ (firstInterval -> text, L"")) {
 		IntervalTier_removeLeftBoundary (me, 2);
 	}
-	if (my intervals -> size < 2) return;
-	Thing_cast (TextInterval, lastInterval, my intervals -> item [my intervals -> size]);
+	if (my numberOfIntervals () < 2) return;
+	TextInterval lastInterval = my interval (my numberOfIntervals ());
 	if (Melder_wcsequ (lastInterval -> text, L"")) {
-		IntervalTier_removeLeftBoundary (me, my intervals -> size);
+		IntervalTier_removeLeftBoundary (me, my numberOfIntervals ());
 	}
-	if (my intervals -> size < 3) return;
-	for (long iinterval = my intervals -> size - 1; iinterval >= 2; iinterval --) {
-		Thing_cast (TextInterval, interval, my intervals -> item [iinterval]);
+	if (my numberOfIntervals () < 3) return;
+	for (long iinterval = my numberOfIntervals () - 1; iinterval >= 2; iinterval --) {
+		TextInterval interval = my interval (iinterval);
 		if (Melder_wcsequ (interval -> text, L"")) {
 			/*
 			 * Distribute the empty interval between its neigbours.
@@ -118,8 +118,8 @@ static void IntervalTier_removeEmptyIntervals (IntervalTier me, IntervalTier bos
 				boss ?
 				IntervalTier_boundaryTimeClosestTo (boss, interval -> xmin, interval -> xmax) :
 				0.5 * (interval -> xmin + interval -> xmax);
-			Thing_cast (TextInterval, previous, my intervals -> item [iinterval - 1]);
-			Thing_cast (TextInterval, next, my intervals -> item [iinterval + 1]);
+			TextInterval previous = my interval (iinterval - 1);
+			TextInterval next = my interval (iinterval + 1);
 			previous -> xmax = newBoundaryTime;
 			next -> xmin = newBoundaryTime;
 			Collection_removeItem (my intervals, iinterval);
@@ -129,11 +129,10 @@ static void IntervalTier_removeEmptyIntervals (IntervalTier me, IntervalTier bos
 
 void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierNumber, long intervalNumber, const wchar_t *languageName, bool includeWords, bool includePhonemes) {
 	try {
-		TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
-		IntervalTier headTier = (IntervalTier) my tiers -> item [tierNumber];
-		if (intervalNumber < 1 || intervalNumber > headTier -> intervals -> size)
+		IntervalTier headTier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
+		if (intervalNumber < 1 || intervalNumber > headTier -> numberOfIntervals ())
 			Melder_throw ("Interval ", intervalNumber, " does not exist.");
-		TextInterval interval = (TextInterval) headTier -> intervals -> item [intervalNumber];
+		TextInterval interval = headTier -> interval (intervalNumber);
 		if (! includeWords && ! includePhonemes)
 			Melder_throw ("Nothing to be done, because you asked neither for word alignment nor for phoneme alignment.");
 		if (wcsstr (headTier -> name, L"/") )
@@ -159,10 +158,10 @@ void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierN
 			 */
 			Melder_assert (analysis -> xmin == interval -> xmin);
 			Melder_assert (analysis -> xmax == interval -> xmax);
-			Melder_assert (analysis -> tiers -> size == 4);
-			Thing_cast (IntervalTier, analysisWordTier, analysis -> tiers -> item [3]);
+			Melder_assert (analysis -> numberOfTiers () == 4);
+			Thing_cast (IntervalTier, analysisWordTier, analysis -> tier (3));
 			IntervalTier_removeEmptyIntervals (analysisWordTier, NULL);
-			Thing_cast (IntervalTier, analysisPhonemeTier, analysis -> tiers -> item [4]);
+			Thing_cast (IntervalTier, analysisPhonemeTier, analysis -> tier (4));
 			IntervalTier_removeEmptyIntervals (analysisPhonemeTier, analysisWordTier);
 		}
 		long wordTierNumber = 0, phonemeTierNumber = 0;
@@ -177,8 +176,8 @@ void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierN
 			autoMelderString newWordTierName;
 			MelderString_copy (& newWordTierName, headTier -> name);
 			MelderString_append (& newWordTierName, L"/word");
-			for (long itier = 1; itier <= my tiers -> size; itier ++) {
-				IntervalTier tier = static_cast <IntervalTier> (my tiers -> item [itier]);
+			for (long itier = 1; itier <= my numberOfTiers (); itier ++) {
+				IntervalTier tier = static_cast <IntervalTier> (my tier (itier));
 				if (Melder_wcsequ (newWordTierName.string, tier -> name)) {
 					if (tier -> classInfo != classIntervalTier)
 						Melder_throw ("A tier with the prospective word tier name (", tier -> name, ") already exists, but it is not an interval tier."
@@ -193,7 +192,7 @@ void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierN
 				Ordered_addItemPos (my tiers, newWordTier.transfer(), wordTierNumber = tierNumber + 1);
 			}
 			Melder_assert (wordTierNumber >= 1 && wordTierNumber <= my tiers -> size);
-			wordTier = static_cast <IntervalTier> (my tiers -> item [wordTierNumber]);
+			wordTier = static_cast <IntervalTier> (my tier (wordTierNumber));
 			/*
 			 * Make sure that the word tier has boundaries at the edges of the interval.
 			 */
@@ -204,16 +203,16 @@ void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierN
 			long wordIntervalNumber = IntervalTier_hasTime (wordTier, interval -> xmin);
 			Melder_assert (wordIntervalNumber != 0);
 			if (analysis.peek()) {
-				Thing_cast (IntervalTier, analysisWordTier, analysis -> tiers -> item [3]);
-				for (long ianalysisInterval = 1; ianalysisInterval <= analysisWordTier -> intervals -> size; ianalysisInterval ++) {
-					Thing_cast (TextInterval, analysisInterval, analysisWordTier -> intervals -> item [ianalysisInterval]);
+				Thing_cast (IntervalTier, analysisWordTier, analysis -> tier (3));
+				for (long ianalysisInterval = 1; ianalysisInterval <= analysisWordTier -> numberOfIntervals (); ianalysisInterval ++) {
+					TextInterval analysisInterval = analysisWordTier -> interval (ianalysisInterval);
 					TextInterval wordInterval = NULL;
 					double tmin = analysisInterval -> xmin, tmax = analysisInterval -> xmax;
 					if (tmax == analysis -> xmax) {
-						wordInterval = (TextInterval) wordTier -> intervals -> item [wordIntervalNumber];
+						wordInterval = wordTier -> interval (wordIntervalNumber);
 						TextInterval_setText (wordInterval, analysisInterval -> text);
 					} else {
-						wordInterval = (TextInterval) wordTier -> intervals -> item [wordIntervalNumber];
+						wordInterval = wordTier -> interval (wordIntervalNumber);
 						autoTextInterval newInterval = TextInterval_create (tmin, tmax, analysisInterval -> text);
 						wordInterval -> xmin = tmax;
 						Collection_addItem (wordTier -> intervals, newInterval.transfer());
@@ -232,8 +231,8 @@ void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierN
 			autoMelderString newPhonemeTierName;
 			MelderString_copy (& newPhonemeTierName, headTier -> name);
 			MelderString_append (& newPhonemeTierName, L"/phon");
-			for (long itier = 1; itier <= my tiers -> size; itier ++) {
-				IntervalTier tier = static_cast <IntervalTier> (my tiers -> item [itier]);
+			for (long itier = 1; itier <= my numberOfTiers (); itier ++) {
+				IntervalTier tier = static_cast <IntervalTier> (my tier (itier));
 				if (Melder_wcsequ (newPhonemeTierName.string, tier -> name)) {
 					if (tier -> classInfo != classIntervalTier)
 						Melder_throw ("A tier with the prospective phoneme tier name (", tier -> name, ") already exists, but it is not an interval tier."
@@ -260,15 +259,15 @@ void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierN
 			Melder_assert (phonemeIntervalNumber != 0);
 			if (analysis.peek()) {
 				Thing_cast (IntervalTier, analysisPhonemeTier, analysis -> tiers -> item [4]);
-				for (long ianalysisInterval = 1; ianalysisInterval <= analysisPhonemeTier -> intervals -> size; ianalysisInterval ++) {
-					Thing_cast (TextInterval, analysisInterval, analysisPhonemeTier -> intervals -> item [ianalysisInterval]);
+				for (long ianalysisInterval = 1; ianalysisInterval <= analysisPhonemeTier -> numberOfIntervals (); ianalysisInterval ++) {
+					TextInterval analysisInterval = analysisPhonemeTier -> interval (ianalysisInterval);
 					TextInterval phonemeInterval = NULL;
 					double tmin = analysisInterval -> xmin, tmax = analysisInterval -> xmax;
 					if (tmax == analysis -> xmax) {
-						phonemeInterval = (TextInterval) phonemeTier -> intervals -> item [phonemeIntervalNumber];
+						phonemeInterval = phonemeTier -> interval (phonemeIntervalNumber);
 						TextInterval_setText (phonemeInterval, analysisInterval -> text);
 					} else {
-						phonemeInterval = (TextInterval) phonemeTier -> intervals -> item [phonemeIntervalNumber];
+						phonemeInterval = phonemeTier -> interval (phonemeIntervalNumber);
 						autoTextInterval newInterval = TextInterval_create (tmin, tmax, analysisInterval -> text);
 						phonemeInterval -> xmin = tmax;
 						Collection_addItem (phonemeTier -> intervals, newInterval.transfer());
@@ -289,9 +288,9 @@ void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierN
 }
 
 void TextGrid_Sound_draw (TextGrid me, Sound sound, Graphics g, double tmin, double tmax,
-	int showBoundaries, int useTextStyles, int garnish)   // STEREO BUG
+	bool showBoundaries, bool useTextStyles, bool garnish)   // STEREO BUG
 {
-	long numberOfTiers = my tiers -> size;
+	long numberOfTiers = my numberOfTiers ();
 
 	/*
 	 * Automatic windowing:
@@ -310,7 +309,7 @@ void TextGrid_Sound_draw (TextGrid me, Sound sound, Graphics g, double tmin, dou
 		Graphics_line (g, tmin, 0.0, tmax, 0.0);
 		Graphics_setLineType (g, Graphics_DRAWN);      
 		Graphics_function (g, sound -> z [1], first, last,
-			Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
+			sound -> f_indexToX (first), sound -> f_indexToX (last));
 	}
 
 	/*
@@ -322,14 +321,14 @@ void TextGrid_Sound_draw (TextGrid me, Sound sound, Graphics g, double tmin, dou
 	Graphics_setCircumflexIsSuperscript (g, useTextStyles);
 	Graphics_setUnderscoreIsSubscript (g, useTextStyles);
 	for (long itier = 1; itier <= numberOfTiers; itier ++) {
-		Function anyTier = (Function) my tiers -> item [itier];
+		Function anyTier = my tier (itier);
 		double ymin = -1.0 - 0.5 * itier, ymax = ymin + 0.5;
 		Graphics_rectangle (g, tmin, tmax, ymin, ymax);
 		if (anyTier -> classInfo == classIntervalTier) {
-			IntervalTier tier = (IntervalTier) anyTier;
-			long ninterval = tier -> intervals -> size;
+			IntervalTier tier = static_cast <IntervalTier> (anyTier);
+			long ninterval = tier -> numberOfIntervals ();
 			for (long iinterval = 1; iinterval <= ninterval; iinterval ++) {
-				TextInterval interval = (TextInterval) tier -> intervals -> item [iinterval];
+				TextInterval interval = tier -> interval (iinterval);
 				double intmin = interval -> xmin, intmax = interval -> xmax;
 				if (intmin < tmin) intmin = tmin;
 				if (intmax > tmax) intmax = tmax;
@@ -349,10 +348,10 @@ void TextGrid_Sound_draw (TextGrid me, Sound sound, Graphics g, double tmin, dou
 				}
 			}
 		} else {
-			TextTier tier = (TextTier) anyTier;
-			long numberOfPoints = tier -> points -> size;
+			TextTier tier = static_cast <TextTier> (anyTier);
+			long numberOfPoints = tier -> numberOfPoints ();
 			for (long ipoint = 1; ipoint <= numberOfPoints; ipoint ++) {
-				TextPoint point = (TextPoint) tier -> points -> item [ipoint];
+				TextPoint point = tier -> point (ipoint);
 				double t = point -> number;
 				if (t > tmin && t < tmax) {
 					if (showBoundaries) {
@@ -383,9 +382,9 @@ void TextGrid_Sound_draw (TextGrid me, Sound sound, Graphics g, double tmin, dou
 Collection TextGrid_Sound_extractAllIntervals (TextGrid me, Sound sound, long tierNumber, int preserveTimes) {
 	try {
 		IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
-		autoCollection collection = Collection_create (NULL, tier -> intervals -> size);
-		for (long iseg = 1; iseg <= tier -> intervals -> size; iseg ++) {
-			TextInterval segment = (TextInterval) tier -> intervals -> item [iseg];
+		autoCollection collection = Collection_create (NULL, tier -> numberOfIntervals ());
+		for (long iseg = 1; iseg <= tier -> numberOfIntervals (); iseg ++) {
+			TextInterval segment = tier -> interval (iseg);
 			autoSound interval = Sound_extractPart (sound, segment -> xmin, segment -> xmax, kSound_windowShape_RECTANGULAR, 1.0, preserveTimes);
 			Thing_setName (interval.peek(), segment -> text ? segment -> text : L"untitled");
 			Collection_addItem (collection.peek(), interval.transfer()); 
@@ -399,9 +398,9 @@ Collection TextGrid_Sound_extractAllIntervals (TextGrid me, Sound sound, long ti
 Collection TextGrid_Sound_extractNonemptyIntervals (TextGrid me, Sound sound, long tierNumber, int preserveTimes) {
 	try {
 		IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
-		autoCollection collection = Collection_create (NULL, tier -> intervals -> size);
-		for (long iseg = 1; iseg <= tier -> intervals -> size; iseg ++) {
-			TextInterval segment = (TextInterval) tier -> intervals -> item [iseg];
+		autoCollection collection = Collection_create (NULL, tier -> numberOfIntervals ());
+		for (long iseg = 1; iseg <= tier -> numberOfIntervals (); iseg ++) {
+			TextInterval segment = tier -> interval (iseg);
 			if (segment -> text != NULL && segment -> text [0] != '\0') {
 				autoSound interval = Sound_extractPart (sound, segment -> xmin, segment -> xmax, kSound_windowShape_RECTANGULAR, 1.0, preserveTimes);
 				Thing_setName (interval.peek(), segment -> text ? segment -> text : L"untitled");
@@ -420,10 +419,10 @@ Collection TextGrid_Sound_extractIntervalsWhere (TextGrid me, Sound sound, long
 {
 	try {
 		IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
-		autoCollection collection = Collection_create (NULL, tier -> intervals -> size);
+		autoCollection collection = Collection_create (NULL, tier -> numberOfIntervals ());
 		long count = 0;
-		for (long iseg = 1; iseg <= tier -> intervals -> size; iseg ++) {
-			TextInterval segment = (TextInterval) tier -> intervals -> item [iseg];
+		for (long iseg = 1; iseg <= tier -> numberOfIntervals (); iseg ++) {
+			TextInterval segment = tier -> interval (iseg);
 			if (Melder_stringMatchesCriterion (segment -> text, comparison_Melder_STRING, text)) {
 				autoSound interval = Sound_extractPart (sound, segment -> xmin, segment -> xmax, kSound_windowShape_RECTANGULAR, 1.0, preserveTimes);
 				wchar_t name [1000];
@@ -570,7 +569,7 @@ void TextGrid_Pitch_draw (TextGrid grid, Pitch pitch, Graphics g,
 	double fontSize, int useTextStyles, int horizontalAlignment, int garnish, int speckle, int unit)
 {
 	try {
-		TextGrid_checkSpecifiedTierNumberWithinRange (grid, tierNumber);
+		Function anyTier = TextGrid_checkSpecifiedTierNumberWithinRange (grid, tierNumber);
 		double oldFontSize = Graphics_inqFontSize (g);
 		Pitch_draw (pitch, g, tmin, tmax, fmin, fmax, garnish, speckle, unit);
 		if (tmax <= tmin) tmin = grid -> xmin, tmax = grid -> xmax;
@@ -586,11 +585,10 @@ void TextGrid_Pitch_draw (TextGrid grid, Pitch pitch, Graphics g,
 		Graphics_setNumberSignIsBold (g, useTextStyles);
 		Graphics_setCircumflexIsSuperscript (g, useTextStyles);
 		Graphics_setUnderscoreIsSubscript (g, useTextStyles);
-		Function anyTier = (Function) grid -> tiers -> item [tierNumber];
 		if (anyTier -> classInfo == classIntervalTier) {
-			IntervalTier tier = (IntervalTier) anyTier;
-			for (long i = 1; i <= tier -> intervals -> size; i ++) {
-				TextInterval interval = (TextInterval) tier -> intervals -> item [i];
+			IntervalTier tier = static_cast <IntervalTier> (anyTier);
+			for (long i = 1; i <= tier -> numberOfIntervals (); i ++) {
+				TextInterval interval = tier -> interval (i);
 				double tleft = interval -> xmin, tright = interval -> xmax, tmid, f0;
 				if (! interval -> text || ! interval -> text [0]) continue;
 				if (tleft < pitch -> xmin) tleft = pitch -> xmin;
@@ -604,9 +602,9 @@ void TextGrid_Pitch_draw (TextGrid grid, Pitch pitch, Graphics g,
 					f0, interval -> text);
 			}
 		} else {
-			TextTier tier = (TextTier) anyTier;
-			for (long i = 1; i <= tier -> points -> size; i ++) {
-				TextPoint point = (TextPoint) tier -> points -> item [i];
+			TextTier tier = static_cast <TextTier> (anyTier);
+			for (long i = 1; i <= tier -> numberOfPoints (); i ++) {
+				TextPoint point = tier -> point (i);
 				double t = point -> number, f0;
 				if (! point -> mark || ! point -> mark [0]) continue;
 				if (t < tmin || t > tmax) continue;
diff --git a/fon/TextGrid_Sound.h b/fon/TextGrid_Sound.h
index 4b055f7..2059ff5 100644
--- a/fon/TextGrid_Sound.h
+++ b/fon/TextGrid_Sound.h
@@ -1,6 +1,6 @@
 /* TextGrid_Sound.h
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -22,7 +22,7 @@
 #include "Pitch.h"
 
 void TextGrid_Sound_draw (TextGrid me, Sound sound, Graphics g, double tmin, double tmax,
-	int showBoundaries, int useTextStyles, int garnish);
+	bool showBoundaries, bool useTextStyles, bool garnish);
 Collection TextGrid_Sound_extractAllIntervals (TextGrid me, Sound sound, long itier, int preserveTimes);
 Collection TextGrid_Sound_extractNonemptyIntervals (TextGrid me, Sound sound, long itier, int preserveTimes);
 Collection TextGrid_Sound_extractIntervalsWhere (TextGrid me, Sound sound,
diff --git a/fon/TextGrid_def.h b/fon/TextGrid_def.h
index d341cff..cb2ec91 100644
--- a/fon/TextGrid_def.h
+++ b/fon/TextGrid_def.h
@@ -1,6 +1,6 @@
 /* TextGrid_def.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -53,7 +53,9 @@ oo_DEFINE_CLASS (TextTier, Function)
 	oo_COLLECTION (SortedSetOfDouble, points, TextPoint, 0)
 
 	#if oo_DECLARING
-		TextPoint f_item (long i) { return static_cast <TextPoint> (points -> item [i]); }
+		long numberOfPoints () { return our points -> size; }
+		TextPoint point (long i) { return static_cast <TextPoint> (our points -> item [i]); }
+		void removePoints (int which_Melder_STRING, const wchar_t *criterion);
 		virtual int v_domainQuantity () { return MelderQuantity_TIME_SECONDS; }
 		virtual void v_shiftX (double xfrom, double xto);
 		virtual void v_scaleX (double xminfrom, double xmaxfrom, double xminto, double xmaxto);
@@ -69,8 +71,9 @@ oo_DEFINE_CLASS (IntervalTier, Function)
 	oo_COLLECTION (SortedSetOfDouble, intervals, TextInterval, 0)
 
 	#if oo_DECLARING
-		TextInterval f_item (long i) { return static_cast <TextInterval> (intervals -> item [i]); }
-		//TextInterval operator[] (long i) { return static_cast <TextInterval> (intervals -> item [i]); }   // oops: operator[] not for pointer objects
+		long numberOfIntervals () { return our intervals -> size; }
+		TextInterval interval (long i) { return static_cast <TextInterval> (our intervals -> item [i]); }
+		// TextInterval operator[] (long i) { return static_cast <TextInterval> (our intervals -> item [i]); }   // oops: operator[] not for pointer objects
 		virtual int v_domainQuantity () { return MelderQuantity_TIME_SECONDS; }
 		virtual void v_shiftX (double xfrom, double xto);
 		virtual void v_scaleX (double xminfrom, double xmaxfrom, double xminto, double xmaxto);
@@ -86,6 +89,9 @@ oo_DEFINE_CLASS (TextGrid, Function)
 	oo_OBJECT (Ordered, 0, tiers)   // TextTier and IntervalTier objects
 
 	#if oo_DECLARING
+		long numberOfTiers () { return our tiers -> size; }
+		Function tier (long i) { return static_cast <Function> (our tiers -> item [i]); }
+		void removePoints (long tierNumber, int which_Melder_STRING, const wchar_t *criterion);
 		virtual void v_info ();
 		virtual int v_domainQuantity () { return MelderQuantity_TIME_SECONDS; }
 		virtual void v_shiftX (double xfrom, double xto);
diff --git a/fon/TimeSoundAnalysisEditor.cpp b/fon/TimeSoundAnalysisEditor.cpp
index 2060445..bd7e49e 100644
--- a/fon/TimeSoundAnalysisEditor.cpp
+++ b/fon/TimeSoundAnalysisEditor.cpp
@@ -1,6 +1,6 @@
 /* TimeSoundAnalysisEditor.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -754,7 +754,7 @@ static void menu_cb_pitchListing (EDITOR_ARGS) {
 		long i, i1, i2;
 		Sampled_getWindowSamples (my d_pitch, tmin, tmax, & i1, & i2);
 		for (i = i1; i <= i2; i ++) {
-			double t = Sampled_indexToX (my d_pitch, i);
+			double t = my d_pitch -> f_indexToX (i);
 			double f0 = Sampled_getValueAtSample (my d_pitch, i, Pitch_LEVEL_FREQUENCY, my p_pitch_unit);
 			f0 = Function_convertToNonlogarithmic (my d_pitch, f0, Pitch_LEVEL_FREQUENCY, my p_pitch_unit);
 			MelderInfo_writeLine (Melder_fixed (t, 6), L"   ", Melder_fixed (f0, 6));
@@ -1013,7 +1013,7 @@ static void menu_cb_intensityListing (EDITOR_ARGS) {
 		long i, i1, i2;
 		Sampled_getWindowSamples (my d_intensity, tmin, tmax, & i1, & i2);
 		for (i = i1; i <= i2; i ++) {
-			double t = Sampled_indexToX (my d_intensity, i);
+			double t = my d_intensity -> f_indexToX (i);
 			double intensity = Vector_getValueAtX (my d_intensity, t, Vector_CHANNEL_1, Vector_VALUE_INTERPOLATION_NEAREST);
 			MelderInfo_writeLine (Melder_fixed (t, 6), L"   ", Melder_fixed (intensity, 6));
 		}
@@ -1196,7 +1196,7 @@ static void menu_cb_formantListing (EDITOR_ARGS) {
 		long i, i1, i2;
 		Sampled_getWindowSamples (my d_formant, tmin, tmax, & i1, & i2);
 		for (i = i1; i <= i2; i ++) {
-			double t = Sampled_indexToX (my d_formant, i);
+			double t = my d_formant -> f_indexToX (i);
 			double f1 = Formant_getValueAtTime (my d_formant, 1, t, 0);
 			double f2 = Formant_getValueAtTime (my d_formant, 2, t, 0);
 			double f3 = Formant_getValueAtTime (my d_formant, 3, t, 0);
@@ -1816,8 +1816,9 @@ void structTimeSoundAnalysisEditor :: v_draw_analysis () {
 	TimeSoundAnalysisEditor_computeFormants (this);
 	if (p_formant_show && d_formant != NULL) {
 		Graphics_setColour (d_graphics, Graphics_RED);
+		Graphics_setSpeckleSize (d_graphics, p_formant_dotSize);
 		Formant_drawSpeckles_inside (d_formant, d_graphics, d_startWindow, d_endWindow, 
-			p_spectrogram_viewFrom, p_spectrogram_viewTo, p_formant_dynamicRange, p_formant_dotSize);
+			p_spectrogram_viewFrom, p_spectrogram_viewTo, p_formant_dynamicRange);
 		Graphics_setColour (d_graphics, Graphics_BLACK);
 	}
 	/*
diff --git a/fon/TimeSoundEditor.cpp b/fon/TimeSoundEditor.cpp
index 914960e..4e1cb8e 100644
--- a/fon/TimeSoundEditor.cpp
+++ b/fon/TimeSoundEditor.cpp
@@ -1,6 +1,6 @@
 /* TimeSoundEditor.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -551,18 +551,18 @@ void structTimeSoundEditor :: f_drawSound (double globalMinimum, double globalMa
 			double mid = 0.5 * (minimum + maximum);
 			Graphics_text1 (d_graphics, d_startWindow, mid, Melder_float (Melder_half (mid)));
 		} else {
-			if (not cursorVisible or Graphics_dyWCtoMM (d_graphics, cursorFunctionValue - minimum) > 5.0) {
+			if (not cursorVisible or ! NUMdefined (cursorFunctionValue) or Graphics_dyWCtoMM (d_graphics, cursorFunctionValue - minimum) > 5.0) {
 				Graphics_setTextAlignment (d_graphics, Graphics_RIGHT, Graphics_BOTTOM);
 				Graphics_text1 (d_graphics, d_startWindow, minimum, Melder_float (Melder_half (minimum)));
 			}
-			if (not cursorVisible or Graphics_dyWCtoMM (d_graphics, maximum - cursorFunctionValue) > 5.0) {
+			if (not cursorVisible or ! NUMdefined (cursorFunctionValue) or Graphics_dyWCtoMM (d_graphics, maximum - cursorFunctionValue) > 5.0) {
 				Graphics_setTextAlignment (d_graphics, Graphics_RIGHT, Graphics_TOP);
 				Graphics_text1 (d_graphics, d_startWindow, maximum, Melder_float (Melder_half (maximum)));
 			}
 		}
 		if (minimum < 0 && maximum > 0 && ! horizontal) {
 			Graphics_setWindow (d_graphics, 0, 1, minimum, maximum);
-			if (not cursorVisible or fabs (Graphics_dyWCtoMM (d_graphics, cursorFunctionValue - 0.0)) > 3.0) {
+			if (not cursorVisible or ! NUMdefined (cursorFunctionValue) or fabs (Graphics_dyWCtoMM (d_graphics, cursorFunctionValue - 0.0)) > 3.0) {
 				Graphics_setTextAlignment (d_graphics, Graphics_RIGHT, Graphics_HALF);
 				Graphics_text (d_graphics, 0, 0, L"0");
 			}
@@ -606,16 +606,16 @@ void structTimeSoundEditor :: f_drawSound (double globalMinimum, double globalMa
 		/*if (ichan == 1) FunctionEditor_SoundAnalysis_drawPulses (this);*/
 		if (sound) {
 			Graphics_setWindow (d_graphics, d_startWindow, d_endWindow, minimum, maximum);
-			if (cursorVisible)
+			if (cursorVisible && NUMdefined (cursorFunctionValue))
 				FunctionEditor_drawCursorFunctionValue (this, cursorFunctionValue, Melder_float (Melder_half (cursorFunctionValue)), L"");
 			Graphics_setColour (d_graphics, Graphics_BLACK);
 			Graphics_function (d_graphics, sound -> z [ichan], first, last,
-				Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
+				sound -> f_indexToX (first), sound -> f_indexToX (last));
 		} else {
 			Graphics_setWindow (d_graphics, d_startWindow, d_endWindow, minimum * 32768, maximum * 32768);
 			Graphics_function16 (d_graphics,
 				longSound -> buffer - longSound -> imin * nchan + (ichan - 1), nchan - 1, first, last,
-				Sampled_indexToX (longSound, first), Sampled_indexToX (longSound, last));
+				longSound -> f_indexToX (first), longSound -> f_indexToX (last));
 		}
 		Graphics_resetViewport (d_graphics, vp);
 	}
@@ -630,7 +630,7 @@ int structTimeSoundEditor :: v_click (double xbegin, double ybegin, bool shiftKe
 		ybegin = (ybegin - v_getBottomOfSoundArea ()) / (1.0 - v_getBottomOfSoundArea ());
 		int nchan = sound ? sound -> ny : longSound -> numberOfChannels;
 		if (nchan > 8) {
-			//Melder_casual ("%f %f %d %d", xbegin, ybegin, (int) nchan, (int) this -> sound.channelOffset);
+			trace ("%f %f %d %d", xbegin, ybegin, (int) nchan, (int) d_sound.channelOffset);
 			if (xbegin >= d_endWindow && ybegin > 0.875 && ybegin <= 1.000 && d_sound.channelOffset > 0) {
 				d_sound.channelOffset -= 8;
 				return 1;
@@ -650,10 +650,10 @@ void structTimeSoundEditor :: f_init (const wchar_t *title, Function data, Sampl
 		if (ownSound) {
 			Melder_assert (Thing_member (sound, classSound));
 			d_sound.data = Data_copy ((Sound) sound);   // deep copy; ownership transferred
-			Matrix_getWindowExtrema (sound, 1, d_sound.data -> nx, 1, d_sound.data -> ny, & d_sound.minimum, & d_sound.maximum);
+			Matrix_getWindowExtrema (d_sound.data, 1, d_sound.data -> nx, 1, d_sound.data -> ny, & d_sound.minimum, & d_sound.maximum);
 		} else if (Thing_member (sound, classSound)) {
 			d_sound.data = (Sound) sound;   // reference copy; ownership not transferred
-			Matrix_getWindowExtrema (sound, 1, d_sound.data -> nx, 1, d_sound.data -> ny, & d_sound.minimum, & d_sound.maximum);
+			Matrix_getWindowExtrema (d_sound.data, 1, d_sound.data -> nx, 1, d_sound.data -> ny, & d_sound.minimum, & d_sound.maximum);
 		} else if (Thing_member (sound, classLongSound)) {
 			d_longSound.data = (LongSound) sound;
 			d_sound.minimum = -1.0, d_sound.maximum = 1.0;
diff --git a/fon/Vector.cpp b/fon/Vector.cpp
index c7cdae3..d94999d 100644
--- a/fon/Vector.cpp
+++ b/fon/Vector.cpp
@@ -1,6 +1,6 @@
 /* Vector.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -118,14 +118,14 @@ double Vector_getValueAtX (Vector me, double x, long ilevel, int interpolation)
 	if (x <  leftEdge || x > rightEdge) return NUMundefined;
 	if (ilevel > Vector_CHANNEL_AVERAGE) {
 		Melder_assert (ilevel <= my ny);
-		return NUM_interpolate_sinc (my z [ilevel], my nx, Sampled_xToIndex (me, x),
+		return NUM_interpolate_sinc (my z [ilevel], my nx, my f_xToIndex (x),
 			interpolation == Vector_VALUE_INTERPOLATION_SINC70 ? NUM_VALUE_INTERPOLATE_SINC70 :
 			interpolation == Vector_VALUE_INTERPOLATION_SINC700 ? NUM_VALUE_INTERPOLATE_SINC700 :
 			interpolation);
 	}
 	double sum = 0.0;
 	for (long channel = 1; channel <= my ny; channel ++) {
-		sum += NUM_interpolate_sinc (my z [channel], my nx, Sampled_xToIndex (me, x),
+		sum += NUM_interpolate_sinc (my z [channel], my nx, my f_xToIndex (x),
 			interpolation == Vector_VALUE_INTERPOLATION_SINC70 ? NUM_VALUE_INTERPOLATE_SINC70 :
 			interpolation == Vector_VALUE_INTERPOLATION_SINC700 ? NUM_VALUE_INTERPOLATE_SINC700 :
 			interpolation);
@@ -403,25 +403,27 @@ void Vector_draw (Vector me, Graphics g, double *pxmin, double *pxmax, double *p
 	Graphics_setWindow (g, xreversed ? *pxmax : *pxmin, xreversed ? *pxmin : *pxmax, yreversed ? *pymax : *pymin, yreversed ? *pymin : *pymax);
 	if (wcsstr (method, L"bars") || wcsstr (method, L"Bars")) {
 		for (ix = ixmin; ix <= ixmax; ix ++) {
-			double x = Sampled_indexToX (me, ix);
+			double x = my f_indexToX (ix);
 			double y = my z [1] [ix];
 			double left = x - 0.5 * my dx, right = x + 0.5 * my dx;
 			if (y > *pymax) y = *pymax;
 			if (left < *pxmin) left = *pxmin;
 			if (right > *pxmax) right = *pxmax;
-			Graphics_line (g, left, y, right, y);
-			Graphics_line (g, left, y, left, *pymin);
-			Graphics_line (g, right, y, right, *pymin);
+			if (y > *pymin) {
+				Graphics_line (g, left, y, right, y);
+				Graphics_line (g, left, y, left, *pymin);
+				Graphics_line (g, right, y, right, *pymin);
+			}
 		}
 	} else if (wcsstr (method, L"poles") || wcsstr (method, L"Poles")) {
 		for (ix = ixmin; ix <= ixmax; ix ++) {
-			double x = Sampled_indexToX (me, ix);
+			double x = my f_indexToX (ix);
 			Graphics_line (g, x, 0, x, my z [1] [ix]);
 		}
 	} else if (wcsstr (method, L"speckles") || wcsstr (method, L"Speckles")) {
 		for (ix = ixmin; ix <= ixmax; ix ++) {
-			double x = Sampled_indexToX (me, ix);
-			Graphics_fillCircle_mm (g, x, my z [1] [ix], 1.0);
+			double x = my f_indexToX (ix);
+			Graphics_speckle (g, x, my z [1] [ix]);
 		}
 	} else {
 		/*
diff --git a/fon/manual_Fon.cpp b/fon/manual_Fon.cpp
index 327aaef..dd068cd 100644
--- a/fon/manual_Fon.cpp
+++ b/fon/manual_Fon.cpp
@@ -1,6 +1,6 @@
 /* manual_Fon.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -140,16 +140,16 @@ INTRO (L"A command for changing the data in all selected @Cochleagram objects.")
 NORMAL (L"See the @Formulas tutorial for examples and explanations.")
 MAN_END
 
-MAN_BEGIN (L"Create DurationTier...", L"ppgb", 20021204)
+MAN_BEGIN (L"Create DurationTier...", L"ppgb", 20140421)
 INTRO (L"A command in the @@New menu@ to create an empty @DurationTier object.")
 NORMAL (L"The resulting object will have the specified name and time domain, but contain no duration points. "
 	"To add some points to it, use @@DurationTier: Add point... at .")
 ENTRY (L"Scripting example")
 NORMAL (L"To create a tier 0.9 seconds long, with an deceleration around 0.6 seconds, you do:")
-CODE (L"Create DurationTier... dur 0 0.9")
-CODE (L"Add point... 0.3 1")
-CODE (L"Add point... 0.6 2.3")
-CODE (L"Add point... 0.7 1")
+CODE (L"Create DurationTier: \"dur\", 0, 0.9")
+CODE (L"Add point: 0.3, 1")
+CODE (L"Add point: 0.6, 2.3")
+CODE (L"Add point: 0.7, 1")
 NORMAL (L"The result will look like")
 PICTURE (5, 2.5, draw_CreateDurationTier)
 NORMAL (L"The target duration will be the area under this curve, which is 0.9 + 1/2 \\.c 1.3 \\.c 0.4 = 1.16 seconds.")
@@ -202,7 +202,7 @@ INTRO (L"A command in the @@New menu@ to create a @Strings object containing a l
 	"It works completely analogously to @@Create Strings as file list... at .")
 MAN_END
 
-MAN_BEGIN (L"Create Strings as file list...", L"ppgb", 20130521)
+MAN_BEGIN (L"Create Strings as file list...", L"ppgb", 20140107)
 INTRO (L"A command in the @@New menu@ to create a @Strings object containing a list of files in a given directory.")
 ENTRY (L"Settings")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (2.6), L""
@@ -234,20 +234,20 @@ NORMAL (L"In a script, you can use this command to cycle through the files in a
 	"For instance, to read in all the sound files in a specified directory, "
 	"you could use the following script:")
 CODE (L"directory\\$  = \"/usr/people/miep/sounds\"")
-CODE (L"strings = do (\"Create Strings as file list...\", \"list\", directory\\$  + \"/*.wav\")")
-CODE (L"numberOfFiles = do (\"Get number of strings\")")
+CODE (L"strings = Create Strings as file list: \"list\", directory\\$  + \"/*.wav\"")
+CODE (L"numberOfFiles = Get number of strings")
 CODE (L"for ifile to numberOfFiles")
-	CODE1 (L"selectObject (strings)")
-	CODE1 (L"fileName\\$  = do\\$  (\"Get string...\", ifile)")
-	CODE1 (L"do (\"Read from file...\", directory\\$  + \"/\" + fileName\\$ )")
+	CODE1 (L"selectObject: strings")
+	CODE1 (L"fileName\\$  = Get string: ifile")
+	CODE1 (L"Read from file: directory\\$  + \"/\" + fileName\\$ ")
 CODE (L"endfor")
 NORMAL (L"If the script has been saved to a script file, you can use file paths that are relative to the directory "
 	"where you saved the script. Thus, with")
-CODE (L"do (\"Create Strings as file list...\", \"list\", \"*.wav\")")
+CODE (L"Create Strings as file list: \"list\", \"*.wav\"")
 NORMAL (L"you get a list of all the .wav files that are in the same directory as the script that contains this line. "
 	"And to get a list of all the .wav files in the directory Sounds that resides in the same directory as your script, "
 	"you can do")
-CODE (L"do (\"Create Strings as file list...\", \"list\", \"Sounds/*.wav\")")
+CODE (L"Create Strings as file list: \"list\", \"Sounds/*.wav\"")
 NORMAL (L"As is usual in Praat scripting, the forward slash (\"/\") in this example can be used on all platforms, including Windows. "
 	"This makes your script portable across platforms.")
 ENTRY (L"See also")
@@ -791,7 +791,7 @@ LIST_ITEM (L"Page-down (in sound windows): Scroll page forward")
 LIST_ITEM (L"Escape: Interrupt playing")
 MAN_END
 
-MAN_BEGIN (L"Log files", L"ppgb", 20110808)
+MAN_BEGIN (L"Log files", L"ppgb", 20140421)
 INTRO (L"With some commands in the @Query menu of the @SoundEditor and @TextGridEditor, "
 	"you can write combined information about times, pitch values, formants, and intensities "
 	"to the @@Info window@ and to a log file.")
@@ -868,8 +868,8 @@ NORMAL (L"You may sometimes require information in your log file that cannot be
 CODE (L"f1 = Get first formant")
 CODE (L"f2 = Get second formant")
 CODE (L"f21 = f2 - f1")
-CODE (L"printline 'f1:0' 'f21:0'")
-CODE (L"fileappend \"D:\\bsPraat logs\\bsFormant log.txt\" 'f1:0''tab\\$ ''f21:0''newline\\$ '")
+CODE (L"appendInfoLine: fixed\\$  (f1, 0), \" \", fixed\\$  (f21, 0)")
+CODE (L"appendFileLine: \"D:\\bsPraat logs\\bsFormant log.txt\", fixed\\$  (f1, 0), tab\\$ , fixed\\$  (f21, 0)")
 NORMAL (L"With this script, the information would be appended both to the Info window and to the "
 	"file \"Formant log.txt\" on your desktop.")
 NORMAL (L"You can make this script accessible with Option-F12 (or Command-F12) "
@@ -883,8 +883,8 @@ CODE (L"endform")
 CODE (L"f1 = Get first formant")
 CODE (L"f2 = Get second formant")
 CODE (L"f21 = f2 - f1")
-CODE (L"printline 'vowel\\$ ' 'f1:0' 'f21:0'")
-CODE (L"fileappend \"~/Praat logs/Vowels and formants log\" 'vowel\\$ ''f1:0''tab\\$ ''f21:0''newline\\$ '")
+CODE (L"appendInfoLine: vowel\\$ , \" \", fixed\\$  (f1, 0), \" \", fixed\\$  (f21, 0)")
+CODE (L"appendFileLine: \"~/Praat logs/Vowels and formants log\", vowel\\$ , tab\\$ , fixed\\$  (f1, 0), tab\\$ , fixed\\$  (f21, 0)")
 NORMAL (L"Beware of the following pitfall: because of the nature of scripts, you should not try to do this "
 	"when you have two editor windows with the same name. I cannot predict which of the two windows "
 	"will answer the #Get queries...")
@@ -1924,7 +1924,8 @@ NORMAL (L"where %x is a normalized time that runs from 0 to 1 and %U(%x) is the
 	"If %power1 = 2.0 and %power2 = 3.0, the glottal flow shape is that proposed by @@Rosenberg (1971)@, "
 	"upon which for instance the Klatt synthesizer is based (@@Klatt & Klatt (1990)@):")
 SCRIPT (4.5, 3,
-	L"Axes... 0 1 -0.1 1\n"
+	L"Select outer viewport... 0 4.5 -0.4 3\n"
+	"Axes... 0 1 -0.1 1\n"
 	"One mark left... 0 yes yes yes\n"
 	"One mark bottom... 0 yes yes no\n"
 	"One mark bottom... 1 yes yes no\n"
@@ -1946,7 +1947,7 @@ SCRIPT (4.5, 3,
 	"Text left... yes Glottal flow\n"
 )
 NORMAL (L"For the generation of speech sounds, we do not take the glottal flow itself, "
-	"but rather its derivative (this takes into account the influence of raditaion at the lips). "
+	"but rather its derivative (this takes into account the influence of radiation at the lips). "
 	"The glottal flow derivative is given by")
 FORMULA (L"%dU(%x)/%dx = %power1 %x^^(%power1-1)^ - %power2 %x^^(%power2-1)^")
 NORMAL (L"The flow derivative clearly shows the influence of the smoothing mentioned above. "
diff --git a/fon/manual_Manual.cpp b/fon/manual_Manual.cpp
index 0374a96..fbfedb9 100644
--- a/fon/manual_Manual.cpp
+++ b/fon/manual_Manual.cpp
@@ -1,6 +1,6 @@
 /* manual_Manual.cpp
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -53,7 +53,7 @@ ENTRY (L"Your own manual pages")
 NORMAL (L"To create your own manual pages, create @ManPages text files.")
 MAN_END
 
-MAN_BEGIN (L"ManPages", L"ppgb", 20130814)
+MAN_BEGIN (L"ManPages", L"ppgb", 20140421)
 INTRO (L"You can create a documentation or education system with files that you and others "
 	"can read into Praat (with the @@Read from file...@ command). "
 	"Your files will become a hypertext system very similar to the usual @Manual.")
@@ -173,8 +173,8 @@ NORMAL (L"Your text may contain Praat scripts. They typically draw a picture in
 	"with the font and font size of the manual until you specify otherwise in the script. The format is:")
 CODE (L"<script> 4.5 4 \"")
 CODE1 (L"Draw inner box")
-CODE1 (L"Axes... 0 100 0 100")
-CODE1 (L"Text... 50 Centre 50 Half Hello!!")
+CODE1 (L"Axes: 0, 100, 0, 100")
+CODE1 (L"Text: 50, \"Centre\", 50, \"Half\", \"Hello!!\"")
 CODE (L"\\\"r")
 NORMAL (L"The two numbers after ##<script># are the width and the height of the picture "
 	"(the \"outer viewport\") in inches, if the font size of the manual is 12. "
@@ -184,10 +184,10 @@ NORMAL (L"Please note that the script is enclosed within double quotes. "
 NORMAL (L"If needed, a script like this can create objects in the object list of the manual. "
 	"However, you have to make sure that you remove them after use:")
 CODE (L"<script> 6 3 \"")
-CODE1 (L"Create Sound from formula... sineWithNoise Mono 0.0 1.0 44100 1/2*sin(2*pi*377*x)+randomGauss(0,0.1)")
-CODE1 (L"To Spectrogram... 0.005 5000 0.002 20 Gaussian")
-CODE1 (L"Paint... 0 0 0 0 100.0 yes 50.0 6.0 0.0 yes")
-CODE1 (L"plus Sound sineWithNoise")
+CODE1 (L"Create Sound from formula: \"sineWithNoise\", 1, 0.0, 1.0, 44100, \"1/2*sin(2*pi*377*x)+randomGauss(0,0.1)\"")
+CODE1 (L"To Spectrogram: 0.005, 5000, 0.002, 20, \"Gaussian\"")
+CODE1 (L"Paint: 0, 0, 0, 0, 100.0, \"yes\", 50.0, 6.0, 0.0, \"yes\"")
+CODE1 (L"plusObject: \"Sound sineWithNoise\"")
 CODE1 (L"Remove")
 CODE (L"\\\"r")
 NORMAL (L"Note that unlike the previous script, this script does not set the font and font size. "
@@ -205,20 +205,20 @@ NORMAL (L"The commands ##Set outer viewport...# and ##Set inner viewport...# are
 	"as in the Picture window, so that you can test your picture with a normal Praat script; "
 	"for instance, the following script draws a cross in the upper half of the picture and a rectangle in the lower half:")
 CODE (L"<script> 4.5 4 \"")
-CODE1 (L"Axes... 0 100 0 100")
-CODE1 (L"Select inner viewport... 0 4.5 0 2")
-CODE1 (L"Draw line... 0 0 100 100")
-CODE1 (L"Draw line... 0 100 100 0")
-CODE1 (L"Select inner viewport... 0 4.5 2 4")
-CODE1 (L"Draw rectangle... 0 100 0 100")
+CODE1 (L"Axes: 0, 100, 0, 100")
+CODE1 (L"Select inner viewport: 0, 4.5, 0, 2")
+CODE1 (L"Draw line: 0, 0, 100, 100")
+CODE1 (L"Draw line: 0, 100, 100, 0")
+CODE1 (L"Select inner viewport: 0, 4.5, 2, 4")
+CODE1 (L"Draw rectangle: 0, 100, 0, 100")
 CODE (L"\\\"r")
 SCRIPT (4.5, 4, L""
-	"Axes... 0 100 0 100\n"
-	"Select inner viewport... 0 4.5 0 2\n"
-	"Draw line... 0 0 100 100\n"
-	"Draw line... 0 100 100 0\n"
-	"Select inner viewport... 0 4.5 2 4\n"
-	"Draw rectangle... 0 100 0 100\n"
+	"Axes: 0, 100, 0, 100\n"
+	"Select inner viewport: 0, 4.5, 0, 2\n"
+	"Draw line: 0, 0, 100, 100\n"
+	"Draw line: 0, 100, 100, 0\n"
+	"Select inner viewport: 0, 4.5, 2, 4\n"
+	"Draw rectangle: 0, 100, 0, 100\n"
 )
 ENTRY (L"Script links")
 NORMAL (L"Your text may contain links to Praat scripts. They are drawn in blue. "
diff --git a/fon/manual_Picture.cpp b/fon/manual_Picture.cpp
index 1938c3d..64d06ec 100644
--- a/fon/manual_Picture.cpp
+++ b/fon/manual_Picture.cpp
@@ -1,6 +1,6 @@
 /* manual_Picture.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -390,7 +390,7 @@ NORMAL (L"For most of the codes, the first letter tells you the most similar let
 	"The codes for \\sw, \\rh, \\hs and \\kb are abbreviations for %schwa, %%ram's horn%, %horseshoe, and %%kidney bean%.")
 MAN_END
 
-MAN_BEGIN (L"Axes...", L"ppgb", 19970330)
+MAN_BEGIN (L"Axes...", L"ppgb", 20140107)
 INTRO (L"One of the commands in the #Margins and #World menus of the @@Picture window at .")
 ENTRY (L"Purpose")
 NORMAL (L"To view and change the current world coordinates of the horizontal and vertical axes.")
@@ -403,23 +403,23 @@ ENTRY (L"Example")
 NORMAL (L"The following script would draw a person's vowel triangle:")
 CODE (L"\\#  Put F1 (between 300 and 800 Hz) along the horizontal axis,")
 CODE (L"\\#  and F2 (between 600 and 3600 Hz) along the vertical axis.")
-CODE (L"##Axes...# 300 800 600 3600")
+CODE (L"##Axes:# 300, 800, 600, 3600")
 CODE (L"\\#  Draw a rectangle inside the current viewport (selected area),")
 CODE (L"\\#  with text in the margins, and tick marks in steps of 100 Hz along the F1 axis,")
 CODE (L"\\#  and in steps of 200 Hz along the F2 axis.")
 CODE (L"Draw inner box")
-CODE (L"Text top... no Dani\\bse\\\" l's Dutch vowel triangle")
-CODE (L"Text bottom... yes \\% F_1 (Hz)")
-CODE (L"Text left... yes \\% F_2 (Hz)")
-CODE (L"Marks bottom every... 1 100 yes yes yes")
-CODE (L"Marks left every... 1 200 yes yes yes")
+CODE (L"Text top: \"no\", \"Daniël's Dutch vowel triangle\"")
+CODE (L"Text bottom: \"yes\", \"\\% F_1 (Hz)\"")
+CODE (L"Text left: \"yes\", \"\\% F_2 (Hz)\"")
+CODE (L"Marks bottom every: 1, 100, \"yes\", \"yes\", \"yes\", \"\"")
+CODE (L"Marks left every: 1, 200, \"yes\", \"yes\", \"yes\", \"\"")
 CODE (L"\\#  Draw large phonetic symbols at the vowel points.")
-CODE (L"Text special... 340 Centre 688 Half Times 24 0 u")
-CODE (L"Text special... 481 Centre 1195 Half Times 24 0 \\bso/")
+CODE (L"Text special: 340, \"Centre\", 688, \"Half\", \"Times\", 24, \"0\", \"u\"")
+CODE (L"Text special: 481, \"Centre\", 1195, \"Half\", \"Times\", 24, \"0\", \"ø\"")
 CODE (L"\\#  Etcetera")
-NORMAL (L"This example would draw the texts \"Dani\\e\"l's Dutch vowel triangle\", "
+NORMAL (L"This example would draw the texts \"Daniël's Dutch vowel triangle\", "
 	"\"%F__1_ (Hz)\", and \"%F__2_ (Hz)\" in the margins, "
-	"and the texts \"u\" and \"\\o/\" at the appropriate positions inside the drawing area.")
+	"and the texts \"u\" and \"ø\" at the appropriate positions inside the drawing area.")
 MAN_END
 
 MAN_BEGIN (L"Copy to clipboard", L"ppgb", 20120430)   /* Not Unix. */
@@ -673,18 +673,15 @@ DEFINITION (L"you can specify a red-green-blue value as three values between 0 a
 	"and separated by commas, e.g. {0.8,0.1,0.2} is something reddish.")
 MAN_END
 
-MAN_BEGIN (L"Picture window", L"ppgb", 20110129)
+MAN_BEGIN (L"Picture window", L"ppgb", 20140325)
 INTRO (L"One of the two main windows in Praat.")
 TAG (L"File menu")
+LIST_ITEM (L"\\bu @@Save as PDF file...")
+LIST_ITEM (L"\\bu @@Save as PNG file...")
+LIST_ITEM (L"\\bu @@Save as EPS file...")
+LIST_ITEM (L"\\bu @@Save as Windows metafile...@")
 LIST_ITEM (L"\\bu @@Read from Praat picture file...@, @@Save as Praat picture file...")
 LIST_ITEM (L"\\bu @@PostScript settings...")
-#if defined (macintosh)
-	LIST_ITEM (L"\\bu @@Save as PDF file...")
-#endif
-LIST_ITEM (L"\\bu @@Save as EPS file...")
-#if defined (_WIN32)
-	LIST_ITEM (L"\\bu @@Save as Windows metafile...@")
-#endif
 LIST_ITEM (L"\\bu @@Print...")
 TAG (L"Edit menu")
 LIST_ITEM (L"\\bu @@Undo@")
@@ -826,14 +823,15 @@ ENTRY (L"Usage")
 NORMAL (L"With the ##Text...# command, you can use all @@special symbols@ and @@text styles at .")
 MAN_END
 
-MAN_BEGIN (L"Insert picture from file...", L"ppgb", 20110803)
-INTRO (L"A command in the #World menu of the @@Picture window@, on Macintosh and Windows only.")
+MAN_BEGIN (L"Insert picture from file...", L"ppgb", 20140608)
+INTRO (L"A command in the #World menu of the @@Picture window at .")
 ENTRY (L"Purpose")
-NORMAL (L"To draw a JPEG, TIFF or PNG picture into the Picture window (or into the Demo window).")
+NORMAL (L"To draw a picture file (PNG; on Mac and Windows also JPEG or TIFF; "
+	"other picture formats may also work) into the Picture window (or into the Demo window).")
 ENTRY (L"Settings")
 TAG (L"##File name")
-DEFINITION (L"the name of the picture file (JPEG, TIFF or PNG; other picture formats may also work). If you use this command in a script, "
-	"you can use a relative path name such as $$pictures/myface.jpg$ or $$~/Desktop/hello.jpg$.")
+DEFINITION (L"the name of the picture file. If you use this command in a script, "
+	"you can use a relative path name such as $$pictures/myface.png$ or $$~/Desktop/hello.png$.")
 TAG (L"##From x")
 TAG (L"##To x")
 DEFINITION (L"The horizontal location (in world coordinates) where the picture will appear. "
@@ -945,33 +943,17 @@ ENTRY (L"Usage")
 NORMAL (L"You can use all @@special symbols@ and @@text styles at .")
 MAN_END
 
-MAN_BEGIN (L"Save as EPS file...", L"ppgb", 20110129)
+MAN_BEGIN (L"Save as EPS file...", L"ppgb", 20140325)
 INTRO (L"A command in the File menu of the @@Picture window at .")
 NORMAL (L"It saves the picture to an @@Encapsulated PostScript@ (EPS) file, "
 	"which can be imported by many other programs, such as Microsoft^\\re Word^\\tm.")
-ENTRY (L"PostScript = highest possible quality!")
-NORMAL (L"With EPS files you can use high-quality graphics in your word-processor documents. "
-	"The quality is higher than if you use @@Copy to clipboard at .")
-#ifdef _WIN32
-ENTRY (L"The big limitation")
-NORMAL (L"EPS pictures imported in Word for Windows will show correctly only on PostScript printers, or with GhostView, or in PDF files "
-	"created by Adobe^\\re Acrobat^\\tm Distiller^\\tm. "
-	"To print EPS pictures on non-PostScript printers, use a Linux or Macintosh computer.")
-#endif
-#ifdef macintosh
-ENTRY (L"Usage")
-NORMAL (L"To import an EPS file in Word, choose #Insert \\-> #Picture \\-> ##From file...#. "
-	"Word will create a picture with the same size as the originally selected part of the Picture window (the %viewport).")
-NORMAL (L"(In Word versions from 2001 or older, Word will show you a screen preview in a mediocre quality, "
-	"but you will see the high-quality PostScript version when you print.)")
-#endif
-#ifdef _WIN32
 ENTRY (L"Usage")
-NORMAL (L"If Word cannot read your EPS files, you may have to install EPS support from the Office^\\tm CD, "
-	"because the standard installation of Office may not support EPS files. "
-	"When you import an EPS file in an older version of Word, you may only see the file name and the date and time of creation, "
-	"and a message that the picture will print correctly to a PostScript printer (it will, if the PostScript driver has been selected).")
-#endif
+NORMAL (L"EPS files are on the way out, mainly because they do not really support international text. "
+	"On Macintosh and Linux, it is almost always better to use @@Save as PDF file...@ instead, "
+	"or (on the Mac) to use @@Copy to clipboard at . On Windows, which does not really support PDF files yet, "
+	"you may find that EPS files sometimes creates better quality than @@Copy to clipboard@; "
+	"you should also check out @@Save as PNG file...@ on Windows. "
+	"To import an EPS file in Word, choose #Insert \\-> #Picture \\-> ##From file...#. ")
 ENTRY (L"Behaviour")
 NORMAL (L"Though all the contents of the Picture window are written to the EPS file, "
 	"only the part that you selected in the Picture window (the %viewport) may become visible in Word (or another program).")
@@ -979,25 +961,42 @@ ENTRY (L"Settings")
 NORMAL (L"The EPS picture is saved with the grey resolution and fonts that you specified with @@PostScript settings... at .")
 MAN_END
 
-MAN_BEGIN (L"Save as PDF file...", L"ppgb", 20110129)
-INTRO (L"A command in the File menu of the @@Picture window@, on Macintosh only.")
+MAN_BEGIN (L"Save as PDF file...", L"ppgb", 20140325)
+INTRO (L"A command in the File menu of the @@Picture window@, on Macintosh and Linux.")
 NORMAL (L"It saves the picture to a PDF file, "
-	"which can be imported by several other programs, such as Microsoft^\\re Word\\tm 2008 on the Mac.")
-ENTRY (L"PDF = PostScript = highest possible quality!")
+	"which can be imported by several other programs, such as modern versions of Microsoft^\\re Word\\tm.")
+ENTRY (L"PDF means highest possible quality")
 NORMAL (L"With PDF pictures you can use high-quality graphics in your word-processor documents. "
-	"The quality is the same as if you use @@Copy to clipboard at .")
-NORMAL (L"On Windows or Linux, use @@Save as EPS file...@ instead.")
+	"On the Mac, the quality is the same as if you use @@Copy to clipboard at .")
+NORMAL (L"On Windows, use @@Save as PNG file...@ or @@Save as EPS file...@ instead.")
 ENTRY (L"Behaviour")
 NORMAL (L"Though all the contents of the Picture window are written to the PDF file, "
-	"only the part that you selected in the Picture window (the %viewport) may become visible in Word (or another program).")
+	"only the part that you selected in the Picture window (the %viewport) will become visible in Word (or another program).")
+ENTRY (L"Usage")
+NORMAL (L"To import a PDF file in Word, choose #Insert \\-> #Picture \\-> ##From file...#. "
+	"Word will create a picture with the same size as the originally selected part of the Picture window (the %viewport).")
+MAN_END
+
+MAN_BEGIN (L"Save as PNG file...", L"ppgb", 20140325)
+INTRO (L"A command in the File menu of the @@Picture window@, on all platforms.")
+NORMAL (L"It saves the picture to a PNG (\"ping\") image file, "
+	"which can be imported by several other programs, such as Microsoft^\\re Word\\tm. "
+	"For the resolution you can choose between 600 dots per inch (very good quality even when printed) "
+	"and 300 dpi (enough for all web sites, even on retina displays).")
+ENTRY (L"Usage in text processors")
+NORMAL (L"On Windows, PNG files may sometimes have the best quality that you can get, "
+	"although you should also try @@Save as EPS file...@ and @@Copy to clipboard at . "
+	"On Macintosh or Linux, @@Save as PDF file...@ or @@Copy to clipboard@ is almost always better.")
+ENTRY (L"Usage for publication")
+NORMAL (L"Some publishers do not accept PDF pictures. In such a case, "
+	"they may accept 600-dpi or 300-dpi PNG pictures. If they accept TIFF pictures only, "
+	"then you can easily convert your PNG picture to a TIFF picture with any graphics converter program.")
+ENTRY (L"Behaviour")
+NORMAL (L"Only the contents of the part of the Picture window that you selected (the %viewport) "
+	"are written to the PNG file.")
 ENTRY (L"Usage")
-NORMAL (L"To import a PDF file in Word 2008, choose #Insert \\-> #Picture \\-> ##From file...#. "
+NORMAL (L"To import a PNG file in Word, choose #Insert \\-> #Picture \\-> ##From file...#. "
 	"Word will create a picture with the same size as the originally selected part of the Picture window (the %viewport).")
-NORMAL (L"Please note that if you save the Word document as a ##.doc# file, Word will convert the PDF picture into a mediocre 300-dpi bitmap; "
-	"therefore, you should save the document as a ##.docx# file instead. If you cannot use ##.docx# files "
-	"(for instance because your publisher does not support these yet, or because you still use Word 2004), "
-	"you may use @@Save as EPS file...@; "
-	"the quality will be the same, but you cannot use Czech, Russian, Korean or Arabic characters in EPS files.")
 MAN_END
 
 MAN_BEGIN (L"Save as Praat picture file...", L"ppgb", 20110129)
@@ -1008,7 +1007,7 @@ NORMAL (L"With the help of this command, you can transfer the contents of the pi
 	"for instance from a Macintosh to a Windows computer.")
 MAN_END
 
-MAN_BEGIN (L"Save as Windows metafile...", L"ppgb", 20120430)
+MAN_BEGIN (L"Save as Windows metafile...", L"ppgb", 20140325)
 INTRO (L"A command in the File menu of the @@Picture window@, if you are on Windows.")
 NORMAL (L"It saves the selected part of the picture in an \"enhanced metafile\" (.EMF) format, "
 	"which can be imported by many Windows programs, like Adobe^\\re Illustrator^\\tm or Microsoft^\\re Word^\\tm.")
@@ -1020,8 +1019,6 @@ NORMAL (L"You will not use this command very often, "
 	"because it is usually easier to copy the selection to the clipboard with the @@Copy to clipboard@ command, "
 	"and `Paste' it into the other program. You may use a metafile instead of the clipboard if the clipboard is too large "
 	"for the other program to read, or if you want to transfer the picture to another computer.")
-NORMAL (L"If you have a PostScript printer, you would use @@Save as EPS file...@ instead "
-	"for best printing results.")
 MAN_END
 
 }
diff --git a/fon/manual_Sampling.cpp b/fon/manual_Sampling.cpp
index 0858415..18921ed 100644
--- a/fon/manual_Sampling.cpp
+++ b/fon/manual_Sampling.cpp
@@ -1,6 +1,6 @@
 /* manual_Sampling.cpp
  *
- * Copyright (C) 1992-2010 Paul Boersma
+ * Copyright (C) 1992-2010,2014 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
@@ -24,27 +24,27 @@
 void manual_Sampling_init (ManPages me);
 void manual_Sampling_init (ManPages me) {
 
-MAN_BEGIN (L"Get sampling period", L"ppgb", 20040420)
+MAN_BEGIN (L"Get sampling period", L"ppgb", 20140421)
 INTRO (L"A command that becomes available in the #Query menu if you select a @Sound object.")
 NORMAL (L"The Info window will tell you the @@sampling period@ in seconds.")
 ENTRY (L"Usage")
 NORMAL (L"You will not often choose this command with the mouse, "
 	"since the sampling period is included in the information that you get "
 	"by clicking the #Info button. This command is probably more useful in a Praat script:")
-CODE (L"select Sound hello")
+CODE (L"selectObject: \"Sound hello\"")
 CODE (L"samplingPeriod = Get sampling period")
 ENTRY (L"Details for hackers")
 NORMAL (L"With @Inspect, you can see how the sampling period is stored in a #Sound object: it is the #dx attribute.")
 MAN_END
 
-MAN_BEGIN (L"Get sampling frequency", L"ppgb", 20040420)
+MAN_BEGIN (L"Get sampling frequency", L"ppgb", 20140421)
 INTRO (L"A command that becomes available in the #Query menu if you select a @Sound object.")
 NORMAL (L"The Info window will tell you the @@sampling frequency@ in hertz.")
 ENTRY (L"Usage")
 NORMAL (L"You will not often choose this command with the mouse, "
 	"since the sampling frequency is included in the information that you get "
 	"by clicking the #Info button. This command is probably more useful in a Praat script:")
-CODE (L"select Sound hello")
+CODE (L"selectObject: \"Sound hello\"")
 CODE (L"samplingFrequency = Get sampling frequency")
 ENTRY (L"Algorithm")
 NORMAL (L"The sampling frequency is defined as 1 / (\\De%t), where \\De%t is the @@sampling period at . "
@@ -67,7 +67,7 @@ NORMAL (L"If you select a Sound or LongSound and click @Inspect, "
 	"%t__1_ is the #x1 attribute, and \\De%t is the #dx attribute.")
 MAN_END
 
-MAN_BEGIN (L"Get sample number from time...", L"ppgb", 20040505)
+MAN_BEGIN (L"Get sample number from time...", L"ppgb", 20140421)
 INTRO (L"A command that becomes available in the #Query menu if you select a @Sound or @LongSound object.")
 NORMAL (L"The Info window will tell you the sample number belonging to the time that you specify. "
 	"The result is presented as a real number.")
@@ -79,7 +79,7 @@ NORMAL (L"If the sound has a sampling frequency of 10 kHz, the sample number ass
 	"will usually be 1000.5.")
 ENTRY (L"Scripting")
 NORMAL (L"You can use this command to put the nearest sample number into a script variable:")
-CODE (L"select Sound hallo")
+CODE (L"selectObject: \"Sound hallo\"")
 CODE (L"sampleNumber = Get sample number from time... 0.1")
 CODE (L"nearestSample = round (sampleNumber)")
 NORMAL (L"In this case, the value will not be written into the Info window. To round down or up, use")
@@ -123,7 +123,7 @@ NORMAL (L"If you select one of the above objects and click @Inspect, "
 	"%t__1_ is the #x1 attribute, and \\De%t is the #dx attribute.")
 MAN_END
 
-MAN_BEGIN (L"Get frame number from time...", L"ppgb", 20040505)
+MAN_BEGIN (L"Get frame number from time...", L"ppgb", 20140421)
 INTRO (L"A command that becomes available in the #Query menu if you select a sound-analysis object that is a function of time "
 	"and that is evenly sampled in time (@Pitch, @Formant, @Intensity, @Harmonicity).")
 NORMAL (L"The Info window will tell you the frame number belonging to the time that you specify. "
@@ -136,7 +136,7 @@ NORMAL (L"If the Pitch object has a time step of 10 ms, and the first frame is c
 	"the frame number associated with a time of 0.1 seconds is 9.2.")
 ENTRY (L"Scripting")
 NORMAL (L"You can use this command to put the nearest frame centre into a script variable:")
-CODE (L"select Pitch hallo")
+CODE (L"selectObject: \"Pitch hallo\"")
 CODE (L"frame = Get frame from time... 0.1")
 CODE (L"nearestFrame = round (frame)")
 NORMAL (L"In this case, the value will not be written into the Info window. To round down or up, use")
diff --git a/fon/manual_Script.cpp b/fon/manual_Script.cpp
index 37b9f93..95a5a21 100644
--- a/fon/manual_Script.cpp
+++ b/fon/manual_Script.cpp
@@ -1,6 +1,6 @@
 /* manual_Script.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -51,7 +51,7 @@ NORMAL (L"Normally, however, if you want to add a command to a fixed menu, "
 	"you would use the command @@Add to fixed menu...@ of the @ScriptEditor instead.")
 MAN_END
 
-MAN_BEGIN (L"Add to dynamic menu...", L"ppgb", 20110129)
+MAN_BEGIN (L"Add to dynamic menu...", L"ppgb", 20140107)
 INTRO (L"A command in the #File menu of the @ScriptEditor.")
 NORMAL (L"With this command, you add a button to the dynamic menu in the @@Object window at . "
 	"This button will only be visible if the specified combination of objects is selected. "
@@ -94,21 +94,21 @@ ENTRY (L"Example")
 NORMAL (L"If one object of class Sound is selected, you want a submenu called \"Filters\" "
 	"after the #Convolve button, containing the commands \"Autocorrelation\" and \"Band filter...\", "
 	"separated by a horizontal separator line:")
-CODE (L"Add to dynamic menu... Sound 0 \"\" 0 \"\" 0 \"Filters -\" \"Convolve\" 0")
-CODE (L"Add to dynamic menu... Sound 1 \"\" 0 \"\" 0 \"Autocorrelation\" \"Filters -\" 1 /u/praats/demo/autocorrelation.praat")
-CODE (L"Add to dynamic menu... Sound 0 \"\" 0 \"\" 0 \"-- band filter --\" \"Autocorrelation\" 1")
-CODE (L"Add to dynamic menu... Sound 1 \"\" 0 \"\" 0 \"Band filter...\" \"-- band filter --\" 1 /u/praats/demo/bandFilter.praat")
+CODE (L"Add to dynamic menu: \"Sound\", 0, \"\", 0, \"\", 0, \"Filters -\", \"Convolve\", 0, \"\"")
+CODE (L"Add to dynamic menu: \"Sound\", 1, \"\", 0, \"\", 0, \"Autocorrelation\", \"Filters -\", 1, \"/u/praats/demo/autocorrelation.praat\"")
+CODE (L"Add to dynamic menu: \"Sound\", 0, \"\", 0, \"\", 0, \"-- band filter --\", \"Autocorrelation\", 1, \"\"")
+CODE (L"Add to dynamic menu: \"Sound\", 1, \"\", 0, \"\", 0, \"Band filter...\", \"-- band filter --\", 1, \"/u/praats/demo/bandFilter.praat\"")
 NORMAL (L"Note that \"Filters -\" will be a submenu title, %because it is followed by subcommands (%depth 1). "
 	"Note that %number1 is 1 only for the executable buttons; for the cascade button and the separator line, "
 	"this number is ignored.")
 ENTRY (L"Usage convention")
-NORMAL (L"Please adhere to the convention that commands that take arguments, end in three dots (...).")
+NORMAL (L"Please adhere to the convention that command that take arguments, such as \"Band filter...\" above, end in three dots.")
 ENTRY (L"Using this command in a script")
 NORMAL (L"To add a dynamic button from a script (perhaps your @@initialization script@ or a @@plug-ins|plug-in@), "
 	"use the hidden shell command @@Add action command...@ instead.")
 MAN_END
 
-MAN_BEGIN (L"Add to fixed menu...", L"ppgb", 20120915)
+MAN_BEGIN (L"Add to fixed menu...", L"ppgb", 20140107)
 INTRO (L"A command in the #File menu of the @ScriptEditor.")
 NORMAL (L"With this command, you add a button to any fixed menu in the @@Object window@ or in the @@Picture window at . "
 	"Clicking the added button will invoke the specified @@Praat script at .")
@@ -134,20 +134,20 @@ DEFINITION (L"the full path name of the script to invoke. If you saved the scrip
 	"you will get a cascading menu title instead.")
 ENTRY (L"Example 1")
 NORMAL (L"In the #Matrix submenu of the @@New menu@, you want a separator line followed by the command \"Peaks\":")
-CODE (L"Add to fixed menu... Objects New \"-- peaks --\" \"Create simple Matrix...\" 1")
-CODE (L"Add to fixed menu... Objects New \"Peaks\" \"-- peaks --\" 1 /u/praats/demo/peaks.praat")
+CODE (L"Add to fixed menu: \"Objects\", \"New\", \"-- peaks --\", \"Create simple Matrix...\", 1, \"\"")
+CODE (L"Add to fixed menu: \"Objects\", \"New\", \"Peaks\", \"-- peaks --\", 1, \"/u/praats/demo/peaks.praat\"")
 ENTRY (L"Example 2")
 NORMAL (L"In the @@New menu@, you want a submenu called \"Demo\", with a subitem titled \"Lorenz...\":")
-CODE (L"Add to fixed menu... Objects New \"Demo\" \"\" 0")
-CODE (L"Add to fixed menu... Objects New \"Lorenz...\" \"Demo\" 1 /u/praats/demo/lorentz.praat")
+CODE (L"Add to fixed menu: \"Objects\", \"New\", \"Demo\", \"\", 0,, \"\"")
+CODE (L"Add to fixed menu: \"Objects\", \"New\", \"Lorenz...\", \"Demo\", 1, \"/u/praats/demo/lorentz.praat\"")
 ENTRY (L"Usage convention")
-NORMAL (L"Please adhere to the convention that commands that take arguments, end in three dots (...).")
+NORMAL (L"Please adhere to the convention that command that take arguments, such as \"Lorenz...\" above, end in three dots.")
 ENTRY (L"Using this command in a script")
 NORMAL (L"To add a fixed button from a script (perhaps your @@initialization script@ or a @@plug-ins|plug-in@), "
 	"use the hidden shell command @@Add menu command...@ instead.")
 MAN_END
 
-MAN_BEGIN (L"binomialQ", L"ppgb", 20130421)
+MAN_BEGIN (L"binomialQ", L"ppgb", 20140223)
 INTRO (L"A function that can be used in @@Formulas at . The complement of the cumulative binomial distribution.")
 ENTRY (L"Syntax")
 TAG (L"$$binomialQ (%p, %k, %n)")
@@ -167,13 +167,13 @@ NORMAL (L"You convert 1000 values of pitch targets in Hz to the nearest note on
 CODE (L"a = 597")
 CODE (L"b = 403")
 CODE (L"p = 7/12 ; no preference")
-CODE (L"writeInfoLine (\"*** Binomial test \", a, \", \", b, \", p = \", fixed\\$  (p, 6), \" ***\")")
+CODE (L"writeInfoLine: \"*** Binomial test \", a, \", \", b, \", p = \", fixed\\$  (p, 6), \" ***\"")
 CODE (L"pbin = binomialQ (p, a, a+b)")
-CODE (L"appendInfoLine (\"P (binomial) = \", fixed\\$  (pbin, 6))")
+CODE (L"appendInfoLine: \"P (binomial) = \", fixed\\$  (pbin, 6)")
 CODE (L"\\#  Chi-square test with Yates correction:")
 CODE (L"x2 = (a - 1/2 - p * (a+b))\\^ 2/(p*(a+b)) + (b + 1/2 - (1-p) * (a+b))\\^ 2/((1-p)*(a+b))")
 CODE (L"px2 = chiSquareQ (x2, 1)")
-CODE (L"appendInfoLine (\"P (chi-square) = \", fixed\\$  (px2, 6))")
+CODE (L"appendInfoLine: \"P (chi-square) = \", fixed\\$  (px2, 6)")
 NORMAL (L"The result is:")
 CODE (L"*** Binomial test 597, 403, p = 0.583333 ***")
 CODE (L"P (binomial) = 0.199330")
@@ -544,7 +544,7 @@ TAG (L"\\bu ##self (%%x-expression%)")
 DEFINITION (L"the expression is linearly interpolated between the two nearest samples (or frames).")
 MAN_END
 
-MAN_BEGIN (L"Formulas 1.9. Formulas in scripts", L"ppgb", 20130406)
+MAN_BEGIN (L"Formulas 1.9. Formulas in scripts", L"ppgb", 20140223)
 INTRO (L"In scripts, you can assign numeric expressions to numeric variables, "
 	"and string expressions to string variables. You can also use numeric and string variables in expressions.")
 ENTRY (L"Example: report a square")
@@ -552,7 +552,7 @@ NORMAL (L"Choose @@New Praat script@ from the @@Praat menu at . A script editor win
 	"Type the following lines into that window:")
 CODE (L"x = 99")
 CODE (L"x2 = x * x")
-CODE (L"writeInfoLine (\"The square of \", x, \" is \", x2, \".\")")
+CODE (L"writeInfoLine: \"The square of \", x, \" is \", x2, \".\"")
 NORMAL (L"This is an example of a simple @@Praat script@; it assigns the results of the numeric formulas $$99$ and $$x * x$ "
 	"to the numeric variables %x and %x2. Note that the formula $$x * x$ itself refers to the variable %x. "
 	"To run (execute) this script, type Command-R or choose #Run from the #Run menu. "
@@ -565,7 +565,7 @@ CODE (L"current\\$  = \"Bush\"")
 CODE (L"previous\\$  = \"Clinton\"")
 CODE (L"famous\\$  = \"Lincoln\"")
 CODE (L"newCapital\\$  = current\\$  + mid\\$  (famous\\$ , 2, 3) + right\\$  (previous\\$ , 3)")
-CODE (L"writeInfoLine (\"The new capital will be \", newCapital\\$ , \".\")")
+CODE (L"writeInfoLine: \"The new capital will be \", newCapital\\$ , \".\"")
 NORMAL (L"This script assigns the results of four string expressions to the four string variables %%current\\$ %, "
 	"%%previous\\$ %, %%famous\\$ %, and %%newCapital\\$ %. The dollar sign is the notation for a string variable or "
 	"for a function whose result is a string (like ##left\\$ #). Note that the formula in the fourth line refers to three existing "
@@ -574,15 +574,15 @@ NORMAL (L"To see what the new name of the capital will be, choose #Run.")
 ENTRY (L"Example: numeric expressions in settings in scripts")
 NORMAL (L"As in real settings windows, you can use numeric expressions in all numeric fields. "
 	"The example of two pages back becomes:")
-CODE (L"do (\"Create Sound from formula...\", \"sine\", \"Mono\", 0, 10000 / 44100, 44100, \"0.9 * sin (2*pi*377*x)\")")
+CODE (L"Create Sound from formula: \"sine\", \"Mono\", 0, 10000 / 44100, 44100, \"0.9 * sin (2*pi*377*x)\"")
 ENTRY (L"Example: string expressions in settings in scripts")
 NORMAL (L"As in real settings windows, you can use string expressions in all text fields:")
 CODE (L"soundName\\$  = \"hello\"")
-CODE (L"do (\"Read from file...\", soundName\\$  + \".wav\")")
+CODE (L"Read from file: soundName\\$  + \".wav\"")
 ENTRY (L"Example: numeric expressions in creation in scripts")
 NORMAL (L"Suppose you want to generate a sine wave whose frequency is held in a variable. This is the way:")
 CODE (L"frequency = 377")
-CODE (L"do (\"Create Sound from formula...\", \"sine\", \"Mono\", 0, 1, 44100, \"0.9 * sin (2*pi*frequency*x)\")")
+CODE (L"Create Sound from formula: \"sine\", \"Mono\", 0, 1, 44100, \"0.9 * sin (2*pi*frequency*x)\"")
 NORMAL (L"In this example, Praat will protest if %x is a variable as well, because that would be ambiguous "
 	"with the %x that refers to the time in the sound (see @@Formulas 1.8. Formulas for modification@).")
 MAN_END
@@ -844,7 +844,7 @@ TAG (L"##besselI (%n, %x)")
 TAG (L"##besselK (%n, %x)")
 MAN_END
 
-MAN_BEGIN (L"Formulas 5. String functions", L"ppgb", 20130522)
+MAN_BEGIN (L"Formulas 5. String functions", L"ppgb", 20140223)
 INTRO (L"String functions are functions that either return a text string or have at least one text string as an argument. "
 	"Since string computations are not very useful in the @calculator, in settings windows, or in creation and "
 	"modification formulas, this page only gives examples of strings in scripts, so that the example may contain "
@@ -923,7 +923,7 @@ DEFINITION (L"the same as ##fixed\\$ #, but with a percent sign. For instance, $
 TAG (L"##number (a\\$ )")
 DEFINITION (L"interprets a string as a number.")
 		CODE2 (L"string\\$  = \"5e6\"")
-		CODE2 (L"writeInfoLine (3 + number (string\\$ ))")
+		CODE2 (L"writeInfoLine: 3 + number (string\\$ )")
 DEFINITION (L"the Info window contains the number 500003.")
 TAG (L"##date\\$  ( )")
 DEFINITION (L"gives the date and time in the following format:")
@@ -931,18 +931,20 @@ DEFINITION (L"gives the date and time in the following format:")
 DEFINITION (L"To write the day of the month into the Info window, you type:")
 		CODE2 (L"date\\$  = date\\$  ()")
 		CODE2 (L"day\\$  = mid\\$  (date\\$ , 9, 2)")
-		CODE2 (L"writeInfoLine (\"The month day is \", day\\$ , \".\")")
+		CODE2 (L"writeInfoLine: \"The month day is \", day\\$ , \".\"")
 TAG (L"##extractNumber (\"Type: Sound\" + newline\\$  + \"Name: hello there\" + newline\\$  + \"Size: 44007\", \"Size:\")")
 DEFINITION (L"looks for a number after the first occurrence of \"Size:\" in the long string. Outcome: 44007. "
 	"This is useful in scripts that try to get information from long reports, as the following script that "
 	"runs in the Sound editor window:")
-		CODE2 (L"report\\$  = do\\$  (\"Editor info\")")
+		CODE2 (L"report\\$  = Editor info")
 		CODE2 (L"maximumFrequency = extractNumber (report\\$ , \"Spectrogram window length:\")")
 TAG (L"##extractWord\\$  (\"Type: Sound\" + newline\\$  + \"Name: hello there\" + newline\\$  + \"Size: 44007\", \"Type:\")")
 DEFINITION (L"looks for a word without spaces after the first occurrence of \"Type:\" in the long string. Outcome: Sound.")
 TAG (L"##extractLine\\$  (\"Type: Sound\" + newline\\$  + \"Name: hello there\" + newline\\$  + \"Size: 44007\", \"Name: \")")
 DEFINITION (L"looks for the rest of the line (including spaces) after the first occurrence of \"Name: \" in the long string. "
 	"Outcome: hello there. Note how \"Name: \" includes a space, so that the `rest of the line' starts with the %h.")
+TAG (L"##backslashTrigraphsToUnicode\\$  (x\\$ ), unicodeToBackslashTrigraphs\\$  (x\\$ )")
+DEFINITION (L"converts e.g. \\bsct to \\ct or the reverse. See @@Special symbols at .")
 MAN_END
 
 MAN_BEGIN (L"Formulas 6. Control structures", L"ppgb", 20030519)
@@ -1048,7 +1050,7 @@ NORMAL (L"causes the sound to decay exponentially in such a way that it has only
 NORMAL (L"More examples of the use of attributes are on the next page.")
 MAN_END
 
-MAN_BEGIN (L"Formulas 8. Data in objects", L"ppgb", 20130415)
+MAN_BEGIN (L"Formulas 8. Data in objects", L"ppgb", 20140223)
 NORMAL (L"With square brackets, you can get the values inside some objects.")
 ENTRY (L"Object contents in the calculator")
 NORMAL (L"The outcomes of the following examples can be checked with the @calculator.")
@@ -1120,16 +1122,16 @@ CODE (L"sumDiagonal = 0")
 CODE (L"for i to Matrix_hello.ncol")
 	CODE1 (L"sumDiagonal += Matrix_hello [i, i]")
 CODE (L"endfor")
-CODE (L"writeInfoLine (\"The sum of cells along the diagonal is \", sumDiagonal, \".\")")
+CODE (L"writeInfoLine: \"The sum of cells along the diagonal is \", sumDiagonal, \".\"")
 NORMAL (L"This example could have been written completely with commands from the dynamic menu:")
 CODE (L"select Matrix hello")
 CODE (L"sumDiagonal = 0")
-CODE (L"ncol = do (\"Get number of columns\")")
+CODE (L"ncol = Get number of columns")
 CODE (L"for i to ncol")
-	CODE1 (L"value = do (\"Get value in cell...\", i, i)")
+	CODE1 (L"value = Get value in cell: i, i")
 	CODE1 (L"sumDiagonal += value")
 CODE (L"endfor")
-CODE (L"writeInfoLine (\"The sum of cells along the diagonal is \", sumDiagonal, \".\")")
+CODE (L"writeInfoLine: \"The sum of cells along the diagonal is \", sumDiagonal, \".\"")
 NORMAL (L"The first version, which accesses the contents directly, is not only three lines shorter, but also three times faster.")
 MAN_END
 
@@ -1188,7 +1190,7 @@ NORMAL (L"This macro mechanism is much more flexible than the usual opaque macro
 	"See the @Scripting tutorial for all the things that you can do in scripts.")
 MAN_END
 
-MAN_BEGIN (L"initialization script", L"ppgb", 20060920)
+MAN_BEGIN (L"initialization script", L"ppgb", 20140107)
 INTRO (L"Your initialization script is a normal @@Praat script@ that is run as soon as you start Praat.")
 #if defined (UNIX) || defined (macintosh)
 NORMAL (L"On Unix or MacOS X, you create an initialization script by creating a file named \"praat-startUp\" "
@@ -1206,11 +1208,11 @@ ENTRY (L"Example")
 NORMAL (L"If you like to be greeted by your husband when Praat starts up, "
 	"you could put the following lines in your initialization script:")
 #if defined (UNIX)
-	CODE (L"Read from file... /u/miep/helloMiep.wav")
+	CODE (L"Read from file: \"/u/miep/helloMiep.wav\"")
 #elif defined (macintosh)
-	CODE (L"Read from file... /Users/miep/helloMiep.wav")
+	CODE (L"Read from file: \"/Users/miep/helloMiep.wav\"")
 #elif defined (_WIN32)
-	CODE (L"Read from file... C:\\bsDocuments and Settings\\bsMiep\\bshelloMiep.wav")
+	CODE (L"Read from file: \"C:\\bsDocuments and Settings\\bsMiep\\bshelloMiep.wav\"")
 #else
 	#error Some audio file reading example should go here
 #endif
@@ -1268,7 +1270,7 @@ FORMULA (L"(1 / (0.463\\.c0.072)) (10^^(0.072/10)(10log(%I/%I__0_))^ \\-- 1) "
 	"= 30 \\.c (1.0167^^SL^ \\-- 1)")
 MAN_END
 
-MAN_BEGIN (L"plug-ins", L"ppgb", 20070129)
+MAN_BEGIN (L"plug-ins", L"ppgb", 20140212)
 INTRO (L"Experienced Praat script writers can distribute their product as a plug-in to Praat.")
 ENTRY (L"The Praat plug-in mechanism")
 NORMAL (L"When Praat starts up, it will execute all Praat scripts called ##setup.praat# "
@@ -1284,8 +1286,8 @@ NORMAL (L"Suppose that you have a set of Praat scripts specialized in the analys
 	"Suppose now that you want to distribute those two commands to other guinea pig vocalization researchers.")
 NORMAL (L"What you do is that you create a Praat script called ##setup.praat# (in the same directory as the two other scripts), "
 	"that contains the following two lines:")
-CODE (L"@@Add action command...@ Sound 1 \"\" 0 \"\" 0 \"Analyse queak\" \"\" 0 analyseQueak.praat")
-CODE (L"@@Add menu command...@ Objects New \"Create queak...\" \"\" 0 createQueak.praat")
+CODE (L"@@Add action command...|Add action command:@ \"Sound\", 1, \"\", 0, \"\", 0, \"Analyse queak\", \"\", 0, \"analyseQueak.praat\"")
+CODE (L"@@Add menu command...|Add menu command:@ \"Objects\", \"New\", \"Create queak...\", \"\", 0, \"createQueak.praat\"")
 NORMAL (L"(If you ran this script, Praat would install those two commands in the correct menus, and remember them in the @@buttons file@; "
 	"but you are now going to install them in a different way.)")
 NORMAL (L"You now put the three scripts in a new directory called ##plugin_Queak#, "
@@ -1304,7 +1306,7 @@ NORMAL (L"In the example ##setup.praat# file above, the names of the scripts ##a
 	"where ##setup.praat# is located. If your plug-in is much larger than two scripts, you may want to put subdirectories into "
 	"the directory ##plugin_Queak#. For instance, if you put ##analyseQueak.praat# into the subdirectory ##analysis#, "
 	"your line in the ##setup.praat# script would look as follows:")
-CODE (L"@@Add action command...@ Sound 1 \"\" 0 \"\" 0 \"Analyse queak\" \"\" 0 analysis/analyseQueak.praat")
+CODE (L"@@Add action command...|Add action command:@ \"Sound\", 1, \"\", 0, \"\", 0, \"Analyse queak\", \"\", 0, \"analysis/analyseQueak.praat\"")
 NORMAL (L"The forward slash (\"/\") in this example makes your plug-in platform-independent: it will work unchanged "
 	"on Windows, Macintosh, and Unix.")
 NORMAL (L"Nothing prevents you from adding data files to your plug-in. For instance, your ##plugin_Queak# directory "
@@ -1318,7 +1320,7 @@ LIST_ITEM (L"1. Create a script that adds buttons to the fixed and dynamic menus
 LIST_ITEM (L"2. Put this script where everybody can see it, "
 	"for instance in ##U:\\bsMaldenGuineaPigResearchButtons.praat#, where U is your shared computer.")
 LIST_ITEM (L"3. Create a file ##setup.praat# that contains only the following line:")
-CODE1 (L"execute U:\\bsMaldenGuineaPigResearchButtons.praat")
+CODE1 (L"runScript: \"U:\\bsMaldenGuineaPigResearchButtons.praat\"")
 LIST_ITEM (L"4. Put the ##setup.praat# file in a new directory called ##plugin_MaldenGuineaPigResearch#, "
 	"and distribute this directory among your local colleagues.")
 NORMAL (L"This procedure allows all members of the group to automatically enjoy all the later changes in your "
@@ -1363,17 +1365,7 @@ NORMAL (L"On Windows it is called ##Preferences5.ini#, "
 	"for instance ##C:\\bsDocuments and Settings\\bsMiep\\bsPraat\\bsPreferences5.ini#.")
 MAN_END
 
-MAN_BEGIN (L"Run script...", L"ppgb", 20050822)
-INTRO (L"A hidden command in the @@Praat menu at . Runs a @@Praat script at .")
-ENTRY (L"Usage")
-NORMAL (L"This command is hidden because you would normally open a script "
-	"with @@Open Praat script...@, so that you can run it several times without "
-	"selecting a file each time.")
-NORMAL (L"In scripts, the command ##%%Run script...#% is automatically replaced "
-	"by the script directive #execute.")
-MAN_END
-
-MAN_BEGIN (L"Scripting", L"ppgb", 20130428)
+MAN_BEGIN (L"Scripting", L"ppgb", 20140212)
 INTRO (L"This is one of the tutorials of the Praat program. It assumes you are familiar with the @Intro.")
 NORMAL (L"A %script is a text that consists of menu commands and action commands. "
 	"If you %run the script (perhaps from a @ScriptEditor), "
@@ -1401,16 +1393,16 @@ LIST_ITEM1 (L"@@Scripting 5.4. Loops@ (for/endfor, while/endwhile, repeat/until)
 LIST_ITEM1 (L"@@Scripting 5.5. Procedures@ (\\@ , procedure)")
 LIST_ITEM1 (L"@@Scripting 5.6. Arrays")
 LIST_ITEM1 (L"@@Scripting 5.7. Including other scripts")
-LIST_ITEM1 (L"@@Scripting 5.8. Quitting@ (exit)")
+LIST_ITEM1 (L"@@Scripting 5.8. Quitting@ (exitScript)")
 LIST_ITEM (L"@@Scripting 6. Communication outside the script")
-LIST_ITEM1 (L"@@Scripting 6.1. Arguments to the script@ (form/endform, execute)")
+LIST_ITEM1 (L"@@Scripting 6.1. Arguments to the script@ (form/endform, runScript)")
 LIST_ITEM1 (L"@@Scripting 6.2. Writing to the Info window@ (writeInfoLine, appendInfoLine, appendInfo, tab\\$ )")
 LIST_ITEM1 (L"@@Scripting 6.3. Query commands@ (Get, Count)")
 LIST_ITEM1 (L"@@Scripting 6.4. Files@ (fileReadable, readFile, writeFile, deleteFile, createDirectory)")
 LIST_ITEM1 (L"@@Scripting 6.5. Calling system commands@ (system, environment\\$ , stopwatch)")
 LIST_ITEM1 (L"@@Scripting 6.6. Controlling the user@ (pause, beginPause/endPause, chooseReadFile\\$ )")
 LIST_ITEM1 (L"@@Scripting 6.7. Sending a message to another program@ (sendsocket)")
-LIST_ITEM1 (L"@@Scripting 6.8. Messages to the user@ (exit, assert, nowarn, nocheck)")
+LIST_ITEM1 (L"@@Scripting 6.8. Messages to the user@ (exitScript, assert, nowarn, nocheck)")
 LIST_ITEM1 (L"@@Scripting 6.9. Calling from the command line")
 LIST_ITEM (L"@@Scripting 7. Scripting the editors")
 LIST_ITEM1 (L"@@Scripting 7.1. Scripting an editor from a shell script@ (editor/endeditor)")
@@ -1427,7 +1419,7 @@ LIST_ITEM (L"@@Scripting 9.2. Old functions")
 NORMAL (L"Also see the @@scripting examples at .")
 MAN_END
 
-MAN_BEGIN (L"Scripting 1. Your first scripts", L"ppgb", 20130406)
+MAN_BEGIN (L"Scripting 1. Your first scripts", L"ppgb", 20140106)
 INTRO (L"This page tells you how to create, run and save a script. "
 	"To get a feel for how it works, you are advised to try out all the steps.")
 ENTRY (L"1. A minimal script")
@@ -1436,14 +1428,14 @@ NORMAL (L"Suppose that you want to create a script that allows you to play a sel
 	"A @ScriptEditor window will appear on your screen:")
 SCRIPT (6, 4, L""
 	Manual_DRAW_WINDOW (4, "untitled script", "File   Edit   Search   Convert   Font   Run   Help")
-	"do (\"Draw rectangle...\", 0, 560, 0, 360)\n"
-	"info$ = do$ (\"Picture info\")\n"
+	"Draw rectangle: 0, 560, 0, 360\n"
+	"info$ = Picture info\n"
 	"fontSize = extractNumber (info$, \"Font size: \")\n"
-	";do (\"Text...\", 50, \"centre\", 50, \"half\", fontSize, \"\")\n"
+	";Text: 50, \"centre\", 50, \"half\", string$ (fontSize)\n"
 )
 NORMAL (L"In this window, you type")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Play\")")
+CODE (L"Play")
+CODE (L"Play")
 NORMAL (L"Now select a Sound in the ##Praat Objects# window. As you expect from selecting a Sound, a #Play button will "
 	"appear in the dynamic menu. If you now choose #Run from the #Run menu in the ScriptEditor, Praat will play the sound twice. "
 	"This works because #Play is a command that becomes available in the dynamic menu when you select a Sound.")
@@ -1453,18 +1445,18 @@ NORMAL (L"In the above example, you could use the #Play command because that was
 	"Apart from these selection-dependent (dynamic) commands, "
 	"you can also use all fixed commands from the menus of the @@Object window@ "
 	"and the @@Picture window at . For instance, try the following script:")
-CODE (L"do (\"Erase all\")")
-CODE (L"do (\"Draw inner box\")")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Erase all\")")
+CODE (L"Erase all")
+CODE (L"Draw inner box")
+CODE (L"Play")
+CODE (L"Play")
+CODE (L"Erase all")
 NORMAL (L"When you run this script, you'll see a rectangle appear in the ##Praat Picture# window "
 	"(that's what the command ##Draw inner box# in the #Margins menu does), "
 	"then you'll hear the Sound play tiwce, then you'll see the rectangle disappear from the Picture window "
 	"(that's what the command ##Erase all# from the #Edit menu does).")
 NORMAL (L"Here we see that the Praat scripting language is an example of a %%procedural programming language%, "
 	"which means that the five %statements are executed in the order in which they appear in the script, "
-	"i.e. first ##do (\"Erase all\")#, then ##do (\"Draw inner box\")#, then ##do (\"Play\")# twice, and finally ##do (\"Erase all\")#.")
+	"i.e. first ##Erase all#, then ##Draw inner box#, then ##Play# twice, and finally ##Erase all#.")
 ENTRY (L"3. Experimenting with your script")
 NORMAL (L"You don't have to be afraid of making mistakes. Here are a couple that you can try to make.")
 NORMAL (L"First, try to run the script when a Sound is not selected "
@@ -1473,8 +1465,8 @@ NORMAL (L"First, try to run the script when a Sound is not selected "
 	"Indeed, if you select a Pitch or if you select nothing, then no command #Play appears in the dynamic menu, "
 	"so the script cannot execute it. Note that the commands ##Erase all# and ##Draw inner box# are still available, "
 	"because they continue to be present in the menus of the Picture window; "
-	"therefore, the script will execute the first two lines ($$do (\"Erase all\") $and$$ do (\"Draw inner box\")$) "
-	"and stop running at the third line, i.e. at your first$$do (\"Play\")$. "
+	"therefore, the script will execute the first two lines ($$Erase all $and$$ Draw inner box$) "
+	"and stop running at the third line, i.e. at your first$$ Play$. "
 	"The result is that the \"box\" will stay visible in the Picture window, because the fifth line of the script, "
 	"which should erase the box, is never executed.")
 NORMAL (L"Second, try to mistype a command (there's a good chance you already did it by accident), "
@@ -1489,13 +1481,13 @@ NORMAL (L"Please try this with the five-line script you just typed. "
 	"After saving the script, the name of the script file will appear in the window title:")
 SCRIPT (6, 4, L""
 	Manual_DRAW_WINDOW (4, "Script \"/Users/Rose/Desktop/test.praat\"", "File   Edit   Search   Convert   Font   Run   Help")
-	"do (\"Courier\")\n"
-	"do (\"Text...\", 0, \"left\",  75, \"half\", \"\\s{do (\"\"Erase all\"\")}\")\n"
-	"do (\"Text...\", 0, \"left\",  90, \"half\", \"\\s{do (\"\"Draw inner box\"\")}\")\n"
-	"do (\"Text...\", 0, \"left\", 105, \"half\", \"\\s{do (\"\"Play\"\")}\")\n"
-	"do (\"Text...\", 0, \"left\", 120, \"half\", \"\\s{do (\"\"Play\"\")}\")\n"
-	"do (\"Text...\", 0, \"left\", 135, \"half\", \"\\s{do (\"\"Erase all\"\")}\")\n"
-	"do (\"Draw rectangle...\", 0, 560, 0, 360)\n"
+	"Courier\n"
+	"Text: 0, \"left\",  75, \"half\", \"\\s{Erase all}\"\n"
+	"Text: 0, \"left\",  90, \"half\", \"\\s{Draw inner box}\"\n"
+	"Text: 0, \"left\", 105, \"half\", \"\\s{Play}\"\n"
+	"Text: 0, \"left\", 120, \"half\", \"\\s{Play}\"\n"
+	"Text: 0, \"left\", 135, \"half\", \"\\s{Erase all}\"\n"
+	"Draw rectangle: 0, 560, 0, 360\n"
 )
 NORMAL (L"After you save your script, you can close the ScriptEditor window without losing the script: "
 	"you can reopen the script file by using @@Open Praat script...@ from the #Praat menu, "
@@ -1505,7 +1497,7 @@ NORMAL (L"It advisable to use$$ .praat $as the extension for script file names.
 	"On the Mac and on Windows, if you drag a$$ .praat $file on the Praat icon, Praat will also start up and show the script.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 2. How to script settings windows", L"ppgb", 20130406)
+MAN_BEGIN (L"Scripting 2. How to script settings windows", L"ppgb", 20140119)
 INTRO (L"Not all menu commands are as simple as those on the @@Scripting 1. Your first scripts|previous page@, "
 	"which act immediately once you choose them from a menu (e.g. ##Play#, ##Erase all#). "
 	"Most commands in Praat require the user to supply additional information; "
@@ -1523,12 +1515,11 @@ SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (4), L""
 NORMAL (L"In this example, all the settings have their standard values: you want to draw the whole time domain of the Sound, "
 	"you want to have autoscaling vertically, you want to see garnishings around the picture (a box, labelled axes, and numbers), "
 	"and you want the waveform to be drawn as a curve. Pressing the OK button in the above window is equivalent to executing the following script line:")
-CODE (L"do (\"Draw...\", 0, 0, 0, 0, \"yes\", \"Curve\")")
-NORMAL (L"You see that in a script, all of the arguments are supplied inside the same "
-	"#do function as the command, separated by commas, "
+CODE (L"Draw: 0, 0, 0, 0, \"yes\", \"Curve\"")
+NORMAL (L"You see that in a script, all of the arguments are supplied after the command, preceded by a colon and separated by commas, "
 	"in the same order as in the settings window, counted from top to bottom (and, within a line, from left to right). "
 	"The texts \"(= all)\" and \"(= auto)\" above are just Praat's explanations of what it means to type a zero in those fields "
-	"(namely \"draw all times\" and \"use vertical autoscaling\", respectively); in a script they are superfluous and you shouldn't write them.")
+	"(namely `draw all times' and `use vertical autoscaling', respectively); in a script they are superfluous and you shouldn't write them.")
 NORMAL (L"If you want to draw the sound with different settings, say from 1 to 3.2 seconds, scaled between -1 and +1 instead of automatically, "
 	"with garnishings off, and with the waveform drawn as poles, you would have the following settings window:")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (4), L""
@@ -1539,7 +1530,7 @@ SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (4), L""
 	Manual_DRAW_SETTINGS_WINDOW_OPTIONMENU ("Drawing method", "Poles")
 )
 NORMAL (L"In a script this would look like")
-CODE (L"do (\"Draw...\", 1.0, 3.2, -1, 1, \"no\", \"Poles\")")
+CODE (L"Draw: 1.0, 3.2, -1, 1, \"no\", \"Poles\"")
 ENTRY (L"1. Numeric arguments")
 NORMAL (L"The first four arguments in the above examples are %%numeric arguments%: they are (real or integer) numbers. "
 	"You just write them in the script as you would write them into the settings window.")
@@ -1566,7 +1557,7 @@ SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (6.1), L""   // 7 - 3 * 0.3 (three is
 )
 NORMAL (L"In supplying arguments to a command in a script, there is no difference between an option menu and a radio box. "
 	"This last example will therefore again look like the following in a script:")
-CODE (L"do (\"Draw...\", 1.0, 3.2, -1, 1, \"no\", \"Poles\")")
+CODE (L"Draw: 1.0, 3.2, -1, 1, \"no\", \"Poles\"")
 ENTRY (L"4. Text arguments")
 NORMAL (L"Consider another frequently used menu command, namely ##Create Sound from formula...# in the #New menu:")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (6.6), L""
@@ -1579,65 +1570,51 @@ SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (6.6), L""
 	Manual_DRAW_SETTINGS_WINDOW_TEXT ("Formula", "1/2 * sin(2*pi*377*x)")
 )
 NORMAL (L"In a script this would look like:")
-CODE (L"do (\"Create Sound from formula...\", \"sine\", 1, 0.0, 1.0, 44100, \"1/2 * sin(2*pi*377*x))\"")
+CODE (L"Create Sound from formula: \"sine\", 1, 0.0, 1.0, 44100, \"1/2 * sin(2*pi*377*x)\"")
 NORMAL (L"Both the first argument (#Name) and the sixth argument (#Formula) are %%text arguments%. "
 	"In a script they are written within quotes.")
 ENTRY (L"5. File arguments")
 NORMAL (L"The commands from the Open and Save menus, and several other commands whose names "
 	"start with #Read, #Open, or #Save, present a %%file selector window% instead of a typical Praat "
 	"settings window. File selector windows ask the user to supply a single argument: the file name.")
-#if defined (UNIX)
-	NORMAL (L"In a script, you can supply the complete %path, including the directory (folder) hierarchy "
-	"and the name of the file. On Unix, it goes like this (if you are user \"miep\"):")
-	CODE (L"do (\"Read from file...\", \"/home/miep/sounds/animals/miauw.aifc\")")
-	NORMAL (L"or just")
-	CODE (L"do (\"Read from file...\", \"~/sounds/animals/miauw.aifc\")")
-	NORMAL (L"where \"~\" is the Unix way to refer to your home directory.")
-#elif defined (macintosh)
-	NORMAL (L"In a script, you can supply the complete %path, including the directory (folder) hierarchy "
-	"and the name of the file. On MacOS X, it goes like this (if you are user \"miep\"):")
-	CODE (L"do (\"Read from file...\", \"/Users/miep/Sounds/Animals/miauw.aifc\")")
-	NORMAL (L"or just")
-	CODE (L"do (\"Read from file...\", \"~/Sounds/Animals/miauw.aifc\")")
-	NORMAL (L"where \"~\" is the Unix way to refer to your home directory. If your file is on the desktop, the command would be:")
-	CODE (L"do (\"Read from file...\", \"/Users/miep/Desktop/miauw.aifc\")")
-	NORMAL (L"or just")
-	CODE (L"do (\"Read from file...\", \"~/Desktop/miauw.aifc\")")
-	NORMAL (L"If your Sounds folder is on a USB drive called Miep, it would be:")
-	CODE (L"do (\"Read from file...\", \"/Volumes/Miep/Sounds/Animals/miauw.aifc\")")
-#elif defined (_WIN32)
-	NORMAL (L"In a script, you can supply the complete %path, including the directory (folder) hierarchy "
-	"and the name of the file. In Windows, it goes like this:")
-	CODE (L"do (\"Read from file...\", \"D:\\bsSounds\\bsAnimals\\bsmiauw.aifc\")")
-#else
-	#error Supply an example complete path to a sound file
-#endif
-NORMAL (L"Instead of these complete path names, you can use %relative path names. "
-	"These are taken as relative to the directory in which your script resides.")
-#if defined (UNIX)
-	NORMAL (L"On Unix, a relative path name starts without a \"/\". So if your script is "
-		"%%/home/miep/sounds/analysis.praat%, the above line could be")
-	CODE (L"do (\"Read from file...\", \"animals/miauw.aifc\")")
-	NORMAL (L"Finally, your script may not be in a directory %above the directory from which you "
-		"like to read, but in a directory on the side, like /home/miep/scripts. The command would then read")
-	CODE (L"do (\"Read from file...\", \"../animals/miauw.aifc\")")
-#elif defined (macintosh)
-	NORMAL (L"On MacOS X, a relative path name starts without a \"/\". So if your script is "
-		"%%/Users/miep/Sounds/analysis.praat%, the above line could be")
-	CODE (L"do (\"Read from file...\", \"Animals/miauw.aifc\")")
-	NORMAL (L"Finally, your script may not be in a directory %above the directory from which you "
-		"like to read, but in a directory on the side, like /Users/miep/scripts. The command would then read")
-	CODE (L"do (\"Read from file...\", \"../Animals/miauw.aifc\")")
-#elif defined (_WIN32)
-	NORMAL (L"In Windows, a relative path name starts without a backslash. So if your script is "
-		"C:\\bsSounds\\bsAnalysis.praat, the sound file is read by")
-	CODE (L"do (\"Read from file...\", \"Animals\\bsmiauw.aifc\")")
-	NORMAL (L"Finally, your script may not be in a directory %above the directory from which you "
-		"like to read, but in a directory on the side, like D:\\bsScripts. The commands would then read")
-	CODE (L"do (\"Read from file...\", \"..\\bsAnimals\\bsmiauw.aifc\")")
-#else
-	#error Supply an example partial path to a sound file
-#endif
+NORMAL (L"In a script you can either supply the %%complete path% to the file, or supply a %%relative path%.")
+NORMAL (L"A complete path includes both the directory (folder) hierarchy and the name of the file. "
+	"This goes slightly differently on the Windows platform on the one hand, "
+	"and on the Mac and Linux platforms on the other. If your user name is Miep, "
+	"and your home directory contains a folder #Sounds, "
+	"and this folder contains a folder #Animals, and this contains the file ##miauw.wav#, "
+	"you can open that file as follows:")
+CODE (L"Read from file: \"C:/Users/Miep/Sounds/Animals/miauw.wav\"   ; Windows")
+CODE (L"Read from file: \"/Users/Miep/Sounds/Animals/miauw.wav\"   ; Mac")
+CODE (L"Read from file: \"/home/miep/Sounds/Animals/miauw.wav\"   ; Linux")
+NORMAL (L"(the part before your user name may be slightly different on your computer; "
+	"use your command or terminal window to find out)")
+NORMAL (L"In these examples, \"C\" is the Windows %%drive letter% and "
+	"##/Users/Miep# or ##/home/Miep# is your %%home directory%. Both the home directory and the drive letter "
+	"can be abbreviated away by using the tilde (\"~\"):")
+CODE (L"Read from file: \"~/Sounds/Animals/miauw.wav\"")
+NORMAL (L"If your #Sounds folder is not in your home directory but on your desktop, you do")
+CODE (L"Read from file: \"~/Desktop/Sounds/Animals/miauw.wav\"")
+NORMAL (L"(this works because on all three platforms, the desktop folder is a subfolder of your home directory)")
+NORMAL (L"If your Sounds folder is on a USB drive called PORCH, it would be something like:")
+CODE (L"Read from file: \"G:/Sounds/Animals/miauw.wav\"   ; Windows")
+CODE (L"Read from file: \"/Volumes/PORCH/Sounds/Animals/miauw.wav\"   ; Mac")
+CODE (L"Read from file: \"/media/PORCH/Sounds/Animals/miauw.wav\"   ; Linux")
+NORMAL (L"Instead of all these complete path names, you can use %relative path names. "
+	"These are taken as relative to the directory in which your script resides, "
+	"and help to make your script portable if you move the script along with your data.")
+NORMAL (L"Thus, if your script (after you have saved it!) is in the #Animals folder mentioned above, "
+	"i.e. in the same folder as ##miauw.wav#, you would simply open the file with")
+CODE (L"Read from file: \"miauw.wav\"")
+NORMAL (L"If your script is in the #Sounds folder mentioned above, "
+	"i.e. in the same folder as where the #Animals folder is, you would open the file with")
+CODE (L"Read from file: \"Animals/miauw.aifc\"")
+NORMAL (L"If your script is in the folder #Scripts that is inside the #Sounds folder, "
+	"i.e. if your script is a sister folder of the #Animals folder, you would open the file with")
+CODE (L"Read from file: \"../Animals/miauw.aifc\"")
+NORMAL (L"where \"..\" is the general way on all platforms to go one folder up in the hierarchy.")
+NORMAL (L"Note that on Windows you could use the backslash (\"\\bs\") instead of the forward slash (\"/\"), "
+	"but with the forward slash your script will work on all three platforms.")
 ENTRY (L"6. How to supply arguments automatically")
 NORMAL (L"Now you know all the ways to write the arguments of commands in a script line. "
 	"If you dislike manually copying arguments from settings windows into your script, "
@@ -1666,71 +1643,71 @@ MAN_END
 #define Manual_DRAW_PICTURE_WINDOW(height,vpLeft,vpRight,vpTop,vpBottom) \
 	Manual_DRAW_WINDOW (height, "Praat Picture", "File   Edit   Margins   World   Select   Pen   Font   Help") \
 	"worldHeight = " #height " - 1\n" \
-	"Select inner viewport... 0.2 5.8 0.8 0.8+worldHeight\n" \
-	"Axes... 0 5.6 worldHeight 0\n" \
+	"Select inner viewport: 0.2, 5.8, 0.8, 0.8+worldHeight\n" \
+	"Axes: 0, 5.6, worldHeight, 0\n" \
 	"vpLeft = " #vpLeft "\nvpRight = " #vpRight "\nvpTop = " #vpTop "\nvpBottom = " #vpBottom "\n" \
-	"Paint rectangle... Pink vpLeft vpRight vpTop vpBottom\n" \
-	"Paint rectangle... White vpLeft+0.69 vpRight-0.69 vpTop+0.46 vpBottom-0.46\n" \
+	"Paint rectangle: \"Pink\", vpLeft, vpRight, vpTop, vpBottom\n" \
+	"Paint rectangle: \"White\", vpLeft+0.69, vpRight-0.69, vpTop+0.46, vpBottom-0.46\n" \
 	"Yellow\n" \
-	"Draw line... 3 0 3 worldHeight\n" \
+	"Draw line: 3, 0, 3, worldHeight\n" \
 	"for i to worldHeight/3\n" \
-	"   Draw line... 0 i*3 5.6 i*3\n" \
+	"   Draw line: 0, i*3, 5.6, i*3\n" \
 	"Red\n" \
 	"for i to 5\n" \
-	"   Text special... i centre 0 top Helvetica fontSize/1.2 0 'i'\n" \
+	"   Text special: i, \"centre\", 0, \"top\", \"Helvetica\", fontSize/1.2, \"0\", string$(i)\n" \
 	"endfor\n" \
 	"for i to worldHeight\n" \
-	"   Text special... 0 left i half Helvetica fontSize/1.2 0 'i'\n" \
+	"   Text special: 0, \"left\", i, \"half\", \"Helvetica\", fontSize/1.2, \"0\", string$(i)\n" \
 	"endfor\n" \
 	"Black\n" \
-	"Draw line... 0 0 5.6 0\n" \
+	"Draw line: 0, 0, 5.6, 0\n" \
 
-MAN_BEGIN (L"Scripting 3.1. Hello world", L"ppgb", 20130421)
+MAN_BEGIN (L"Scripting 3.1. Hello world", L"ppgb", 20140111)
 INTRO (L"Many manuals of computer programming languages start with their answer on the following question:")
 NORMAL (L"%%How do I write the text \"Hello world\" on the screen?")
 NORMAL (L"For the Praat scripting language, there are two answers.")
 ENTRY (L"1. \"Hello world\" in the Info window")
 NORMAL (L"The simplest answer is that you open the ScriptEditor window with ##New Praat script# from the #Praat menu, "
 	"then type the following line into the ScriptEditor window:")
-CODE (L"writeInfoLine (\"Hello world\")")
+CODE (L"writeInfoLine: \"Hello world\"")
 NORMAL (L"and finally choose #Run from the #Run menu.")
 NORMAL (L"When you try this, the result should be that the Info window comes to the front, and that it shows the text $$Hello world$:")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{Hello world}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{Hello world}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 NORMAL (L"Now suppose that you to write two lines instead of just one, so you try a script with two lines:")
-CODE (L"writeInfoLine (\"Hello world\")")
-CODE (L"writeInfoLine (\"How do you do?\")")
+CODE (L"writeInfoLine: \"Hello world\"")
+CODE (L"writeInfoLine: \"How do you do?\"")
 NORMAL (L"This turns out not to do what you want: it seems to write only the text $$How do you do?$. "
 	"This happens because the #writeInfoLine function first erases the Info window, then writes the line of text. "
 	"So the first line of the script did write the text $$Hello world$, but the second line wiped it out "
 	"and wrote $$How do you do?$ instead. The script that does what you want is")
-CODE (L"writeInfoLine (\"Hello world\")")
-CODE (L"appendInfoLine (\"How do you do?\")")
+CODE (L"writeInfoLine: \"Hello world\"")
+CODE (L"appendInfoLine: \"How do you do?\"")
 NORMAL (L"Now the result will be")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{Hello world}\n"
-	"Text... 0 left 90 half \\s{How do you do?}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{Hello world}\"\n"
+	"Text: 0, \"left\", 90, \"half\", \"\\s{How do you do?}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
-NORMAL (L"This works because #appendInfoLine write a line without erasing the Info window first.")
+NORMAL (L"This works because #appendInfoLine writes a line without erasing the Info window first.")
 NORMAL (L"Finally, try the following script:")
-CODE (L"appendInfoLine (\"Another try\")")
-CODE (L"appendInfoLine (\"Goodbye\")")
+CODE (L"appendInfoLine: \"Another try\"")
+CODE (L"appendInfoLine: \"Goodbye\"")
 NORMAL (L"The result could be")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{Hello world}\n"
-	"Text... 0 left 90 half \\s{How do you do?}\n"
-	"Text... 0 left 105 half \\s{Another try}\n"
-	"Text... 0 left 120 half \\s{Goodbye}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{Hello world}\"\n"
+	"Text: 0, \"left\", 90, \"half\", \"\\s{How do you do?}\"\n"
+	"Text: 0, \"left\", 105, \"half\", \"\\s{Another try}\"\n"
+	"Text: 0, \"left\", 120, \"half\", \"\\s{Goodbye}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 NORMAL (L"In other words, #appendInfoLine writes lines into the Info window without erasing it, even if you run a script anew. "
 	"This is why many Praat scripts that write into the Info window do a #writeInfoLine first, and follow it with a series of #appendInfoLine calls.")
@@ -1740,25 +1717,25 @@ NORMAL (L"You can also show text in the Picture window. If you are an experience
 	"You can use it do draw a text at the top of the current %viewport, which is the part of the Picture window where the next drawing will occur "
 	"and which is marked by the pink %margins. Thus, when you select the top 4\\xx3 inches of the Picture window (with the mouse), "
 	"set the font size to 12 (with the #Pen menu), and run the script")
-CODE (L"do (\"Text top...\", \"yes\", \"Hello world\")")
+CODE (L"Text top: \"yes\", \"Hello world\"")
 NORMAL (L"then you'll see")
 SCRIPT (6, 4.5, L""
 	Manual_DRAW_PICTURE_WINDOW (4.5, 0,4,0,3)
-	"Select outer viewport... 0.2 4.2 0.8 12\n" \
+	"Select outer viewport: 0.2, 4.2, 0.8, 12\n" \
 	"Times\n" \
-	"Text top... yes Hello world\n" \
-	"Select inner viewport... 0.2 5.8 0.2 4.3\n"\
-	"Axes... 0 1 0 1\n" \
-	"Draw rectangle... 0 1 0 1\n"
+	"Text top: \"yes\", \"Hello world\"\n" \
+	"Select inner viewport: 0.2, 5.8, 0.2, 4.3\n"\
+	"Axes: 0, 1, 0, 1\n" \
+	"Draw rectangle: 0, 1, 0, 1\n"
 )
 NORMAL (L"So this works the same as when you choose ##Text top...# from the #Margins menu by hand, with #Far switched on.")
 NORMAL (L"If you want your script to always show the same text at the same position, with nothing else in the picture, "
 	"then you can make your script a bit more extensive:")
-CODE (L"do (\"Erase all\")")
-CODE (L"do (\"Times\")")
-CODE (L"do (\"Font size...\", 12)")
-CODE (L"do (\"Select outer viewport...\", 0, 4, 0, 3)")
-CODE (L"do (\"Text top...\", \"yes\", \"Hello world\")")
+CODE (L"Erase all")
+CODE (L"Times")
+CODE (L"Font size: 12")
+CODE (L"Select outer viewport: 0, 4, 0, 3")
+CODE (L"Text top: \"yes\", \"Hello world\"")
 NORMAL (L"In this script, line 1 erases the Picture window, so that nothing besides your text can appear in the Picture window.")
 NORMAL (L"Line 2 executes the command #Times from the #Font menu, so that the script will always draw the text in Times, "
 	"even if you choose #Helvetica in the #Font menu with the mouse before you run the script "
@@ -1775,7 +1752,7 @@ NORMAL (L"Line 5 finally writes the text.")
 NORMAL (L"For more information on these commands, see @@Picture window at .")
 MAN_END
 
-MAN_BEGIN (L"Scripting 3.2. Numeric variables", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 3.2. Numeric variables", L"ppgb", 20130411)
 INTRO (L"In any general procedural programming language you can work with %variables, "
 	"which are places in your computer's memory where you can store a number or anything else.")
 NORMAL (L"For instance, you could put the number 3.1 into the variable $b in the following way:")
@@ -1789,19 +1766,19 @@ NORMAL (L"You can regard a variable as a box: you put the value 3.1 into the box
 NORMAL (L"To see what value a variable contains (what's in the box, or who lives in the house), "
 	"you can use the #writeInfoLine function:")
 CODE (L"b = 3.1")
-CODE (L"writeInfoLine (\"The value is \", b, \".\")")
+CODE (L"writeInfoLine: \"The value is \", b, \".\"")
 NORMAL (L"This will put the text $$The value is 3.1.$ into the Info window, as you are invited to verify.")
 NORMAL (L"A variable is called a variable because it is %variable, i.e. its value can change. Try the script")
 CODE (L"b = 3.1")
 CODE (L"b = 5.8")
-CODE (L"writeInfoLine (\"The value is \", b, \".\")")
+CODE (L"writeInfoLine: \"The value is \", b, \".\"")
 NORMAL (L"You will see that $b ends up having the value 5.8. The first line puts the value 3.1 there, but the second line "
 	"replaces it with 5.8. It's like taking the 3.1 out of the box and putting the 5.8 in its stead. "
 	"Or the family 3.1 moves from the house, and the family called 5.8 moves in.")
 NORMAL (L"In an assignment, the part to the right of the \"becomes\" sign (the \"=\" sign) doesn't have to be a number; "
 	"it can be any %formula that %evaluates to a number. For instance, the script")
 CODE (L"b = 3.1 * 2")
-CODE (L"writeInfoLine (\"The value is \", b, \".\")")
+CODE (L"writeInfoLine: \"The value is \", b, \".\"")
 NORMAL (L"puts the text $$The value is 6.2.$ into the Info window. This works because Praat handles the first line "
 	"in the following way:")
 LIST_ITEM (L"1. the formula $$3.1 * 2$ is %evaluated (i.e. its value is computed), and the result is 6.2.")
@@ -1811,7 +1788,7 @@ NORMAL (L"After line 1 has been executed, the variable $b just contains the valu
 NORMAL (L"Formulas can contain more things than numbers: they can also contain other variables:")
 CODE (L"b = 3.1")
 CODE (L"c = b * 2")
-CODE (L"writeInfoLine (\"The value of b is \", b, \", and the value of c is \", c, \".\")")
+CODE (L"writeInfoLine: \"The value of b is \", b, \", and the value of c is \", c, \".\"")
 NORMAL (L"In the first line, $b gets the value 3.1. In the second line, the formula $$b * 2$ first has to be evaluated. "
 	"Praat looks up the value of $b (which is 3.1), so that it knows that the formula actually means $$3.1 * 2$. "
 	"Praat evaluates this formula and stores the result (namely the value 6.2) "
@@ -1821,7 +1798,7 @@ NORMAL (L"After these explanations, consider the following script:")
 CODE (L"b = 3.1")
 CODE (L"c = b * 2")
 CODE (L"b = 5.8")
-CODE (L"writeInfoLine (\"The value of c is \", c, \".\")")
+CODE (L"writeInfoLine: \"The value of c is \", c, \".\"")
 NORMAL (L"Can you figure out what the Info will report? If you think it will report "
 	"$$The value of c is 6.2.$, then you are correct: after the first line, $b contains the value 3.1; "
 	"after the second line, the value of $c is therefore 6.2, and nothing more; "
@@ -1834,7 +1811,7 @@ NORMAL (L"If you thought that $c would end up having the value 11.6, then you're
 	"consult anybody who writes programs.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 3.3. Numeric queries", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 3.3. Numeric queries", L"ppgb", 20140111)
 INTRO (L"Now that you know how to script a menu command, and you know how variables work, "
 	"you are ready to combine the two.")
 NORMAL (L"Suppose you have selected a Sound in the object list. One of the commands available in the #Query menu "
@@ -1847,15 +1824,15 @@ NORMAL (L"When you click OK, something like the following will appear in the Inf
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{0.1350605005239421 Pa2}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{0.1350605005239421 Pa2}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 NORMAL (L"This is the mean power of the whole Sound.")
 NORMAL (L"In a script, you want to use the value of this power in the script itself, not in the Info window, "
 	"perhaps because you want to do computations with it or because you want to report the value with a nice text around it. "
 	"This is how you do the latter:")
-CODE (L"power = do (\"Get power...\", 0.0, 0.0)")
-CODE (L"writeInfoLine (\"The power of this sound is \", power, \" Pascal-squared.\")")
+CODE (L"power = Get power: 0.0, 0.0")
+CODE (L"writeInfoLine: \"The power of this sound is \", power, \" Pascal-squared.\"")
 NORMAL (L"The first line of this script executes the menu command ##Get power...#, "
 	"but puts the value 0.1350605005239421 into the variable $power instead of into the Info window "
 	"(the variable can have any name you like, as long as it starts with a lower-case letter "
@@ -1864,18 +1841,18 @@ NORMAL (L"The second line then reports the value in the Info window, this time w
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{The power of this sound is 0.1350605005239421 Pascal-squared.}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{The power of this sound is 0.1350605005239421 Pascal-squared.}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 MAN_END
 
-MAN_BEGIN (L"Scripting 3.4. String variables", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 3.4. String variables", L"ppgb", 20130411)
 INTRO (L"Just as you can store @@Scripting 3.2. Numeric variables|numeric variables@, "
 	"you can store %%string variables%, which contain text instead of numbers. Here is an example:")
 CODE (L"word1\\$  = \"Hello\"")
 CODE (L"word2\\$  = \"world\"")
 CODE (L"sentence\\$  = word1\\$  + \" \" + word2\\$ ")
-CODE (L"writeInfoLine (\"The whole sentence is: \", sentence\\$ )")
+CODE (L"writeInfoLine: \"The whole sentence is: \", sentence\\$ ")
 NORMAL (L"Yes, this is another way to get the sentence $$Hello world$ into the Info window. "
 	"It's a more linguistically valid way to do it, and here is how it works:")
 LIST_ITEM (L"1. In line 1, the value \"Hello\", which is a text (as we can see by its use of quotes), "
@@ -1891,7 +1868,7 @@ LIST_ITEM (L"6. Still in line 3, the string value \"Hello world\" is assigned to
 LIST_ITEM (L"7. Line 4 reports in the Info window: $$The whole sentence is: Hello world$")
 MAN_END
 
-MAN_BEGIN (L"Scripting 3.5. String queries", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 3.5. String queries", L"ppgb", 20140111)
 INTRO (L"Just as you can use menu commands (usually in a #Query menu) to query @@Scripting 3.3. Numeric queries|numbers@, "
 	"you can query texts as well.")
 NORMAL (L"For instance, when you select a Textgrid, the #Query menu will contain the command ##Get label of interval...#, "
@@ -1905,104 +1882,104 @@ NORMAL (L"When you click OK, and interval 3 of tier 1 happens to contain the tex
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{hello}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{hello}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 NORMAL (L"In a script, you will want to put the result of the query in a string variable instead of in the Info window, "
 	"because you want to manipulate it further:")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, 3)")
-CODE (L"writeInfoLine (\"The text in interval 3 of tier 1 is: \", text\\$ )")
+CODE (L"text\\$  = Get label of interval: 1, 3")
+CODE (L"writeInfoLine: \"The text in interval 3 of tier 1 is: \", text\\$ ")
 NORMAL (L"The script first stores the text of the interval, i.e. $$hello$, into the variable ##text\\$ #, "
 	"then writes it, preceded by some informative text, into the Info window:")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{The text in interval 3 of tier 1 is: hello}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{The text in interval 3 of tier 1 is: hello}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 NORMAL (L"Hey, yet another way to implement \"Hello world\" with the Praat scripting language!")
 ENTRY (L"The difference between numeric queries and string queries")
-NORMAL (L"A string query, with the ##do\\$ # function, stores in a string variable the whole text that would appear in the Info window. "
+NORMAL (L"A string query stores in a string variable the whole text that would appear in the Info window. "
 	"For instance, the script")
-CODE (L"power\\$  = do\\$  (\"Get power...\", 0.0, 0.0)")
-CODE (L"writeInfoLine (power\\$ )")
+CODE (L"power\\$  = Get power: 0.0, 0.0")
+CODE (L"writeInfoLine: power\\$ ")
 NORMAL (L"could give you the following result:")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{0.1350605005239421 Pa2}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{0.1350605005239421 Pa2}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
-NORMAL (L"A numeric query, with the ##do# function, stores in a numeric variable only the first number that it can find in the text that would appear in the Info window. "
+NORMAL (L"A numeric query stores in a numeric variable only the first number that it can find in the text that would appear in the Info window. "
 	"For instance, the script")
-CODE (L"power = do (\"Get power...\", 0.0, 0.0)")
-CODE (L"writeInfoLine (power)")
+CODE (L"power = Get power: 0.0, 0.0")
+CODE (L"writeInfoLine: power")
 NORMAL (L"could give you the following result:")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{0.1350605005239421}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{0.1350605005239421}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 MAN_END
 
-MAN_BEGIN (L"Scripting 3.6. \"For\" loops", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 3.6. \"For\" loops", L"ppgb", 20140111)
 INTRO (L"The power of a procedural programming language is most easily illustrated with the %%for-loop%.")
 NORMAL (L"Take the example of the @@Scripting 3.5. String queries|previous page@, "
 	"whereas you wanted to know the text in the third interval of the first tier of a selected TextGrid. "
 	"It's easy to imagine that you actually want the texts of %%all the first five% intervals. "
 	"With knowledge from the previous sections, you could write it like this:")
-CODE (L"writeInfoLine (\"The texts in the first five intervals:\")")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, 1)")
-CODE (L"appendInfoLine (\"Interval 1: \", text\\$ )")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, 2)")
-CODE (L"appendInfoLine (\"Interval 2: \", text\\$ )")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, 3)")
-CODE (L"appendInfoLine (\"Interval 3: \", text\\$ )")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, 4)")
-CODE (L"appendInfoLine (\"Interval 4: \", text\\$ )")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, 5)")
-CODE (L"appendInfoLine (\"Interval 5: \", text\\$ )")
+CODE (L"writeInfoLine: \"The texts in the first five intervals:\"")
+CODE (L"text\\$  = Get label of interval: 1, 1")
+CODE (L"appendInfoLine: \"Interval 1: \", text\\$ ")
+CODE (L"text\\$  = Get label of interval: 1, 2")
+CODE (L"appendInfoLine: \"Interval 2: \", text\\$ ")
+CODE (L"text\\$  = Get label of interval: 1, 3")
+CODE (L"appendInfoLine: \"Interval 3: \", text\\$ ")
+CODE (L"text\\$  = Get label of interval: 1, 4")
+CODE (L"appendInfoLine: \"Interval 4: \", text\\$ ")
+CODE (L"text\\$  = Get label of interval: 1, 5")
+CODE (L"appendInfoLine: \"Interval 5: \", text\\$ ")
 NORMAL (L"The result will be something like")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{The texts in the first five intervals:}\n"
-	"Text... 0 left 90 half \\s{Interval 1: I}\n"
-	"Text... 0 left 105 half \\s{Interval 2: say}\n"
-	"Text... 0 left 120 half \\s{Interval 3: hello}\n"
-	"Text... 0 left 135 half \\s{Interval 4: and}\n"
-	"Text... 0 left 150 half \\s{Interval 5: you}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{The texts in the first five intervals:}\"\n"
+	"Text: 0, \"left\", 90, \"half\", \"\\s{Interval 1: I}\"\n"
+	"Text: 0, \"left\", 105, \"half\", \"\\s{Interval 2: say}\"\n"
+	"Text: 0, \"left\", 120, \"half\", \"\\s{Interval 3: hello}\"\n"
+	"Text: 0, \"left\", 135, \"half\", \"\\s{Interval 4: and}\"\n"
+	"Text: 0, \"left\", 150, \"half\", \"\\s{Interval 5: you}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
-NORMAL (L"This can be done nicer. The first step is to realize that the sentences starting with $$text\\$ $ are similar to each other, "
+NORMAL (L"This can be done more nicely. The first step is to realize that the sentences starting with $$text\\$ $ are similar to each other, "
 	"and the sentence starting with $appendInfoLine are also similar to each other. They only differ in the interval number, "
 	"and can therefore be made %identical by using a variable for the interval number, like this:")
-CODE (L"writeInfoLine (\"The texts in the first five intervals:\")")
+CODE (L"writeInfoLine: \"The texts in the first five intervals:\"")
 CODE (L"intervalNumber = 1")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE (L"appendInfoLine (\"Interval \", intervalNumber, \": \", text\\$ )")
+CODE (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE (L"appendInfoLine: \"Interval \", intervalNumber, \": \", text\\$ ")
 CODE (L"intervalNumber = 2")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE (L"appendInfoLine (\"Interval \", intervalNumber, \": \", text\\$ )")
+CODE (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE (L"appendInfoLine: \"Interval \", intervalNumber, \": \", text\\$ ")
 CODE (L"intervalNumber = 3")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE (L"appendInfoLine (\"Interval \", intervalNumber, \": \", text\\$ )")
+CODE (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE (L"appendInfoLine: \"Interval \", intervalNumber, \": \", text\\$ ")
 CODE (L"intervalNumber = 4")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE (L"appendInfoLine (\"Interval \", intervalNumber, \": \", text\\$ )")
+CODE (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE (L"appendInfoLine: \"Interval \", intervalNumber, \": \", text\\$ ")
 CODE (L"intervalNumber = 5")
-CODE (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE (L"appendInfoLine (\"Interval \", intervalNumber, \": \", text\\$ )")
+CODE (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE (L"appendInfoLine: \"Interval \", intervalNumber, \": \", text\\$ ")
 NORMAL (L"A new trick that you see here is that as a numeric argument (##Interval number#, "
 	"the second argument to ##Get label of interval...#), you can use not only a number "
 	"(as in all previous examples), but also a variable ($intervalNumber). "
 	"The rest of the script should be known stuff by now.")
 NORMAL (L"The script above is long, but it can be made much shorter with the use of a %%for-loop%:")
-CODE (L"writeInfoLine (\"The texts in the first five intervals:\")")
+CODE (L"writeInfoLine: \"The texts in the first five intervals:\"")
 CODE (L"for intervalNumber from 1 to 5")
-CODE1 (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE1 (L"appendInfoLine (\"Interval \", intervalNumber, \": \", text\\$ )")
+CODE1 (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE1 (L"appendInfoLine: \"Interval \", intervalNumber, \": \", text\\$ ")
 CODE (L"endfor")
 NORMAL (L"The two lines that were repeated five times in the previous version now show up with indentation "
 	"between a %for line and its corresponding %endfor. Those two lines (the $$text\\$ $ and the $appendInfoLine line) "
@@ -2013,54 +1990,54 @@ NORMAL (L"In the above example, using a loop does not do much more than save eig
 	"the version without the loop is no longer possible. By contrast, the version %with the loop is still possible, "
 	"because we have the command ##Get number of intervals...#, which gives us the number of intervals in the specified tier "
 	"(here, tier 1). So you do:")
-CODE (L"numberOfIntervals = do (\"Get number of intervals...\", 1)")
-CODE (L"writeInfoLine (\"The texts in all \", numberOfIntervals, \" intervals:\")")
+CODE (L"numberOfIntervals = Get number of intervals: 1")
+CODE (L"writeInfoLine: \"The texts in all \", numberOfIntervals, \" intervals:\"")
 CODE (L"for intervalNumber from 1 to numberOfIntervals")
-CODE1 (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE1 (L"appendInfoLine (\"Interval \", intervalNumber, \": \", text\\$ )")
+CODE1 (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE1 (L"appendInfoLine: \"Interval \", intervalNumber, \": \", text\\$ ")
 CODE (L"endfor")
 NORMAL (L"This may yield something like")
 SCRIPT (6, 3, L""
 	Manual_DRAW_WINDOW (3, "Praat Info", "File   Edit   Search   Convert   Font   Help")
 	"Courier\n"
-	"Text... 0 left 75 half \\s{The texts in all 7 intervals:}\n"
-	"Text... 0 left 90 half \\s{Interval 1: I}\n"
-	"Text... 0 left 105 half \\s{Interval 2: say}\n"
-	"Text... 0 left 120 half \\s{Interval 3: hello}\n"
-	"Text... 0 left 135 half \\s{Interval 4: and}\n"
-	"Text... 0 left 150 half \\s{Interval 5: you}\n"
-	"Text... 0 left 165 half \\s{Interval 6: say}\n"
-	"Text... 0 left 180 half \\s{Interval 7: goodbye}\n"
-	"Draw rectangle... 0 560 0 260\n"
+	"Text: 0, \"left\", 75, \"half\", \"\\s{The texts in all 7 intervals:}\"\n"
+	"Text: 0, \"left\", 90, \"half\", \"\\s{Interval 1: I}\"\n"
+	"Text: 0, \"left\", 105, \"half\", \"\\s{Interval 2: say}\"\n"
+	"Text: 0, \"left\", 120, \"half\", \"\\s{Interval 3: hello}\"\n"
+	"Text: 0, \"left\", 135, \"half\", \"\\s{Interval 4: and}\"\n"
+	"Text: 0, \"left\", 150, \"half\", \"\\s{Interval 5: you}\"\n"
+	"Text: 0, \"left\", 165, \"half\", \"\\s{Interval 6: say}\"\n"
+	"Text: 0, \"left\", 180, \"half\", \"\\s{Interval 7: goodbye}\"\n"
+	"Draw rectangle: 0, 560, 0, 260\n"
 )
 NORMAL (L"This is the first script in this tutorial that is useful in itself. On the basis of it "
 	"you can create all kinds of ways to list the texts in intervals. Here is how you would also list the durations "
 	"of those intervals:")
-CODE (L"numberOfIntervals = do (\"Get number of intervals...\", 1)")
-CODE (L"writeInfoLine (\"The durations and texts in all \", numberOfIntervals, \" intervals:\")")
+CODE (L"numberOfIntervals = Get number of intervals: 1")
+CODE (L"writeInfoLine: \"The durations and texts in all \", numberOfIntervals, \" intervals:\"")
 CODE (L"for intervalNumber from 1 to numberOfIntervals")
-CODE1 (L"startTime = do (\"Get start point...\", 1, intervalNumber)")
-CODE1 (L"endTime = do (\"Get end point...\", 1, intervalNumber)")
+CODE1 (L"startTime = Get start point: 1, intervalNumber")
+CODE1 (L"endTime = Get end point: 1, intervalNumber")
 CODE1 (L"duration = endTime - startTime")
-CODE1 (L"text\\$  = do\\$  (\"Get label of interval...\", 1, intervalNumber)")
-CODE1 (L"appendInfoLine (\"Interval \", intervalNumber, \" is \", duration, \" seconds long and contains the text: \", text\\$ )")
+CODE1 (L"text\\$  = Get label of interval: 1, intervalNumber")
+CODE1 (L"appendInfoLine: \"Interval \", intervalNumber, \" is \", duration, \" seconds long and contains the text: \", text\\$ ")
 CODE (L"endfor")
 MAN_END
 
-MAN_BEGIN (L"Scripting 3.7. Layout", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 3.7. Layout", L"ppgb", 20140111)
 INTRO (L"This chapter handles the way you use white space, comments, and continuation lines in a Praat script.")
 ENTRY (L"White space")
 NORMAL (L"Praat ignores all white space (spaces and tabs) that you put at the beginning of lines. The indentation "
 	"that you saw on the @@Scripting 3.6. \"For\" loops|previous page@ was therefore used solely for readability. "
 	"You are advised to use indenting, though, with three or four spaces for each level, "
 	"as in the following example, which loops over all tiers and intervals of a TextGrid:")
-CODE (L"writeInfoLine (\"The texts in all tiers and intervals:\")")
-CODE (L"numberOfTiers = do (\"Get number of tiers\")")
+CODE (L"writeInfoLine: \"The texts in all tiers and intervals:\"")
+CODE (L"numberOfTiers = Get number of tiers")
 CODE (L"for tierNumber from 1 to numberOfTiers")
-CODE1 (L"numberOfIntervals = do (\"Get number of intervals...\", tierNumber)")
+CODE1 (L"numberOfIntervals = Get number of intervals: tierNumber")
 CODE1 (L"for intervalNumber from 1 to numberOfIntervals")
-CODE2 (L"text\\$  = do (\"Get label of interval...\", tierNumber, intervalNumber)")
-CODE2 (L"appendInfoLine (\"Tier \", tierNumber, \", interval \", intervalNumber, \": \", text\\$ )")
+CODE2 (L"text\\$  = Get label of interval: tierNumber, intervalNumber")
+CODE2 (L"appendInfoLine: \"Tier \", tierNumber, \", interval \", intervalNumber, \": \", text\\$ ")
 CODE1 (L"endfor")
 CODE (L"endfor")
 NORMAL (L"Praat also ignores lines that are empty or consist solely of white space, "
@@ -2069,27 +2046,27 @@ ENTRY (L"Comments")
 NORMAL (L"Comments are lines that start with \"\\# \" or \";\". Praat ignores these lines when your script is running:")
 CODE (L"\\#  Create 1 second of a sine wave with a frequency of 100 Hertz,")
 CODE (L"\\#  sampled at 44100 Hz:")
-CODE (L"do (\"Create Sound from formula...\", \"sine\", 1, 0, 1, 44100, \"sin (2*pi*100*x)\")")
+CODE (L"Create Sound from formula: \"sine\", 1, 0, 1, 44100, \"sin (2*pi*100*x)\"")
 NORMAL (L"Because of its visibility, you are advised to use \"\\# \" for comments that structure your script, "
 	"and \";\" perhaps only for \"commenting out\" a statement, i.e. to temporarily put it before a line "
 	"that you don't want to execute.")
 ENTRY (L"Continuation lines")
 NORMAL (L"There is normally one line per statement, and one statement per line. But some statements are very long, "
 	"such as this one on a previous page:")
-CODE1 (L"appendInfoLine (\"Interval \", intervalNumber, \" is \", duration, \" seconds long and contains the text: \", text\\$ )")
+CODE1 (L"appendInfoLine: \"Interval \", intervalNumber, \" is \", duration, \" seconds long and contains the text: \", text\\$ ")
 NORMAL (L"By making the current window wider, you can see that I really put this whole statement on a single line. "
 	"I could have distributed it over two lines in the following way, by using three dots (an %ellipsis):")
-CODE1 (L"appendInfoLine (\"Interval \", intervalNumber, \" is \", duration, \" seconds long")
-CODE1 (L"... and contains the text: \", text\\$ )")
+CODE1 (L"appendInfoLine: \"Interval \", intervalNumber, \" is \", duration, \" seconds long")
+CODE1 (L"... and contains the text: \", text\\$ ")
 NORMAL (L"Here is another common type of example:")
-CODE (L"do (\"Create Sound from formula...\", \"windowedSine\", 1, 0, 1, 44100,")
-CODE (L"... \"0.5 * sin(2*pi*1000*x) * exp(-0.5*((x-0.5)/0.1)\\^ 2)\")")
+CODE (L"Create Sound from formula: \"windowedSine\", 1, 0, 1, 44100,")
+CODE (L"... \"0.5 * sin(2*pi*1000*x) * exp(-0.5*((x-0.5)/0.1)\\^ 2)\"")
 NORMAL (L"You will normally want to follow such an ellipsis with a space, unless you want to concatenate "
 	"the parts of a long word:")
-CODE (L"do (\"Select outer viewport...\", 0, 10, 0, 4)")
-CODE (L"do (\"Text top...\", \"yes\", \"It's a long way to Llanfairpwllgwyngyll")
+CODE (L"Select outer viewport: 0, 10, 0, 4")
+CODE (L"Text top: \"yes\", \"It's a long way to Llanfairpwllgwyngyll")
 CODE (L"...gogerychwyrndrobwllllantysiliogogogoch,")
-CODE (L"... unless you start from Tyddyn-y-felin.\")")
+CODE (L"... unless you start from Tyddyn-y-felin.\"")
 MAN_END
 
 MAN_BEGIN (L"Scripting 4. Object selection", L"ppgb", 20130501)
@@ -2100,7 +2077,7 @@ LIST_ITEM (L"@@Scripting 4.2. Removing objects")
 LIST_ITEM (L"@@Scripting 4.3. Querying objects")
 MAN_END
 
-MAN_BEGIN (L"Scripting 4.1. Selecting objects", L"ppgb", 20130501)
+MAN_BEGIN (L"Scripting 4.1. Selecting objects", L"ppgb", 20140223)
 NORMAL (L"To simulate the mouse-clicked and dragged selection in the list of objects, "
 	"you have the commands #selectObject, #plusObject and #minusObject.")
 NORMAL (L"Suppose you start Praat and use ##Create Sound as tone...# to create a Sound called %tone. "
@@ -2108,80 +2085,80 @@ NORMAL (L"Suppose you start Praat and use ##Create Sound as tone...# to create a
 	"Suppose you then do ##To Spectrum...# from the ##Analyse Spectrum# menu. "
 	"A second object, called \"2. Spectrum tone\" appears in the list and is selected. "
 	"To select and play the Sound, you can do either")
-CODE (L"#selectObject (1)")
-CODE (L"do (\"Play\")")
+CODE (L"#selectObject: 1")
+CODE (L"Play")
 NORMAL (L"or")
-CODE (L"#selectObject (\"Sound tone\")")
-CODE (L"do (\"Play\")")
+CODE (L"#selectObject: \"Sound tone\"")
+CODE (L"Play")
 NORMAL (L"So you can select an object either by its unique ID (identifier: the unique number by which it appears in the list) "
 	"or by name.")
 NORMAL (L"The function #selectObject works by first deselecting all objects, and then selecting the one you mention. "
 	"If you don't want to deselect the existing selection, you can use #plusObject or #minusObject. "
 	"When the Sound is selected, you can select the Spectrum as well by doing")
-CODE (L"#plusObject (2)")
+CODE (L"#plusObject: 2")
 NORMAL (L"or")
-CODE (L"#plusObject (\"Spectrum tone\")")
+CODE (L"#plusObject: \"Spectrum tone\"")
 NORMAL (L"If you then want to deselect the Sound, and keep the Spectrum selected, you can do")
-CODE (L"#minusObject (1)")
+CODE (L"#minusObject: 1")
 NORMAL (L"or")
-CODE (L"#minusObject (\"Sound tone\")")
+CODE (L"#minusObject: \"Sound tone\"")
 NORMAL (L"All these functions can take more than one argument. To select the Sound and the Spectrum together, you can do")
-CODE (L"#selectObject (1, 2)")
+CODE (L"#selectObject: 1, 2")
 NORMAL (L"or")
-CODE (L"#selectObject (\"Sound tone\", \"Spectrum tone\")")
+CODE (L"#selectObject: \"Sound tone\", \"Spectrum tone\"")
 NORMAL (L"or even")
-CODE (L"#selectObject (1, \"Spectrum tone\")")
+CODE (L"#selectObject: 1, \"Spectrum tone\"")
 ENTRY (L"How to refer to objects created in your script")
 NORMAL (L"In a script, you typically don't know whether the IDs of the objects are 1 and 2, or much higher numbers. "
-	"Fortunately, the #do function gives you the ID of the object that is created, "
+	"Fortunately, commands that create a new object give you the ID of the object that is created, "
 	"so that you can refer to the object later on. For instance, suppose you want to generate a sine wave, play it, "
 	"draw its spectrum, and then throw away both the Sound and the Spectrum. Here is how you do it:")
-CODE (L"sound = do (\"Create Sound as pure tone...\", \"sine377\",")
-CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01)   ; remember the ID of the Sound")
-CODE (L"do (\"Play\")   ; the Sound is selected, so it plays")
-CODE (L"do (\"To Spectrum...\", \"yes\")")
-CODE (L"do (\"Draw...\", 0, 5000, 20, 80, \"yes\")   ; the Spectrum is selected, so it is drawn")
+CODE (L"sound = Create Sound as pure tone: \"sine377\",")
+CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01   ; remember the ID of the Sound")
+CODE (L"Play   ; the Sound is selected, so it plays")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Draw: 0, 5000, 20, 80, \"yes\"   ; the Spectrum is selected, so it is drawn")
 CODE (L"\\#  Remove the created Spectrum and Sound:")
-CODE (L"#plusObject (sound)   ; the Spectrum was already selected")
-CODE (L"do (\"Remove\")")
+CODE (L"#plusObject: sound   ; the Spectrum was already selected")
+CODE (L"Remove")
 NORMAL (L"You could also select the objects by name:")
-CODE (L"do (\"Create Sound as pure tone...\", \"sine377\",")
-CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01)   ; no need to remember the ID of the Sound")
-CODE (L"do (\"Play\")   ; the Sound is selected, so it plays")
-CODE (L"do (\"To Spectrum...\", \"yes\")")
-CODE (L"do (\"Draw...\", 0, 5000, 20, 80, \"yes\")   ; the Spectrum is selected, so it is drawn")
+CODE (L"Create Sound as pure tone: \"sine377\",")
+CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01   ; no need to remember the ID of the Sound")
+CODE (L"Play   ; the Sound is selected, so it plays")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Draw: 0, 5000, 20, 80, \"yes\"   ; the Spectrum is selected, so it is drawn")
 CODE (L"\\#  Remove the created Spectrum and Sound:")
-CODE (L"#plusObject (\"Sound sine377\")   ; the Spectrum was already selected")
-CODE (L"do (\"Remove\")")
+CODE (L"#plusObject: \"Sound sine377\"   ; the Spectrum was already selected")
+CODE (L"Remove")
 NORMAL (L"This works even if there are multiple objects called \"Sound sine377\", "
-	"because if there are more objects with the same name, #selectedObject and #plusObject select the most recently created one, "
+	"because if there are more objects with the same name, #selectObject and #plusObject select the most recently created one, "
 	"i.e., the one nearest to the bottom of the list of objects.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 4.2. Removing objects", L"ppgb", 20130501)
+MAN_BEGIN (L"Scripting 4.2. Removing objects", L"ppgb", 20140111)
 NORMAL (L"In @@Scripting 4.1. Selecting objects|\\SS4.1@ we saw that objects could be removed by selecting them first and then calling the #Remove command. "
 	"A faster way is the #removeObject function, which can also remove unselected objects:")
-CODE (L"sound = do (\"Create Sound as pure tone...\", \"sine377\",")
-CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01)   ; remember the ID of the Sound")
-CODE (L"do (\"Play\")   ; the Sound is selected, so it plays")
-CODE (L"spectrum = do (\"To Spectrum...\", \"yes\")   ; remember the ID of the Spectrum")
-CODE (L"do (\"Draw...\", 0, 5000, 20, 80, \"yes\")   ; the Spectrum is selected, so it is drawn")
+CODE (L"sound = Create Sound as pure tone: \"sine377\",")
+CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01   ; remember the ID of the Sound")
+CODE (L"Play   ; the Sound is selected, so it plays")
+CODE (L"spectrum = To Spectrum: \"yes\"   ; remember the ID of the Spectrum")
+CODE (L"Draw: 0, 5000, 20, 80, \"yes\"   ; the Spectrum is selected, so it is drawn")
 CODE (L"\\#  Remove the created Spectrum and Sound:")
-CODE (L"#removeObject (sound, spectrum)   ; remove one selected and one unselected object")
+CODE (L"#removeObject: sound, spectrum   ; remove one selected and one unselected object")
 NORMAL (L"The #removeObject function keeps the objects selected that were selected before "
 	"(except of course the ones it throws away). "
 	"This allows you to easily throw away objects as soon as you no longer need them:")
-CODE (L"sound = do (\"Create Sound as pure tone...\", \"sine377\",")
-CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01)   ; remember the ID of the Sound")
-CODE (L"do (\"Play\")   ; the Sound is selected, so it plays")
-CODE (L"spectrum = do (\"To Spectrum...\", \"yes\")")
-CODE (L"#removeObject (sound)   ; we no longer need the Sound, so we remove it")
-CODE (L"do (\"Draw...\", 0, 5000, 20, 80, \"yes\")   ; the Spectrum is still selected, so it is drawn")
-CODE (L"#removeObject (spectrum)   ; remove the last object created by the script")
+CODE (L"sound = Create Sound as pure tone: \"sine377\",")
+CODE (L"... 1, 0, 1, 44100, 377, 0.2, 0.01, 0.01   ; remember the ID of the Sound")
+CODE (L"Play   ; the Sound is selected, so it plays")
+CODE (L"spectrum = To Spectrum: \"yes\"")
+CODE (L"#removeObject: sound   ; we no longer need the Sound, so we remove it")
+CODE (L"Draw: 0, 5000, 20, 80, \"yes\"   ; the Spectrum is still selected, so it is drawn")
+CODE (L"#removeObject: spectrum   ; remove the last object created by the script")
 ENTRY (L"Selecting and removing all objects from the list (don't)")
 NORMAL (L"A very strange command, which you should not normally use, is ##select all#:")
 CODE1 (L"##select all")
-CODE1 (L"do (\"Remove\")")
+CODE1 (L"Remove")
 NORMAL (L"This selects all objects in the list and then removes them. "
 	"Please try not to use this, because it will remove even the objects that your script did not create! "
 	"After all, you don't want the users of your script to lose the objects they created! "
@@ -2189,7 +2166,7 @@ NORMAL (L"This selects all objects in the list and then removes them. "
 	"even if the script is for your own use (because if it is a nice script, others will want to use it).")
 MAN_END
 
-MAN_BEGIN (L"Scripting 4.3. Querying objects", L"ppgb", 20130501)
+MAN_BEGIN (L"Scripting 4.3. Querying objects", L"ppgb", 20140111)
 NORMAL (L"You can get the name of a selected object into a string variable. "
 	"For instance, the following reads the name of the second selected Sound "
 	"(as counted from the top of the list of objects) into the variable %name\\$ :")
@@ -2208,9 +2185,9 @@ NORMAL (L"Negative numbers count from the bottom. Thus, to get the name of the b
 	"object, you say")
 CODE (L"name\\$  = ##selected\\$ # (\"Sound\", -1)")
 NORMAL (L"You would use ##selected\\$ # for drawing the object name in a picture:")
-CODE (L"do (\"Draw...\", 0, 0, 0, 0, \"yes\")")
+CODE (L"Draw: 0, 0, 0, 0, \"yes\"")
 CODE (L"name\\$  = ##selected\\$ # (\"Sound\")")
-CODE (L"do (\"Text top...\", \"no\", \"This is sound \" + name\\$ )")
+CODE (L"Text top: \"no\", \"This is sound \" + name\\$ ")
 NORMAL (L"For identifying previously selected objects, this method is not very suitable, since "
 	"there may be multiple objects with the same name:")
 CODE (L"\\#  The following two lines are OK:")
@@ -2218,13 +2195,13 @@ CODE (L"soundName\\$  = ##selected\\$ # (\"Sound\", -1)")
 CODE (L"pitchName\\$  = ##selected\\$ # (\"Pitch\")")
 CODE (L"\\#  But the following line is questionable, since it doesn't")
 CODE (L"\\#  necessarily select the previously selected Pitch again:")
-CODE (L"#selectObject (\"Pitch \" + pitchName\\$ )")
+CODE (L"#selectObject: \"Pitch \" + pitchName\\$ ")
 NORMAL (L"Instead of this error-prone approach, you should get the object's unique ID. "
 	"The correct version of our example becomes:")
 CODE (L"sound = #selected (\"Sound\", -1)")
 CODE (L"pitch = #selected (\"Pitch\")")
 CODE (L"\\#  Correct:")
-CODE (L"#selectObject (pitch)")
+CODE (L"#selectObject: pitch")
 NORMAL (L"To get the number of selected Sound objects into a variable, use")
 CODE (L"numberOfSelectedSounds = #numberOfSelected (\"Sound\")")
 NORMAL (L"To get the number of selected objects into a variable, use")
@@ -2236,16 +2213,16 @@ CODE (L"#for i to n")
 CODE (L"#endfor")
 CODE (L"\\#  Median pitches of all selected sounds:")
 CODE (L"#for i to n")
-	CODE1 (L"#selectObject (sound [i])")
-	CODE1 (L"do (\"To Pitch...\", 0.0, 75, 600)")
-	CODE1 (L"f0 = do (\"Get quantile...\", 0, 0, 0.50, \"Hertz\")")
-	CODE1 (L"appendInfoLine (f0)")
-	CODE1 (L"do (\"Remove\")")
+	CODE1 (L"#selectObject: sound [i]")
+	CODE1 (L"To Pitch: 0.0, 75, 600")
+	CODE1 (L"f0 = Get quantile: 0, 0, 0.50, \"Hertz\"")
+	CODE1 (L"appendInfoLine: f0")
+	CODE1 (L"Remove")
 CODE (L"#endfor")
 CODE (L"\\#  Restore selection:")
-CODE (L"#selectObject ()   ; deselect all objects")
+CODE (L"#selectObject ( )   ; deselect all objects")
 CODE (L"#for i from 1 to n")
-	CODE1 (L"#plusObject (sound [i])")
+	CODE1 (L"#plusObject: sound [i]")
 CODE (L"#endfor")
 MAN_END
 
@@ -2263,7 +2240,7 @@ LIST_ITEM (L"@@Scripting 5.7. Including other scripts@")
 LIST_ITEM (L"@@Scripting 5.8. Quitting@ (exit)")
 MAN_END
 
-MAN_BEGIN (L"Scripting 5.1. Variables", L"ppgb", 20130501)
+MAN_BEGIN (L"Scripting 5.1. Variables", L"ppgb", 20140111)
 INTRO (L"A %variable is a location in your computer's memory that has a name and where you can store something, "
 	"as explained in @@Scripting 3.2. Numeric variables|\\SS3.2@ and @@Scripting 3.4. String variables|\\SS3.4 at . "
 	"In a Praat script, you can store numbers and texts, i.e. you can use %%numeric variables% and %%string variables%.")
@@ -2272,7 +2249,7 @@ NORMAL (L"Numeric variables can hold integer numbers between -1,000,000,000,000,
 	"or real numbers between -10^^308^ and +10^^308^. The smallest numbers lie near -10^^-308^ and +10^^-308^.")
 NORMAL (L"You use numeric variables in your script like this:")
 CODE (L"#length = 10")
-CODE (L"do (\"Draw line...\", 0, #length, 1, 1)")
+CODE (L"Draw line: 0, #length, 1, 1")
 NORMAL (L"This draws a line in the Picture window from position (0, 10) to position (1, 1). "
 	"In the first line, you assign the value 10 to the variable called %length, "
 	"and in the second line you use the value of %length as the second argument to the command \"Draw line...\".")
@@ -2281,36 +2258,36 @@ NORMAL (L"Names of numeric variables must start with a lower-case letter, option
 ENTRY (L"String variables")
 NORMAL (L"You use string variables, which contain text, as follows:")
 CODE (L"##title\\$ # = \"Dutch nasal place assimilation\"")
-CODE (L"do (\"Text top...\", \"yes\", ##title\\$ #)")
+CODE (L"Text top: \"yes\", ##title\\$ #")
 NORMAL (L"This writes the text \"Dutch nasal place assimilation\"")
 NORMAL (L"As in the programming language Basic, the names of string variables end in a dollar sign.")
 ENTRY (L"Making numeric variables visible")
 NORMAL (L"You can write the content of numeric variables directly to the info window:")
 CODE (L"x = 2.0")
 CODE (L"root = sqrt (x)")
-CODE (L"#writeInfoLine (\"The square root of \", x, \" is \", root, \".\")")
+CODE (L"#writeInfoLine: \"The square root of \", x, \" is \", root, \".\"")
 NORMAL (L"This will write the following text to the Info window:")
 CODE (L"The square root of 2 is 1.4142135623730951.")
 NORMAL (L"You can fix the number of digits after the decimal point by use of the ##fixed\\$ # function:")
 CODE (L"x = 2.0")
 CODE (L"root = sqrt (x)")
-CODE (L"writeInfoLine (\"The square root of \", ##fixed\\$ # (x, 3), \" is approximately \", ##fixed\\$ # (root, 3), \".\")")
+CODE (L"writeInfoLine: \"The square root of \", ##fixed\\$ # (x, 3), \" is approximately \", ##fixed\\$ # (root, 3), \".\"")
 NORMAL (L"This will write the following text to the Info window:")
 CODE (L"The square root of 2.000 is approximately 1.414.")
 NORMAL (L"By using 0 decimal digits, you round to whole values:")
 CODE (L"root = sqrt (2)")
-CODE (L"writeInfoLine (\"The square root of 2 is very approximately \", ##fixed\\$ # (root, #0), \".\")")
+CODE (L"writeInfoLine: \"The square root of 2 is very approximately \", ##fixed\\$ # (root, #0), \".\"")
 NORMAL (L"This will write the following text to the Info window:")
 CODE (L"The square root of 2 is very approximately 1.")
 NORMAL (L"By using the ##percent\\$ # function, you give the result in a percent format:")
 CODE (L"jitter = 0.0156789")
-CODE (L"writeInfoLine (\"The jitter is \", ##percent\\$ # (jitter, 3), \".\")")
+CODE (L"writeInfoLine: \"The jitter is \", ##percent\\$ # (jitter, 3), \".\"")
 NORMAL (L"This will write the following text to the Info window:")
 CODE (L"The jitter is 1.568\\% .")
 NORMAL (L"The number 0, however, will always be written as 0, and for small numbers the number of "
 	"significant digits will never be less than 1:")
 CODE (L"jitter = 0.000000156789")
-CODE (L"writeInfoLine (\"The jitter is \", percent\\$  (jitter, 3), \".\")")
+CODE (L"writeInfoLine: \"The jitter is \", percent\\$  (jitter, 3), \".\"")
 NORMAL (L"This will write the following text to the Info window:")
 CODE (L"The jitter is 0.00002\\% .")
 ENTRY (L"Predefined variables")
@@ -2323,7 +2300,7 @@ NORMAL (L"Some predefined ##numeric variables# are $macintosh, $windows, and $un
 	"Another one is $praatVersion, which is e.g. " xstr(PRAAT_VERSION_NUM) " for the current version of Praat.")
 NORMAL (L"Some ##predefined string variables# are $$newline\\$ $,  $$tab\\$ $, and $$shellDirectory\\$ $. "
 	"The last one specifies the directory that was the default directory when Praat started up; "
-	"you can use it in scripts that run from the Unix or DOS command line. "
+	"you can use it in scripts that run from the Unix or Windows command line. "
 	"Likewise, there exist the predefined string variables $$homeDirectory\\$ $, "
 	"$$preferencesDirectory\\$ $, and $$temporaryDirectory\\$ $. These three refer to your home directory "
 	"(which is where you log in), the Praat @@preferences directory@, and a directory for saving temporary files; "
@@ -2339,41 +2316,41 @@ form Convert from WAV to AIFF
    text fileName hello.wav
 endform
 fileName$ = fileName$ - ".wav"
-Read from file... 'shellDirectory$'/'fileName$'.wav
-Save as AIFF file... 'shellDirectory$'/'fileName$'.aiff
+Read from file: shellDirectory$ + \"/\" + fileName$ + \".wav\"
+Save as AIFF file: shellDirectory$ + \"/\" + fileName$ + \".aiff\"
 
 if left$ (fileName$) <> "/"
-   fileName$ = 'shellDirectory$'/'fileName$'
+   fileName$ = shellDirectory$ + \"/\" + fileName$
 endif
 */
 
-MAN_BEGIN (L"Scripting 5.2. Expressions", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 5.2. Expressions", L"ppgb", 20140111)
 INTRO (L"In a Praat script, you can use numeric expressions as well as string expressions.")
 ENTRY (L"Numeric expressions")
 NORMAL (L"You can use a large variety of @@Formulas@ in your script:")
 CODE (L"length = 10")
 CODE (L"height = length/2")
 CODE (L"area = length * height")
-CODE (L"writeInfoLine (\"The area is \", area, \".\")")
+CODE (L"writeInfoLine: \"The area is \", area, \".\"")
 NORMAL (L"You can use numeric variables and formulas in numeric arguments to commands:")
-CODE (L"do (\"Draw line...\", 0, 0, length / 2, 2 * height)")
+CODE (L"Draw line: 0, 0, length / 2, 2 * height")
 NORMAL (L"You can use numeric expressions in assignments (as above), or after "
 	"#if, #elsif, #while, #until, and twice after #for.")
 ENTRY (L"String expressions")
 NORMAL (L"You can use a large variety of @@Formulas@ in your script:")
 CODE (L"addressee\\$  = \"Silke\"")
 CODE (L"greeting\\$  = \"Hi \" + addressee\\$  + \"!\"")
-CODE (L"writeInfoLine (\"The greeting is: \", greeting\\$ )")
+CODE (L"writeInfoLine: \"The greeting is: \", greeting\\$ ")
 NORMAL (L"You can use string variables and formulas in numeric arguments to commands:")
-CODE (L"do (\"Draw line...\", 0, length (greeting\\$ ), 0, 100)")
-CODE (L"do (\"Draw line...\", 0, if answer\\$  = \"yes\" then 20 else 30 fi, 0, 100)")
+CODE (L"Draw line: 0, length (greeting\\$ ), 0, 100")
+CODE (L"Draw line: 0, if answer\\$  = \"yes\" then 20 else 30 fi, 0, 100")
 NORMAL (L"You can use numeric and string variables and formulas in string arguments to commands:")
-CODE (L"do (\"Text top...\", \"yes\", \"Hi \" + addressee\\$  + \"!\")")
-CODE (L"do (\"Text top...\", \"yes\", left\\$  (fileName\\$ , index (fileName\\$ , \".\") - 1))")
+CODE (L"Text top: \"yes\", \"Hi \" + addressee\\$  + \"!\"")
+CODE (L"Text top: \"yes\", left\\$  (fileName\\$ , index (fileName\\$ , \".\") - 1)")
 NORMAL (L"The two examples from the end of @@Scripting 3.5. String queries|\\SS3.5@ could be abbreviated as the one-liners")
-CODE (L"writeInfoLine (do\\$  (\"Get power...\", 0.0, 0.0))")
+CODE (L"writeInfoLine: do\\$  (\"Get power...\", 0.0, 0.0)")
 NORMAL (L"and")
-CODE (L"writeInfoLine (do (\"Get power...\", 0.0, 0.0))")
+CODE (L"writeInfoLine: do (\"Get power...\", 0.0, 0.0)")
 ENTRY (L"Assignments from query commands")
 NORMAL (L"On how to get information from commands that normally write to the Info window, "
 	"see @@Scripting 6.3. Query commands at .")
@@ -2396,7 +2373,7 @@ CODE (L"#endif")
 NORMAL (L"A variant spelling for #elsif is #elif.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 5.4. Loops", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 5.4. Loops", L"ppgb", 20140111)
 ENTRY (L"\"For\" loops")
 TAG (L"#for %variable #from %expression__1_ #to %expression__2_")
 TAG (L"#for %variable #to %expression")
@@ -2405,9 +2382,9 @@ DEFINITION (L"the statements between the #for line and the matching #endfor will
 	"on each turn of the loop. If there is no #from, the loop variable starts at 1.")
 NORMAL (L"The following script plays nine sine waves, with frequencies of 200, 300, ..., 1000 Hz:")
 CODE (L"#for i #from 2 #to 10")
-CODE1 (L"do (\"Create Sound as pure tone...\", \"tone\", 1, 0, 0.3, 44100, i * 100, 0.2, 0.01, 0.01)")
-CODE1 (L"do (\"Play\")")
-CODE1 (L"do (\"Remove\")")
+CODE1 (L"Create Sound as pure tone: \"tone\", 1, 0, 0.3, 44100, i * 100, 0.2, 0.01, 0.01")
+CODE1 (L"Play")
+CODE1 (L"Remove")
 CODE (L"#endfor")
 NORMAL (L"The stop value of the #for loop is evaluated on each turn. If the second expression "
 	"is already less than the first expression to begin with, the statements between #for and #endfor "
@@ -2422,7 +2399,7 @@ CODE (L"#repeat")
 	CODE1 (L"eyes = randomInteger (1, 6) + randomInteger (1, 6)")
 	CODE1 (L"throws = throws + 1")
 CODE (L"#until eyes = 12")
-CODE (L"#writeInfoLine (\"It took me \", throws, \" trials to throw 12 with two dice.\")")
+CODE (L"#writeInfoLine: \"It took me \", throws, \" trials to throw 12 with two dice.\"")
 NORMAL (L"The statements in the #repeat/#until loop are executed at least once.")
 ENTRY (L"\"While\" loops")
 TAG (L"#while %expression")
@@ -2441,81 +2418,81 @@ NORMAL (L"If the expression evaluates to zero or %false to begin with, the state
 	"are not executed even once.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 5.5. Procedures", L"ppgb", 20130421)
+MAN_BEGIN (L"Scripting 5.5. Procedures", L"ppgb", 20140126)
 NORMAL (L"Sometimes in a Praat script, you will want to perform the same thing more than once. "
 	"In @@Scripting 5.4. Loops|\\SS5.4@ we saw how %loops can help there. "
 	"In this section we will see how %procedures (also called %subroutines) can help us.")
 NORMAL (L"Imagine that you want to play a musical note with a frequency of 440 Hz (an \"A\") "
 	"followed by a note that is one ocatve higher, i.e. has a frequency of 880 Hz (an \"a\"). "
 	"You could achieve this with the following script:")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 440, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 880, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 440, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 880, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 NORMAL (L"This script creates a sound with a sine wave with an amplitude of 0.4 and a frequency of 440 Hz, "
 	"then plays this sound, then changes the sound into a sine wave with a frequency of 880 Hz, "
 	"then plays this changed sound, and then removes the Sound object from the object list.")
 NORMAL (L"This script is perfect if all you want to do is to play those two notes and nothing more. "
 	"But now imagine that you want to play such an octave jump not only for a note of 440 Hz, "
 	"but also for a note of 400 Hz and for a note of 500 Hz. You could use the following script:")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 440, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 880, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 400, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 800, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 500, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, 1000, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 440, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 880, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 400, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 800, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 500, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, 1000, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 NORMAL (L"This script works but is no longer perfect. It contains many similar lines, and is difficult to read.")
 NORMAL (L"Here is where %procedures come in handy. With procedures, you can re-use similar pieces of code. "
 	"To make the three parts of the above script more similar, I'll rewrite it using two variables "
 	"(%frequency and %octaveHigher):")
 CODE (L"frequency = 440")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 CODE (L"octaveHigher = 2 * frequency")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 CODE (L"frequency = 400")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 CODE (L"octaveHigher = 2 * frequency")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 CODE (L"frequency = 500")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 CODE (L"octaveHigher = 2 * frequency")
-CODE (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 NORMAL (L"You can now see that seven lines of the script appear identically three times. "
 	"I'll put those seven lines into a %procedure that I name \"playOctave\":")
-CODE (L"#procedure playOctave ()")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+CODE (L"#procedure playOctave")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 	CODE1 (L"octaveHigher = 2 * frequency")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 CODE (L"#endproc")
 NORMAL (L"As you see, a %%procedure definition% in Praat consists of three parts:")
 LIST_ITEM (L"1. a line with the word #procedure, followed by the name of the procedure, followed by a pair of parentheses;")
@@ -2526,19 +2503,19 @@ NORMAL (L"You can put a procedure definition anywhere in your script; "
 NORMAL (L"The bodies of procedures are executed only if you %call the procedure explicitly, "
 	"which you can do anywhere in the rest of your script:")
 CODE (L"frequency = 440")
-CODE (L"\\@ playOctave ()")
+CODE (L"\\@ playOctave")
 CODE (L"frequency = 400")
-CODE (L"\\@ playOctave ()")
+CODE (L"\\@ playOctave")
 CODE (L"frequency = 500")
-CODE (L"\\@ playOctave ()")
-CODE (L"#procedure playOctave ()")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+CODE (L"\\@ playOctave")
+CODE (L"#procedure playOctave")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 	CODE1 (L"octaveHigher = 2 * frequency")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 CODE (L"#endproc")
 NORMAL (L"This script works as follows. First, the number 440 is assigned to the variable %frequency in line 1. "
 	"Then, execution of the script arrives at the ##\\@ # (\"call\") statement of line 2. "
@@ -2558,17 +2535,17 @@ NORMAL (L"The above example contains something awkward. The procedure %playOctav
 	"is set to an appropriate value, so before calling %playOctave you always have to insert a line like")
 CODE (L"frequency = 440")
 NORMAL (L"This can be improved upon. In the following version of the script, the procedure %playOctave requires an explicit %argument:")
-CODE (L"\\@ playOctave (440)")
-CODE (L"\\@ playOctave (400)")
-CODE (L"\\@ playOctave (500)")
-CODE (L"#procedure playOctave (frequency)")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+CODE (L"\\@ playOctave: 440")
+CODE (L"\\@ playOctave: 400")
+CODE (L"\\@ playOctave: 500")
+CODE (L"#procedure playOctave: frequency")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 	CODE1 (L"octaveHigher = 2 * frequency")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 CODE (L"#endproc")
 NORMAL (L"This works as follows. The first line of the procedure now not only contains the name (%playOctave), "
 	"but also a list of variables (here only one: %frequency). In the first line of the script, "
@@ -2579,18 +2556,18 @@ ENTRY (L"Encapsulation and local variables")
 NORMAL (L"Although the size of the script has now been reduced to 12 lines, which cannot be further improved upon, "
 	"there is still something wrong with it. Imagine the following script:")
 CODE (L"frequency = 300")
-CODE (L"\\@ playOctave (440)")
-CODE (L"\\@ playOctave (400)")
-CODE (L"\\@ playOctave (500)")
-CODE (L"#writeInfoLine (frequency)")
-CODE (L"#procedure playOctave (frequency)")
-	CODE1 (L"do (\"Create Sound from formula...\", \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+CODE (L"\\@ playOctave: 440")
+CODE (L"\\@ playOctave: 400")
+CODE (L"\\@ playOctave: 500")
+CODE (L"#writeInfoLine: frequency")
+CODE (L"#procedure playOctave: frequency")
+	CODE1 (L"Create Sound from formula: \"note\", 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 	CODE1 (L"octaveHigher = 2 * frequency")
-	CODE1 (L"do (\"Create Sound from formula...\", \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+	CODE1 (L"Create Sound from formula: \"note\", 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 CODE (L"#endproc")
 NORMAL (L"You might have thought that this script will write \"300\" to the Info window, "
 	"because that is what you expect if you look at the first five lines. "
@@ -2601,36 +2578,36 @@ NORMAL (L"What you would want is that variables that are used inside procedures,
 	"could somehow be made not to \"clash\" with variable names used outside the procedure. "
 	"A trick that works would be to include the procedure name into the names of these variables:")
 CODE (L"frequency = 300")
-CODE (L"\\@ playOctave (440)")
-CODE (L"\\@ playOctave (400)")
-CODE (L"\\@ playOctave (500)")
-CODE (L"#writeInfoLine (frequency)")
-CODE (L"#procedure playOctave (playOctave.frequency)")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, playOctave.frequency, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+CODE (L"\\@ playOctave: 440")
+CODE (L"\\@ playOctave: 400")
+CODE (L"\\@ playOctave: 500")
+CODE (L"#writeInfoLine: frequency")
+CODE (L"#procedure playOctave: playOctave.frequency")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, playOctave.frequency, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 	CODE1 (L"playOctave.octaveHigher = 2 * playOctave.frequency")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, playOctave.octaveHigher, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, playOctave.octaveHigher, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 CODE (L"#endproc")
 NORMAL (L"This works. The six tones will be played, and \"300\" will be written to the Info window. "
 	"But the formulation is a bit wordy, isn't it?")
 NORMAL (L"Fortunately, Praat allows an abbreviated version of these long names: "
 	"just leave \"playOctave\" off from the names of the variables, but keep the period (.):")
 CODE (L"frequency = 300")
-CODE (L"\\@ playOctave (440)")
-CODE (L"\\@ playOctave (400)")
-CODE (L"\\@ playOctave (500)")
-CODE (L"#writeInfoLine (frequency)")
-CODE (L"#procedure playOctave (.frequency)")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, .frequency, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+CODE (L"\\@ playOctave: 440")
+CODE (L"\\@ playOctave: 400")
+CODE (L"\\@ playOctave: 500")
+CODE (L"#writeInfoLine: frequency")
+CODE (L"#procedure playOctave: .frequency")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, .frequency, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 	CODE1 (L".octaveHigher = 2 * .frequency")
-	CODE1 (L"do (\"Create Sound as pure tone...\", \"note\", 1, 0, 0.3, 44100, .octaveHigher, 0.2, 0.01, 0.01)")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
+	CODE1 (L"Create Sound as pure tone: \"note\", 1, 0, 0.3, 44100, .octaveHigher, 0.2, 0.01, 0.01")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
 CODE (L"#endproc")
 NORMAL (L"This is the final version of the script. It works because Praat knows that "
 	"you are using the variable names %%.frequency% and %%.octaveHigher% in the context of the procedure %playOctave, "
@@ -2642,47 +2619,47 @@ NORMAL (L"It is advisable that you use such \"local\" variable names for all %pa
 	"and thereby perhaps inadvertently change the value of a variable that you expect to be constant across a procedure call.")
 ENTRY (L"A list of numeric and string arguments")
 NORMAL (L"You can use multiple arguments, separated by commas, and string arguments (with a dollar sign in the variable name):")
-CODE (L"\\@ listSpeaker (\"Bart\", 38)")
-CODE (L"\\@ listSpeaker (\"Katja\", 24)")
-CODE (L"#procedure listSpeaker (.name\\$ , .age)")
-	CODE1 (L"#appendInfoLine (\"Speaker \", .name\\$ , \" is \", .age, \" years old.\")")
+CODE (L"\\@ listSpeaker: \"Bart\", 38")
+CODE (L"\\@ listSpeaker: \"Katja\", 24")
+CODE (L"#procedure listSpeaker: .name\\$ , .age")
+	CODE1 (L"#appendInfoLine: \"Speaker \", .name\\$ , \" is \", .age, \" years old.\"")
 CODE (L"#endproc")
 NORMAL (L"or")
-CODE (L"\\@ conjugateVerb (\"be\", \"I am\", \"you are\", \"she is\")")
-CODE (L"#procedure conjugateVerb (.verb\\$ , .first\\$ , .second\\$ , .third\\$ )")
-	CODE1 (L"#writeInfoLine (\"Conjugation of 'to \", .verb\\$ , \"':\")")
-	CODE1 (L"#appendInfoLine (\"1sg \", .first\\$ )")
-	CODE1 (L"#appendInfoLine (\"2sg \", .second\\$ )")
-	CODE1 (L"#appendInfoLine (\"3sg \", .third\\$ )")
+CODE (L"\\@ conjugateVerb: \"be\", \"I am\", \"you are\", \"she is\"")
+CODE (L"#procedure conjugateVerb: .verb\\$ , .first\\$ , .second\\$ , .third\\$ ")
+	CODE1 (L"#writeInfoLine: \"Conjugation of 'to \", .verb\\$ , \"':\"")
+	CODE1 (L"#appendInfoLine: \"1sg \", .first\\$ ")
+	CODE1 (L"#appendInfoLine: \"2sg \", .second\\$ ")
+	CODE1 (L"#appendInfoLine: \"3sg \", .third\\$ ")
 CODE (L"#endproc")
 NORMAL (L"For the arguments you can use expressions:")
-CODE (L"\\@ playOctave (400 + 100)")
+CODE (L"\\@ playOctave: 400 + 100")
 NORMAL (L"As with all string literals, the double quotes in literal string arguments should be doubled:")
-CODE (L"#procedure texts (.top\\$ , .bottom\\$ )")
-	CODE1 (L"do (\"Text top...\", \"yes\", .top\\$ )")
-	CODE1 (L"do (\"Text bottom...\", \"yes\", .bottom\\$ )")
+CODE (L"#procedure texts: .top\\$ , .bottom\\$ ")
+	CODE1 (L"Text top: \"yes\", .top\\$ ")
+	CODE1 (L"Text bottom: \"yes\", .bottom\\$ ")
 CODE (L"#endproc")
-CODE (L"\\@ texts (\\\" \\\" \\\" hello\\\" \\\"  at the top\\\" , \\\" \\\" \\\" goodbye\\\" \\\"  at the bottom\\\" )")
+CODE (L"\\@ texts: \\\" \\\" \\\" hello\\\" \\\"  at the top\\\" , \\\" \\\" \\\" goodbye\\\" \\\"  at the bottom\\\" ")
 ENTRY (L"Functions")
 NORMAL (L"The Praat scripting language does not have the concept of a \"function\" like some other scripting languages do. "
 	"A function is a procedure that returns a number or a string. For instance, you can imagine the function $$squareNumber$ "
 	"which takes a number (e.g. 5) as an argument and returns the square of that number (e.g. 25). "
 	"Here is an example of how you can do that, using the global availability of local variables:")
-CODE (L"\\@ squareNumber (5)")
-CODE (L"#writeInfoLine (\"The square of 5 is \", squareNumber.result, \".\")")
-CODE (L"#procedure squareNumber (.number)")
+CODE (L"\\@ squareNumber: 5")
+CODE (L"#writeInfoLine: \"The square of 5 is \", squareNumber.result, \".\"")
+CODE (L"#procedure squareNumber: .number")
 	CODE1 (L".result = .number \\^  2")
 CODE (L"#endproc")
 NORMAL (L"Another way to emulate functions is to use a variable name as an argument:")
-CODE (L"\\@ squareNumber (5, \"square5\")")
-CODE (L"#writeInfoLine (\"The square of 5 is \", square5, \".\")")
-CODE (L"#procedure squareNumber (.number, .squareVariableName\\$ )")
+CODE (L"\\@ squareNumber: 5, \"square5\"")
+CODE (L"#writeInfoLine: \"The square of 5 is \", square5, \".\"")
+CODE (L"#procedure squareNumber: .number, .squareVariableName\\$ ")
 	CODE1 (L"'.squareVariableName\\$ ' = .number \\^  2")
 CODE (L"#endproc")
 NORMAL (L"However, this uses variable substitution, a trick better avoided.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 5.6. Arrays", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 5.6. Arrays", L"ppgb", 20140111)
 NORMAL (L"You can use arrays of numeric and string variables:")
 CODE (L"#for i #from 1 #to 5")
 	CODE1 (L"square [i] = i * i")
@@ -2691,72 +2668,69 @@ CODE (L"#endfor")
 NORMAL (L"After this, the variables $$square[1]$, $$square[2]$, $$square[3]$, $$square[4]$, $$square[5]$, "
 	"$$text\\$ [1]$, $$text\\$ [2]$, $$text\\$ [3]$, $$text\\$ [4]$, and $$text\\$ [5]$ contain "
 	"the values 1, 4, 9, 16, 25, \"h\", \"e\", \"l\", \"l\", and \"o\", respectively:")
-CODE (L"#writeInfoLine (\"Some squares:\")")
+CODE (L"#writeInfoLine: \"Some squares:\"")
 CODE (L"#for i #from 1 #to 5")
-	CODE1 (L"#appendInfoLine (\"The square of \", i, \" is \", square [i])")
+	CODE1 (L"#appendInfoLine: \"The square of \", i, \" is \", square [i]")
 CODE (L"#endfor")
 NORMAL (L"You can use any number of variables in a script, but you can also use Matrix or Sound objects for arrays.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 5.7. Including other scripts", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 5.7. Including other scripts", L"ppgb", 20140111)
 INTRO (L"You can include other scripts within your script:")
 CODE (L"a = 5")
 CODE (L"include square.praat")
-CODE (L"writeInfoLine (a)")
-NORMAL (L"The Info window will show the result 25 if the file square.praat is as follows:")
+CODE (L"writeInfoLine: a")
+NORMAL (L"The Info window will show the result 25 if the file square.praat contains the following:")
 CODE (L"a = a * a")
 NORMAL (L"The inclusion is done before any other part of the script is considered, so you can use the #form statement "
 	"and all variables in it. Usually, however, you will put some procedure definitions in the include file, that is "
 	"what it seems to be most useful for. Watch out, however, for using variable names in the include file: "
 	"the example above shows that there is no such thing as a separate name space.")
-NORMAL (L"Since including other scripts is the first thing Praat will do when considering a script, "
-	"you cannot use variable substitution. For instance, the following will not work:")
-CODE (L"scriptName\\$  = \"myscript.praat\"")
-CODE (L"\\# This will *not* work:")
-CODE (L"include 'scriptName\\$ '")
-CODE (L"\\# That did *not* work!!!")
+NORMAL (L"Note that you do not put quotes around the name of the include file. "
+        "This is because the name of the include file has to be given explicitly; you cannot put it into a variable, for instance.")
 NORMAL (L"You can use full or relative file names. For instance, the file square.praat is expected to be in the same "
 	"directory as the script that says %%include square.praat%. If you use the ScriptEditor, you will first have to save "
 	"the script that you are editing before any relative file names become meaningful (this is the same as with other "
 	"uses of relative file names in scripts).")
-NORMAL (L"You can %nest include files, i.e., included scripts can include other scripts. However, relative file names "
+NORMAL (L"You can \"nest\" include files, i.e., included scripts can include other scripts. However, relative file names "
 	"are always evaluated relative to the directory of the outermost script.")
 NORMAL (L"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 (L"Scripting 5.8. Quitting", L"ppgb", 20040414)
+MAN_BEGIN (L"Scripting 5.8. Quitting", L"ppgb", 20140124)
 NORMAL (L"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 (L"#exit")
+TAG (L"#exitScript ( )")
 DEFINITION (L"stops the execution of the script in the normal way, i.e. without any messages to the user. "
 	"Any settings window is removed from the screen.")
-TAG (L"#exit %%error-message%")
+TAG (L"#exitScript: %%error-message%")
 DEFINITION (L"stops the execution of the script while sending an error message to the user. "
+	"You can use the same argument list as with #writeInfoLine. "
 	"Any settings window will stay on the screen.")
 NORMAL (L"For an example, see @@Scripting 6.8. Messages to the user at .")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6. Communication outside the script", L"ppgb", 20130428)
-LIST_ITEM (L"@@Scripting 6.1. Arguments to the script@ (form/endform, execute)")
+MAN_BEGIN (L"Scripting 6. Communication outside the script", L"ppgb", 20140223)
+LIST_ITEM (L"@@Scripting 6.1. Arguments to the script@ (form/endform, runScript)")
 LIST_ITEM (L"@@Scripting 6.2. Writing to the Info window@ (writeInfoLine, appendInfoLine, appendInfo, tab\\$ )")
 LIST_ITEM (L"@@Scripting 6.3. Query commands@ (Get, Count)")
 LIST_ITEM (L"@@Scripting 6.4. Files@ (fileReadable, readFile, writeFile, deleteFile, createDirectory)")
 LIST_ITEM (L"@@Scripting 6.5. Calling system commands@ (system, environment\\$ , stopwatch)")
 LIST_ITEM (L"@@Scripting 6.6. Controlling the user@ (pause, beginPause/endPause, chooseReadFile\\$ )")
 LIST_ITEM (L"@@Scripting 6.7. Sending a message to another program@ (sendsocket)")
-LIST_ITEM (L"@@Scripting 6.8. Messages to the user@ (exit, assert, nowarn, nocheck)")
+LIST_ITEM (L"@@Scripting 6.8. Messages to the user@ (exitScript, assert, nowarn, nocheck)")
 LIST_ITEM (L"@@Scripting 6.9. Calling from the command line")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.1. Arguments to the script", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 6.1. Arguments to the script", L"ppgb", 20140212)
 NORMAL (L"You can cause a Praat script to prompt for arguments. The file $$playSine.praat$ may contain the following:")
 CODE (L"#form Play a sine wave")
 	CODE1 (L"#positive Sine_frequency_(Hz) 377")
 	CODE1 (L"#positive Gain_(0..1) 0.3 (= not too loud)")
 CODE (L"#endform")
-CODE (L"do (\"Create Sound as pure tone...\", \"sine\" + string\\$  (sine_frequency), 1, 0, 1, 44100, sine_frequency, gain, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"sine\" + string\\$  (sine_frequency), 1, 0, 1, 44100, sine_frequency, gain, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 NORMAL (L"When running this script, the interpreter puts a settings window (%form) on your screen, "
 	"entitled \"Play a sine wave\", "
 	"with two fields, titled \"Sine frequency (Hz)\" and \"Gain\", that have been provided "
@@ -2818,7 +2792,7 @@ CODE (L"#form Fill attributes")
 		CODE2 (L"#button Rough")
 		CODE2 (L"#button With holes")
 CODE (L"#endform")
-CODE (L"#writeInfoLine (\"You chose the colour \", colour\\$ , \" and the texture \", texture\\$ , \".\")")
+CODE (L"#writeInfoLine: \"You chose the colour \", colour\\$ , \" and the texture \", texture\\$ , \".\"")
 NORMAL (L"This shows two radio boxes. In the Colour box, the fifth button (Black) is the standard value here. "
 	"If you click on \"Navy blue\" and then #%OK, the variable %colour will have the value \"3\", "
 	"and the variable %%colour\\$ % will have the value \"Navy blue\". "
@@ -2843,29 +2817,28 @@ CODE (L"#form Fill attributes")
 		CODE2 (L"#option Rough")
 		CODE2 (L"#option With holes")
 CODE (L"#endform")
-CODE (L"#writeInfoLine (\"You chose the colour \", colour\\$ , \" and the texture \", texture\\$ , \".\")")
+CODE (L"#writeInfoLine: \"You chose the colour \", colour\\$ , \" and the texture \", texture\\$ , \".\"")
 NORMAL (L"You can combine two short fields into one by using %left and %right:")
 CODE (L"#form Get duration")
 	CODE1 (L"#natural left_Year_range 1940")
 	CODE1 (L"#natural right_Year_range 1945")
 CODE (L"#endform")
 CODE (L"duration = right_Year_range - left_Year_range")
-CODE (L"#writeInfoLine (\"The duration is \", duration, \" years.\")")
+CODE (L"#writeInfoLine: \"The duration is \", duration, \" years.\"")
 NORMAL (L"The interpreter will only show the single text \"Year range\", followed by two small text fields.")
 ENTRY (L"Calling a script from another script")
 NORMAL (L"Scripts can be nested: the file %%doremi.praat% may contain the following:")
-CODE (L"#execute playSine.praat 550 0.9")
-CODE (L"#execute playSine.praat 615 0.9")
-CODE (L"#execute playSine.praat 687 0.9")
-NORMAL (L"With the #execute command, Praat will not display a form window, but simply execute the script "
+CODE (L"#runScript: \"playSine.praat\", 550, 0.9")
+CODE (L"#runScript: \"playSine.praat\", 615, 0.9")
+CODE (L"#runScript: \"playSine.praat\", 687, 0.9")
+NORMAL (L"With #runScript, Praat will not display a form window, but simply execute the script "
 	"with the two arguments that you supply on the same line (e.g. 550 and 0.9).")
-NORMAL (L"Arguments (except for the last) that contain spaces must be put between double quotes, "
-	"and values for #choice must be passed as strings:")
-CODE (L"#execute \"fill attributes.praat\" \"Navy blue\" With holes")
-NORMAL (L"You can pass values for #boolean either as \"yes\" and \"no\" or 1 and 0.")
+NORMAL (L"Values for #choice must be passed as strings:")
+CODE (L"#runScript: \"fill attributes.praat\", \"Navy blue\", \"With holes\"")
+NORMAL (L"You can pass values for #boolean either as \"yes\" and \"no\" or as 1 and 0.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.2. Writing to the Info window", L"ppgb", 20130501)
+MAN_BEGIN (L"Scripting 6.2. Writing to the Info window", L"ppgb", 20140111)
 NORMAL (L"With the @Info button and several commands in the #Query menus, "
 	"you write to the @@Info window@ (if your program is run from the command line, "
 	"the text goes to the console window or to %stdout instead; see @@Scripting 6.9. Calling from the command line|\\SS6.9).")
@@ -2875,42 +2848,42 @@ NORMAL (L"The commands #writeInfo, #writeInfoLine, #appendInfo and #appendInfoLi
 	"that a following #appendInfo or #appendInfoLine will write on the next line.")
 NORMAL (L"These four functions take a variable number of numeric and/or string arguments, separated by commas. "
 	"The following script builds a table with statistics about a pitch contour:")
-CODE (L"#writeInfoLine (\"  Minimum   Maximum\")")
-CODE (L"do (\"Create Sound as pure tone...\", \"sine\", 1, 0, 0.1, 44100, 377, 0.2, 0.01, 0.01)")
-CODE (L"do (\"To Pitch...\", 0.01, 75, 600)")
-CODE (L"minimum = do (\"Get minimum...\", 0, 0, \"Hertz\", \"Parabolic\")")
-CODE (L"#appendInfo (minimum)")
-CODE (L"#appendInfo (tab\\$ )")
-CODE (L"maximum = do (\"Get maximum...\", 0, 0, \"Hertz\", \"Parabolic\")")
-CODE (L"#appendInfo (maximum)")
-CODE (L"#appendInfoLine ( )")
+CODE (L"#writeInfoLine: \"  Minimum   Maximum\"")
+CODE (L"Create Sound as pure tone: \"sine\", 1, 0, 0.1, 44100, 377, 0.2, 0.01, 0.01")
+CODE (L"To Pitch: 0.01, 75, 600")
+CODE (L"minimum = Get minimum: 0, 0, \"Hertz\", \"Parabolic\"")
+CODE (L"#appendInfo: minimum")
+CODE (L"#appendInfo: tab\\$ ")
+CODE (L"maximum = Get maximum: 0, 0, \"Hertz\", \"Parabolic\"")
+CODE (L"#appendInfo: maximum")
+CODE (L"#appendInfoLine: \"\"")
 NORMAL (L"You could combine the last four print statements into:")
-CODE (L"#appendInfoLine (minimum, tab\\$ , maximum)")
+CODE (L"#appendInfoLine: minimum, tab\\$ , maximum")
 NORMAL (L"which is the same as:")
-CODE (L"#appendInfo (minimum, tab\\$ , maximum, newline\\$ )")
+CODE (L"#appendInfo: minimum, tab\\$ , maximum, newline\\$ ")
 NORMAL (L"The little string ##tab\\$ # is a %tab character; it allows you to create "
 	"table files that can be read by some spreadsheet programs. The little string ##newline\\$ # is a %newline character; "
 	"it moves the following text to the next line.")
 NORMAL (L"To clear the Info window, you can do")
-CODE (L"#writeInfo ()")
+CODE (L"#writeInfo: \"\"")
 NORMAL (L"or")
 CODE (L"#clearinfo")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.3. Query commands", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 6.3. Query commands", L"ppgb", 20140107)
 NORMAL (L"If you click the \"Get mean...\" command for a Pitch object, "
 	"the Info window will contain a text like \"150 Hz\" as a result. In a script, you would rather "
 	"have this result in a variable instead of in the Info window. The solution is simple:")
-CODE (L"mean = do (\"Get mean...\", 0, 0, \"Hertz\", \"Parabolic\")")
+CODE (L"mean = Get mean: 0, 0, \"Hertz\", \"Parabolic\"")
 NORMAL (L"The numeric variable \"mean\" now contains the number 150. When assigning to a numeric variable, "
 	"the interpreter converts the part of the text before the first space into a number.")
 NORMAL (L"You can also assign to string variables:")
-CODE (L"mean\\$  = do\\$  (\"Get mean...\", 0, 0, \"Hertz\", \"Parabolic\")")
+CODE (L"mean\\$  = Get mean: 0, 0, \"Hertz\", \"Parabolic\"")
 NORMAL (L"The string variable \"mean\\$ \" now contains the entire string \"150 Hz\".")
 NORMAL (L"This works for every command that would otherwise write into the Info window.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.4. Files", L"ppgb", 20130501)
+MAN_BEGIN (L"Scripting 6.4. Files", L"ppgb", 20140111)
 INTRO (L"You can read from and write to text files from a Praat script.")
 ENTRY (L"Reading a file")
 NORMAL (L"You can check the availability of a file for reading with the function")
@@ -2943,22 +2916,22 @@ CODE (L"else")
 CODE (L"endif")
 ENTRY (L"Writing a file")
 NORMAL (L"You write into a new text file just as you write into the Info window:")
-CODE (L"writeFileLine (\"myFile.txt\", \"The present year is \", 2000 + 13, \".\")")
+CODE (L"writeFileLine: \"myFile.txt\", \"The present year is \", 2000 + 13, \".\"")
 NORMAL (L"and likewise you use %writeFile if you don't want a newline symbol at the end of the file. "
 	"If the file cannot be created, the script terminates with an error message.")
 NORMAL (L"To append text at the end of an existing file, you use")
-CODE (L"appendFileLine (\"myFile.txt\", \"Next year it will be \", 2000 + 14, \".\")")
+CODE (L"appendFileLine: \"myFile.txt\", \"Next year it will be \", 2000 + 14, \".\"")
 NORMAL (L"With %appendFileLine (and %appendFile, which does not add the newline), "
 	"we follow the rule that if the file does not yet exist, it is created first.")
 NORMAL (L"You can create a directory with")
-CODE (L"#createDirectory (%%directoryName\\$ %)")
+CODE (L"#createDirectory: %%directoryName\\$ %")
 NORMAL (L"where, as with file names, %%directoryName\\$ % can be relative to the directory of the script "
 	"(e.g. \"data\", or \"yesterday/data\", or \"../project2/yesterday/data\") "
 	"or an absolute path (e.g. \"C:/Documents and Settings/Paolo/project1/yesterday/data\" on Windows "
 	"or \"/Users/Paolo/project1/yesterday/data\" on the Mac). "
 	"If the directory already exists, this command does nothing.")
 NORMAL (L"You can delete an existing file with the function")
-CODE (L"#deleteFile (%%fileName\\$ %)")
+CODE (L"#deleteFile: %%fileName\\$ %")
 NORMAL (L"If the file does not exist, this command does nothing.")
 ENTRY (L"Example: writing a table of squares")
 NORMAL (L"Suppose that we want to create a file with the following text:")
@@ -2968,20 +2941,31 @@ CODE (L"The square of 3 is 9")
 CODE (L"...")
 CODE (L"The square of 100 is 10000")
 NORMAL (L"We can do this by appending 100 lines:")
-CODE (L"deleteFile (\"squares.txt\")")
+CODE (L"deleteFile: \"squares.txt\"")
 CODE (L"for i to 100")
-	CODE1 (L"appendFileLine (\"squares.txt\", \"The square of \", i, \" is \", i * i)")
+	CODE1 (L"appendFileLine: \"squares.txt\", \"The square of \", i, \" is \", i * i")
 CODE (L"endfor")
 NORMAL (L"Note that we delete the file before appending to it, "
 	"in order that we do not append to an already existing file.")
 NORMAL (L"You can append the contents of the Info window to a file with")
-CODE (L"appendFile (\"out.txt\", info\\$  ())")
+CODE (L"appendFile: \"out.txt\", info\\$  ( )")
 ENTRY (L"Directory listings")
 NORMAL (L"To get the names of the files if a certain type in a certain directory, "
 	"use @@Create Strings as file list... at .")
+ENTRY (L"Alternative syntax")
+NORMAL (L"If, on the basis of the syntax of commands and functions in earlier sections you expected that")
+CODE (L"text\\$  = readFile\\$  (\"myFile.txt\")")
+CODE (L"number = readFile (\"myFile.txt\")")
+NORMAL (L"could be written as")
+CODE (L"text\\$  = readFile\\$ : \"myFile.txt\"")
+CODE (L"number = readFile: \"myFile.txt\"")
+NORMAL (L"then you are right. The syntax with the colon is equivalent to the syntax with the two parentheses. Conversely, instead of")
+CODE (L"#deleteFile: %%fileName\\$ %")
+NORMAL (L"you can also write")
+CODE (L"#deleteFile (%%fileName\\$ %)")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.5. Calling system commands", L"ppgb", 20130821)
+MAN_BEGIN (L"Scripting 6.5. Calling system commands", L"ppgb", 20140111)
 INTRO (L"From a Praat script you can call system commands. "
 	"These are the same commands that you would normally type into a terminal window or into the Window command line prompt.")
 TAG (L"#system %command")
@@ -3014,46 +2998,49 @@ CODE (L"for i to 1000000")
 	CODE1 (L"a = 1.23456789e123")
 CODE (L"endfor")
 CODE (L"time = stopwatch")
-CODE (L"writeInfoLine (a, \" \", fixed\\$  (time, 3))")
+CODE (L"writeInfoLine: a, \" \", fixed\\$  (time, 3)")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.6. Controlling the user", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 6.6. Controlling the user", L"ppgb", 20140726)
 INTRO (L"You can temporarily halt a Praat script:")
-TAG (L"#pause %text")
+TAG (L"#pauseScript: %message")
 DEFINITION (L"suspends execution of the script, and allows the user to interrupt it. "
-	"A message window will appear with the %text and the buttons Stop and Continue:")
-CODE (L"#pause The next file will be beerbeet.TextGrid")
-NORMAL (L"In the pause window you can include the same kinds of arguments as in a @@Scripting 6.1. Arguments to the script|form at . "
+	"A message window will appear with the %message (you can use the same argument list as with #writeInfoLine) and the buttons Stop and Continue:")
+CODE (L"#pauseScript: \"The next file will be \", fileName\\$ ")
+NORMAL (L"The pauseScript function is useful if you want to send a simple message to the user, "
+	"and you only want to ask the user whether she wants to proceed or not. "
+	"More interesting interactions between your script and the user are possible with the %%pause window%. "
+	"In a pause window you can include the same kinds of arguments as in a @@Scripting 6.1. Arguments to the script|form at . "
 	"Here is an extensive example:")
-CODE (L"writeInfoLine (\"script\")")
+CODE (L"writeInfoLine: \"script\"")
 CODE (L"compression = 1")
 CODE (L"number_of_channels = 2")
 CODE (L"worth = 3")
 CODE (L"for i to 5")
-	CODE1 (L"#beginPause (\"Hi\")")
-		CODE2 (L"#comment (\"Type a lot of nonsense below.\")")
-		CODE2 (L"#natural (\"Number of people\", 10)")
-		CODE2 (L"#real (\"Worth\", worth+1)")
-		CODE2 (L"#positive (\"Sampling frequency (Hz)\", \"44100.0 (= CD quality)\")")
-		CODE2 (L"#word (\"hi\", \"hhh\")")
-		CODE2 (L"#sentence (\"lo\", \"two words\")")
-		CODE2 (L"#text (\"ko\", \"jkgkjhkj g gdfg dfg\")")
-		CODE2 (L"#boolean (\"You like it?\", 1)")
+	CODE1 (L"#beginPause: \"Hi\"")
+		CODE2 (L"#comment: \"Type a lot of nonsense below.\"")
+		CODE2 (L"#natural: \"Number of people\", 10")
+		CODE2 (L"#real: \"Worth\", worth + 1")
+		CODE2 (L"#positive: \"Sampling frequency (Hz)\", \"44100.0 (= CD quality)\"")
+		CODE2 (L"#word: \"hi\", \"hhh\"")
+		CODE2 (L"#sentence: \"lo\", \"two words\"")
+		CODE2 (L"#text: \"ko\", \"jkgkjhkj g gdfg dfg\"")
+		CODE2 (L"#boolean: \"You like it?\", 1")
 		CODE2 (L"if worth < 6")
-			CODE3 (L"#choice (\"Compression\", compression)")
-				CODE4 (L"#option (\"lossless (FLAC)\")")
-				CODE4 (L"#option (\"MP3\")")
-				CODE4 (L"#option (\"Ogg\")")
+			CODE3 (L"#choice: \"Compression\", compression")
+				CODE4 (L"#option: \"lossless (FLAC)\"")
+				CODE4 (L"#option: \"MP3\"")
+				CODE4 (L"#option: \"Ogg\"")
 		CODE2 (L"endif")
-		CODE2 (L"#optionMenu (\"Number of channels\", number_of_channels)")
-			CODE3 (L"#option (\"mono\")")
-			CODE3 (L"#option (\"stereo\")")
-			CODE3 (L"#option (\"quadro\")")
-		CODE2 (L"#comment (\"Then click Stop or one of the continuation buttons.\")")
-	CODE1 (L"clicked = #endPause (\"Continue\", \"Next\", \"Proceed\", 2)")
-	CODE1 (L"appendInfoLine (number_of_people, \" \", worth, \" \", sampling_frequency, \" \", clicked)")
-	CODE1 (L"appendInfoLine (\"Compression: \", compression, \" (\", compression\\$ )")
-	CODE1 (L"appendInfoLine (\"Number of channels: \", number_of_channels\\$ )")
+		CODE2 (L"#optionMenu: \"Number of channels\", number_of_channels")
+			CODE3 (L"#option: \"mono\"")
+			CODE3 (L"#option: \"stereo\"")
+			CODE3 (L"#option: \"quadro\"")
+		CODE2 (L"#comment: \"Then click Stop or one of the continuation buttons.\"")
+	CODE1 (L"clicked = #endPause: \"Continue\", \"Next\", \"Proceed\", 2")
+	CODE1 (L"appendInfoLine: number_of_people, \" \", worth, \" \", sampling_frequency, \" \", clicked")
+	CODE1 (L"appendInfoLine: \"Compression: \", compression, \" (\", compression\\$ ")
+	CODE1 (L"appendInfoLine: \"Number of channels: \", number_of_channels\\$ ")
 CODE (L"endfor")
 NORMAL (L"This example uses several tricks. A useful one is seen with %number_of_channels: "
 	"this is at the same time the value that is passed to #optionMenu (and therefore determines the setting of "
@@ -3064,61 +3051,61 @@ NORMAL (L"Your own pause windows are not likely to be as rich as the above examp
 	"For instance, the example has three continuation buttons (the second of these is the default button, "
 	"i.e. the button that you can \"click\" by pressing the Enter or Return key). "
 	"You will often use only one continuation button, for instance")
-CODE (L"#endPause (\"Continue\", 1)")
+CODE (L"#endPause: \"Continue\", 1")
 NORMAL (L"or")
-CODE (L"#endPause (\"Finish\", 1)")
+CODE (L"#endPause: \"Finish\", 1")
 NORMAL (L"or")
-CODE (L"#endPause (\"OK\", 1)")
+CODE (L"#endPause: \"OK\", 1")
 NORMAL (L"If your script shows multiple different pause windows, then it is in fact a %wizard, "
 	"and it becomes useful to have")
-CODE (L"#endPause (\"Next\", 1)")
+CODE (L"#endPause: \"Next\", 1")
 NORMAL (L"for most of them, and")
-CODE (L"#endPause (\"Finish\", 1)")
+CODE (L"#endPause: \"Finish\", 1")
 NORMAL (L"for the last one.")
 NORMAL (L"The possibility of multiple continuation buttons can save the user a mouse click. "
 	"The following script, for instance, requires two mouse clicks per sound:")
 CODE (L"for i to 20")
-	CODE1 (L"do (\"Read from file...\", \"sound\" + string\\$  (i) + \".wav\")")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
-	CODE1 (L"#beginPause (\"Rate the quality\")")
-		CODE2 (L"#comment (\"How good is the sound on a scale from 1 to 7?\")")
-		CODE2 (L"#choice (\"Quality\", 4)")
-			CODE3 (L"#option (\"1\")")
-			CODE3 (L"#option (\"2\")")
-			CODE3 (L"#option (\"3\")")
-			CODE3 (L"#option (\"4\")")
-			CODE3 (L"#option (\"5\")")
-			CODE3 (L"#option (\"6\")")
-			CODE3 (L"#option (\"7\")")
-	CODE1 (L"#endPause (if i = 20 then \"Finish\" else \"Next\" fi, 1)")
-	CODE1 (L"appendInfoLine (quality)")
+	CODE1 (L"Read from file: \"sound\" + string\\$  (i) + \".wav\"")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
+	CODE1 (L"#beginPause: \"Rate the quality\"")
+		CODE2 (L"#comment: \"How good is the sound on a scale from 1 to 7?\"")
+		CODE2 (L"#choice: \"Quality\", 4")
+			CODE3 (L"#option: \"1\"")
+			CODE3 (L"#option: \"2\"")
+			CODE3 (L"#option: \"3\"")
+			CODE3 (L"#option: \"4\"")
+			CODE3 (L"#option: \"5\"")
+			CODE3 (L"#option: \"6\"")
+			CODE3 (L"#option: \"7\"")
+	CODE1 (L"#endPause: if i = 20 then \"Finish\" else \"Next\" fi, 1")
+	CODE1 (L"appendInfoLine: quality")
 CODE (L"endfor")
 NORMAL (L"The following script works faster:")
 CODE (L"for i to 20")
-	CODE1 (L"do (\"Read from file...\", \"sound\" + string\\$  (i) + \".wav\")")
-	CODE1 (L"do (\"Play\")")
-	CODE1 (L"do (\"Remove\")")
-	CODE1 (L"#beginPause (\"Rate the quality\")")
-		CODE2 (L"#comment (\"How good is the sound on a scale from 1 to 7?\")")
-	CODE1 (L"quality = #endPause (\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", 0)")
-	CODE1 (L"appendInfoLine (quality)")
+	CODE1 (L"Read from file: \"sound\" + string\\$  (i) + \".wav\"")
+	CODE1 (L"Play")
+	CODE1 (L"Remove")
+	CODE1 (L"#beginPause: \"Rate the quality\"")
+		CODE2 (L"#comment: \"How good is the sound on a scale from 1 to 7?\"")
+	CODE1 (L"quality = #endPause: \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", 0")
+	CODE1 (L"appendInfoLine: quality")
 CODE (L"endfor")
 NORMAL (L"In this example, the 0 at the end of #endPause means that there is no default button.")
 ENTRY (L"File selection")
 NORMAL (L"If you want the user to choose a file name for reading (opening), do")
-CODE (L"fileName\\$  = ##chooseReadFile\\$ # (\"Open a table file\")")
+CODE (L"fileName\\$  = ##chooseReadFile\\$ #: \"Open a table file\"")
 CODE (L"if fileName\\$  <> \"\"")
-	CODE1 (L"table = do (\"Read Table from tab-separated file...\", fileName\\$ )")
+	CODE1 (L"table = Read Table from tab-separated file: fileName\\$ ")
 CODE (L"endif")
 NORMAL (L"A file selector window will appear, with (in this example) \"Open a table file\" as the title. "
 	"If the user clicks #OK, the variable $$fileName\\$ $ will contain the name of the file that the user selected; "
 	"if the user clicks #Cancel, the variable $$fileName\\$ $ will contain the empty string (\"\").")
 NORMAL (L"If you want the user to choose a file name for writing (saving), do")
-CODE (L"select mySound")
-CODE (L"fileName\\$  = ##chooseWriteFile\\$ # (\"Save as a WAV file\", \"mySound.wav\")")
+CODE (L"selectObject: mySound")
+CODE (L"fileName\\$  = ##chooseWriteFile\\$ #: \"Save as a WAV file\", \"mySound.wav\"")
 CODE (L"if fileName\\$  <> \"\"")
-	CODE1 (L"do (\"Save as WAV file...\", fileName\\$ )")
+	CODE1 (L"Save as WAV file: fileName\\$ ")
 CODE (L"endif")
 NORMAL (L"A file selector window will appear, with (in this example) \"Save as a WAV file\" as the title "
 	"and \"mySound.wav\" as the suggested file name (which the user can change). "
@@ -3127,11 +3114,11 @@ NORMAL (L"A file selector window will appear, with (in this example) \"Save as a
 	"the variable $$fileName\\$ $ will contain the file name that the user typed; "
 	"if the user clicks #Cancel at any point, the variable $$fileName\\$ $ will contain the empty string (\"\").")
 NORMAL (L"If you want the user to choose a directory (folder) name, do")
-CODE (L"directoryName\\$  = ##chooseDirectory\\$ # (\"Choose a directory to save all the new files in\")")
+CODE (L"directoryName\\$  = ##chooseDirectory\\$ #: \"Choose a directory to save all the new files in\"")
 CODE (L"if directoryName\\$  <> \"\"")
 	CODE1 (L"for i to numberOfSelectedSounds")
-		CODE2 (L"select sound [i]")
-		CODE2 (L"do (\"Save as WAV file...\", directoryName\\$  + \"/sound\" + string\\$  (i) + \".wav\")")
+		CODE2 (L"selectObject: sound [i]")
+		CODE2 (L"Save as WAV file: directoryName\\$  + \"/sound\" + string\\$  (i) + \".wav\"")
 	CODE1 (L"endfor")
 CODE (L"endif")
 NORMAL (L"A directory selector window will appear, with (in this example) \"Choose a directory to save all the new files in\" as the title. "
@@ -3141,13 +3128,13 @@ ENTRY (L"A non-pausing pause window without a Stop button")
 NORMAL (L"Especially if you use the pause window within the @@Demo window@, you may not want to give the user the capability of "
 	"ending the script by hitting #Stop or closing the pause window. In that case, you can add an extra argument to #endPause "
 	"that denotes the cancel button:")
-CODE (L"#beginPause (\"Learning settings\")")
-	CODE1 (L"#positive (\"Learning rate\", \"0.01\")")
-	CODE1 (L"#choice (\"Directions\", 3)")
-		CODE2 (L"#option (\"Forward\")")
-		CODE2 (L"#option (\"Backward\")")
-		CODE2 (L"#option (\"Bidirectional\")")
-CODE (L"clicked = #endPause (\"Cancel\", \"OK\", 2, 1)")
+CODE (L"#beginPause: \"Learning settings\"")
+	CODE1 (L"#positive: \"Learning rate\", \"0.01\"")
+	CODE1 (L"#choice: \"Directions\", 3")
+		CODE2 (L"#option: \"Forward\"")
+		CODE2 (L"#option: \"Backward\"")
+		CODE2 (L"#option: \"Bidirectional\"")
+CODE (L"clicked = #endPause: \"Cancel\", \"OK\", 2, 1")
 CODE (L"if clicked = 2")
 CODE1 (L"learningRate = learning_rate")
 CODE1 (L"includeForward = directions = 1 or directions = 3")
@@ -3159,7 +3146,7 @@ NORMAL (L"In this example, the default button is 2 (i.e. #OK), and the cancel bu
 	"and the variables $$learning_rate$, $directions and $$directions\\$ $ will not be changed (i.e. they might remain undefined).")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.7. Sending a message to another program", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 6.7. Sending a message to another program", L"ppgb", 20140107)
 NORMAL (L"To send messages to running programs that use the Praat shell, "
 	"use $sendpraat (see @@Scripting 8. Controlling Praat from another program@).")
 NORMAL (L"To send a message to another running program that listens to a socket, "
@@ -3168,7 +3155,7 @@ ENTRY (L"Example")
 NORMAL (L"Suppose we are in the Praat-shell program #Praat, which is a system for doing phonetics by computer. "
 	"From this program, we can send a message to the %%non%-Praat-shell program #MovieEdit, "
 	"which does know how to display a sound file:")
-CODE (L"do (\"Save as file...\", \"hallo.wav\")")
+CODE (L"Save as file: \"hallo.wav\"")
 CODE (L"sendsocket fonsg19.hum.uva.nl:6667 display hallo.wav")
 NORMAL (L"In this example, $$fonsg19.hum.uva.nl$ is the computer on which MovieEdit is running; "
 	"you can specify any valid Internet address instead, as long as that computer allows you to send messages to it. "
@@ -3176,40 +3163,40 @@ NORMAL (L"In this example, $$fonsg19.hum.uva.nl$ is the computer on which MovieE
 NORMAL (L"The number 6667 is the port number on which MovieEdit is listening. Other programs will use different port numbers.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.8. Messages to the user", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 6.8. Messages to the user", L"ppgb", 201401024)
 NORMAL (L"If the user makes a mistake (e.g. types conflicting settings into your form window), "
-	"you can use the #exit directive (@@Scripting 5.8. Quitting|\\SS5.8@) "
+	"you can use the #exitScript function (@@Scripting 5.8. Quitting|\\SS5.8@) "
 	"to stop the execution of the script with an error message:")
 CODE (L"form My analysis")
 	CODE1 (L"real Starting_time_(s) 0.0")
 	CODE1 (L"real Finishing_time_(s) 1.0")
 CODE (L"endform")
 CODE (L"if finishing_time <= starting_time")
-	CODE1 (L"#exit The finishing time should exceed 'starting_time' seconds.")
+	CODE1 (L"#exitScript: \"The finishing time should exceed \", starting_time, \" seconds.\"")
 CODE (L"endif")
 CODE (L"\\#  Proceed with the analysis...")
 NORMAL (L"For things that should not normally go wrong, you can use the #assert directive:")
-CODE (L"power = do (\"Get power\")")
+CODE (L"power = Get power")
 CODE (L"assert power > 0")
 NORMAL (L"This is the same as:")
 CODE (L"if (power > 0) = undefined")
-	CODE1 (L"exit Assertion failed in line xx (undefined): power > 0")
+	CODE1 (L"exitScript: \"Assertion failed in line \", lineNumber, \" (undefined): power > 0\"")
 CODE (L"elsif not (power > 0)")
-	CODE1 (L"exit Assertion failed in line xx (false): power > 0")
+	CODE1 (L"exitScript: \"Assertion failed in line \", lineNumber, \" (false): power > 0\"")
 CODE (L"endif")
 NORMAL (L"You can prevent Praat from issuing warning messages:")
-CODE (L"nowarn do (\"Save as WAV file...\", \"hello.wav\")")
+CODE (L"nowarn Save as WAV file: \"hello.wav\"")
 NORMAL (L"This prevents warning messages about clipped samples, for instance.")
 NORMAL (L"You can also prevent Praat from showing a progress window:")
-CODE (L"noprogress do (\"To Pitch...\", 0, 75, 500)")
+CODE (L"noprogress To Pitch: 0, 75, 500")
 NORMAL (L"This prevents the progress window from popping up during lengthy operations. "
 	"Use this only if you want to prevent the user from stopping the execution of the script.")
 NORMAL (L"Finally, you can make Praat ignore error messages:")
-CODE (L"nocheck do (\"Remove\")")
+CODE (L"nocheck Remove")
 NORMAL (L"This would cause the script to continue even if there is nothing to remove.")
 MAN_END
 
-MAN_BEGIN (L"Scripting 6.9. Calling from the command line", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 6.9. Calling from the command line", L"ppgb", 20140212)
 INTRO (L"Previous sections of this tutorial have shown you how to run a Praat script from the Script window. "
 	"However, you can also call a Praat script from the command line (text console) instead. "
 	"Information that would normally show up in the Info window, then goes to %stdout, "
@@ -3236,7 +3223,7 @@ NORMAL (L"Note that you use the program ##praatcon.exe# instead of ##praat.exe#.
 ENTRY (L"How to get arguments into the script")
 NORMAL (L"In the above example, the script ##doit.praat# requires two arguments. In the script ##doit.praat#, "
 	"you use #form and #endform to receive these arguments. See @@Scripting 6.1. Arguments to the script at . "
-	"As with the #execute command, Praat will not present a form window, but simply execute the script "
+	"As with #runScript, Praat will not present a form window, but simply run the script "
 	"with the arguments given on the command line. The example given in @@Scripting 6.1. Arguments to the script@ "
 	"will be called in the following way:")
 CODE (L"> /people/mietta/praat playSine.praat 550 0.9")
@@ -3254,27 +3241,29 @@ NORMAL (L"#Warning: if the purpose of your script is to get information about "
 	"to extract information from the analyses. This also applies if you want to use a TextGrid "
 	"to determine the times at which you want to query the analyses. "
 	"See @@Scripting examples at .")
-LIST_ITEM1 (L"@@Scripting 7.1. Scripting an editor from a shell script@ (editor/endeditor)")
-LIST_ITEM1 (L"@@Scripting 7.2. Scripting an editor from within@")
+LIST_ITEM (L"@@Scripting 7.1. Scripting an editor from a shell script@ (editor/endeditor)")
+LIST_ITEM (L"@@Scripting 7.2. Scripting an editor from within@")
 MAN_END
 
-MAN_BEGIN (L"Scripting 7.1. Scripting an editor from a shell script", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 7.1. Scripting an editor from a shell script", L"ppgb", 20140526)
 NORMAL (L"From a Praat shell script, you can switch to an editor and back again:")
 CODE (L"sound\\$  = \"hallo\"")
 CODE (L"start = 0.3")
 CODE (L"finish = 0.7")
-CODE (L"do (\"Read from file...\", sound\\$  + \".aifc\")")
-CODE (L"do (\"View & Edit\")")
-CODE (L"#editor Sound 'sound\\$ '")
-	CODE1 (L"do (\"Zoom...\", start, finish)")
+CODE (L"sound = Read from file: sound\\$  + \".aifc\"")
+CODE (L"View & Edit")
+CODE (L"#editor: sound")
+	CODE1 (L"Zoom: start, finish")
 CODE (L"#endeditor")
-CODE (L"do (\"Play\")")
+CODE (L"Play")
 NORMAL (L"This script reads a sound file from disk, pops up an editor for the resulting object, "
 	"makes this editor zoom in on the part between 0.3 and 0.7 seconds, "
 	"and returns to the Praat shell to play the entire sound.")
+NORMAL (L"After #editor you can either give the unique id of the object, as above, or its name:")
+CODE (L"#editor: \"Sound \" + sound\\$ ")
 MAN_END
 
-MAN_BEGIN (L"Scripting 7.2. Scripting an editor from within", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 7.2. Scripting an editor from within", L"ppgb", 20140113)
 NORMAL (L"This section will show how you can permanently extend the functionality of an editor.")
 NORMAL (L"As an example, consider the following problem: you want to see a graphic representation "
 	"of the spectrum of the sound around the cursor position in the SoundEditor. To achieve this, "
@@ -3284,12 +3273,12 @@ LIST_ITEM (L"2. View it in a SoundEditor by clicking @@View & Edit at .")
 LIST_ITEM (L"3. Choose ##New editor script# from the @@File menu@ in the SoundEditor. The resulting @ScriptEditor "
 	"will have a name like \"untitled script [Sound hallo]\".")
 LIST_ITEM (L"4. Type the following lines into the ScriptEditor:")
-CODE2 (L"cursor = do (\"Get cursor\")")
-CODE2 (L"do (\"Select...\", cursor - 0.02, cursor + 0.02)")
-CODE2 (L"do (\"Extract selected sound (windowed)...\", \"slice\", \"Kaiser2\", 2, \"no\")")
+CODE2 (L"cursor = Get cursor")
+CODE2 (L"Select: cursor - 0.02, cursor + 0.02")
+CODE2 (L"Extract selected sound (windowed): \"slice\", \"Kaiser2\", 2, \"no\"")
 CODE1 (L"#endeditor")
-CODE1 (L"do (\"To Spectrum...\", \"yes\")")
-CODE1 (L"do (\"View & Edit\")")
+CODE1 (L"To Spectrum: \"yes\"")
+CODE1 (L"View & Edit")
 NORMAL (L"If you choose #Run from the #Run menu in the ScriptEditor, a region of 40 milliseconds around the "
 	"current cursor position in the SoundEditor will become selected. This piece will be copied to the list of objects, "
 	"after applying a double Kaiser window (total length 80 ms). Thus, a Sound named \"slice\" will appear in the list. "
@@ -3314,47 +3303,45 @@ NORMAL (L"To improve the script, we open it again with ##Open editor script...#
 	"we can run it with #Run from the #Run menu again; alternatively, we could save it (with #Save from the #File menu) and choose our new "
 	"\"Show spectrum at cursor\" button (this button will always run the version on disk, never the one viewed in a ScriptEditor).")
 NORMAL (L"To zoom in on the first 5000 Hz, we add the following code at the end of our script:")
-CODE (L"#editor Spectrum slice")
-	CODE1 (L"do (\"Zoom...\", 0, 5000)")
+CODE (L"#editor: \"Spectrum slice\"")
+	CODE1 (L"Zoom: 0, 5000")
 NORMAL (L"To get rid of the \"Sound slice\", we can add:")
 CODE (L"#endeditor")
-CODE (L"#select Sound slice")
-CODE (L"Remove")
+CODE (L"#removeObject: \"Sound slice\"")
 NORMAL (L"Note that #$endeditor is needed to change from the environment of a SpectrumEditor to the environment of the object & picture windows.")
 NORMAL (L"If you now choose the \"Show spectrum at cursor\" button for several cursor positions, you will notice that all those editors have the same name. "
 	"To remedy the ambiguity of the line $$#editor Spectrum slice$, we give each slice a better name. For example, if the cursor was at "
 	"635 milliseconds, the slice could be named \"635ms\". We can achieve this by changing the extraction in the following way:")
 CODE (L"milliseconds = round (cursor*1000)")
-CODE (L"do (\"Extract selection sound (windowed)...\", string\\$  (milliseconds) + \"ms\", \"Kaiser2\", 2, \"no\")")
+CODE (L"Extract selection sound (windowed): string\\$  (milliseconds) + \"ms\", \"Kaiser2\", 2, \"no\"")
 NORMAL (L"The names of the Sound and Spectrum objects will now have more chance of being unique. Two lines will have to be edited trivially.")
 NORMAL (L"Finally, we will reset the selection to the original. At the top of the script, we add two lines to remember the positions of the selection markers:")
-CODE (L"start = do (\"Get start of selection\")")
-CODE (L"end = do (\"Get end of selection\")")
+CODE (L"start = Get start of selection")
+CODE (L"end = Get end of selection")
 NORMAL (L"At the bottom, we reset the selection:")
 CODE (L"#editor")
-CODE1 (L"do (\"Select...\", start, end)")
+CODE1 (L"Select: start, end")
 NORMAL (L"Note that the #$editor directive if not followed by the name of an editor, returns the script to the original environment.")
 NORMAL (L"The complete script is:")
-	CODE1 (L"start = do (\"Get start of selection\")")
-	CODE1 (L"end = do (\"Get end of selection\")")
-	CODE1 (L"cursor = do (\"Get cursor\")")
-	CODE1 (L"do (\"Select...\", cursor - 0.02, cursor + 0.02)")
+	CODE1 (L"start = Get start of selection")
+	CODE1 (L"end = Get end of selection")
+	CODE1 (L"cursor = Get cursor")
+	CODE1 (L"Select: cursor - 0.02, cursor + 0.02")
 	CODE1 (L"\\#  Create a name. E.g. \"670ms\" means at 670 milliseconds.")
 	CODE1 (L"milliseconds = round (cursor*1000)")
-	CODE1 (L"do (\"Extract windowed selection...\", string\\$  (milliseconds) + \"ms\", \"Kaiser2\", 2, \"no\")")
+	CODE1 (L"Extract windowed selection: string\\$  (milliseconds) + \"ms\", \"Kaiser2\", 2, \"no\"")
 CODE (L"#endeditor")
-CODE (L"do (\"To Spectrum...\", \"yes\")")
-CODE (L"do (\"View & Edit\")")
-CODE (L"#editor Spectrum 'milliseconds'ms")
-	CODE1 (L"do (\"Zoom...\", 0, 5000)")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"View & Edit")
+CODE (L"#editor: \"Spectrum \" + string\\$  (milliseconds) + \"ms\"")
+	CODE1 (L"Zoom: 0, 5000")
 CODE (L"#endeditor")
-CODE (L"#select Sound 'milliseconds'ms")
-CODE (L"do (\"Remove\")")
+CODE (L"#removeObject: \"Sound \" + string\\$  (milliseconds) + \"ms\"")
 CODE (L"#editor")
-	CODE1 (L"Select... start end")
+	CODE1 (L"Select: start, end")
 NORMAL (L"This script is useful as it stands. It is good enough for safe use. For instance, if the created Sound object has the same name "
-	"as an already existing Sound object, it will be the newly created Sound object that will be removed in the $Remove line, "
-	"because #$select always selects the most recently created object in case of ambiguity.")
+	"as an already existing Sound object, it will be the newly created Sound object that will be removed by #removeObject, "
+	"because in case of ambiguity #removeObject always removes the most recently created object of that name.")
 MAN_END
 
 MAN_BEGIN (L"sendpraat", L"ppgb", 20000927)
@@ -3367,7 +3354,7 @@ LIST_ITEM (L"@@Scripting 8.2. The sendpraat program")
 LIST_ITEM (L"@@Scripting 8.3. The sendpraat directive")
 MAN_END
 
-MAN_BEGIN (L"Scripting 8.1. The sendpraat subroutine", L"ppgb", 20091020)
+MAN_BEGIN (L"Scripting 8.1. The sendpraat subroutine", L"ppgb", 20140212)
 INTRO (L"A subroutine for sending messages to a %running Praat. "
 	"Also a Unix, MacOS, or DOS console program with the same purpose.")
 ENTRY (L"Syntax")
@@ -3410,19 +3397,19 @@ NORMAL (L"This will work because ##Play reverse# is an action command "
 	"On Unix, sendpraat will allow #Praat at most 1000 seconds to perform this.")
 ENTRY (L"Example 3: executing a large script file")
 NORMAL (L"Sometimes, it may be unpractical to send a large script directly to #sendpraat. "
-	"Fortunately, the receiving program knows the #execute directive:")
+	"Fortunately, the receiving program knows #runScript:")
 CODE (L"char message [100], *errorMessage;")
-CODE (L"strcpy (message, \"execute doAll.praat 20\");")
+CODE (L"strcpy (message, \"runScript: \\bs\"doAll.praat\\bs\", 20\");")
 CODE (L"errorMessage = #sendpraat (NULL, \"praat\", 0, message);")
-NORMAL (L"This causes the program #Praat to execute the script ##doAll.praat# with an argument of \"20\".")
+NORMAL (L"This causes the program #Praat to run the script ##doAll.praat# with an argument of \"20\".")
 ENTRY (L"How to download")
 NORMAL (L"You can download the source code of the sendpraat subroutine and program "
 	"via ##www.praat.org# or from ##http://www.fon.hum.uva.nl/praat/sendpraat.html#.")
 ENTRY (L"Instead")
 NORMAL (L"Instead of using sendpraat, you can also just take the following simple steps in your program:")
-LIST_ITEM (L"1. on Linux, write the script that you want to execute, and save it as ##~/.praat-dir/message#;")
+LIST_ITEM (L"1. on Linux, write the script that you want to run, and save it as ##~/.praat-dir/message#;")
 LIST_ITEM (L"2. get Praat's process id from ##~/.praat-dir/pid#;")
-LIST_ITEM (L"3. if Praat's process id is eg. 1178, send it a SIGUSR1 signal: $$kill -USR1 1178")
+LIST_ITEM (L"3. if Praat's process id is e.g. 1178, send it a SIGUSR1 signal: $$kill -USR1 1178")
 NORMAL (L"If the first line of your script is the comment \"\\#  999\", where 999 stands for the process id of your program, "
 	"Praat will send your program a SIGUSR2 signal back when it finishes handling the script.")
 ENTRY (L"See also")
@@ -3431,7 +3418,7 @@ NORMAL (L"To start a program from the command line instead and sending it a mess
 	"See @@Scripting 6.9. Calling from the command line at .")
 MAN_END
 
-MAN_BEGIN (L"Scripting 8.2. The sendpraat program", L"ppgb", 20050822)
+MAN_BEGIN (L"Scripting 8.2. The sendpraat program", L"ppgb", 20140212)
 INTRO (L"A Unix or DOS console program for sending messages to a %running Praat program.")
 ENTRY (L"Syntax")
 CODE (L"#sendpraat [%timeOut] %program %message...")
@@ -3447,31 +3434,43 @@ NORMAL (L"This works because ##Play reverse# is an action command "
 	"On Unix, sendpraat will allow #Praat at most 1000 seconds to perform this.")
 NORMAL (L"Each line is a separate argument. Lines that contain spaces should be put inside double quotes.")
 ENTRY (L"Example 3: drawing")
-CODE (L"sendpraat als \"for i from 1 to 5\" \"Draw circle... 0.5 0.5 i\" \"endfor\"")
+CODE (L"sendpraat als \"for i from 1 to 5\" \"Draw circle: 0.5, 0.5, i\" \"endfor\"")
 NORMAL (L"This causes the program #Als to draw five concentric circles into the Picture window.")
-ENTRY (L"Example 4: executing a large script")
-CODE (L"sendpraat praat \"execute doAll.praat 20\"")
+ENTRY (L"Example 4: running a large script")
+CODE (L"sendpraat praat \"runScript: \\bs\"doAll.praat\\bs\", 20\"")
 NORMAL (L"This causes the program #Praat to execute the script ##doAll.praat# with an argument of \"20\".")
 MAN_END
 
-MAN_BEGIN (L"Scripting 8.3. The sendpraat directive", L"ppgb", 20021218)
+MAN_BEGIN (L"Scripting 8.3. The sendpraat directive", L"ppgb", 20140112)
 INTRO (L"Besides being a subroutine (@@Scripting 8.1. The sendpraat subroutine@) "
 	"and a program (@@Scripting 8.2. The sendpraat program@), @sendpraat "
 	"can also be called from within a Praat script.")
 ENTRY (L"Example 1: killing a program")
 NORMAL (L"Suppose we are in the Praat-shell program #Als, which is a browser for dictionaries, "
 	"and we want to kill the Praat-shell program #Praat, which is a program for phonetics research:")
-CODE (L"sendpraat Praat Quit")
+CODE (L"beginSendpraat: \"Praat\"")
+CODE1 (L"Quit")
+CODE (L"endSendpraat")
 ENTRY (L"Example 2: playing a sound")
 NORMAL (L"Suppose we are in the Praat-shell program #Als, which is a browser for dictionaries, "
 	"and has no idea of what a %sound is. From this program, we can play a sound file "
 	"by sending a message to the Praat-shell program #Praat, which does know about sounds:")
-CODE (L"fileName\\$  = \"hallo.wav\"")
-CODE (L"sendpraat Praat")
-CODE (L"...'newline\\$ ' Read from file... 'fileName\\$ '")
-CODE (L"...'newline\\$ ' Play")
-CODE (L"...'newline\\$ ' Remove")
-NORMAL (L"The first $$newline\\$ $ is superfluous, but this format seems to read nicely.")
+CODE (L"fileName\\$  = chooseReadFile: \"Play a sound file\"")
+CODE (L"beginSendpraat: \"Praat\", \"fileName\\$ \"")
+CODE1 (L"Read from file: fileName\\$ ")
+CODE1 (L"Play")
+CODE1 (L"Remove")
+CODE (L"endSendpraat")
+NORMAL (L"After #beginSendpraat, you first mention the name of the receiving program (here \"Praat\"), "
+        "then the names of the variables you want the receiving program to know about.")
+NORMAL (L"To have the receiving program return information to you, specify the variables that are to be handed back:")
+CODE (L"fileName\\$  = chooseReadFile: \"Measure a sound file\"")
+CODE (L"beginSendpraat: \"Praat\", \"fileName\\$ \"")
+CODE1 (L"Read from file: fileName\\$ ")
+CODE1 (L"duration = Get total duration")
+CODE1 (L"Remove")
+CODE (L"endSendpraat: \"duration\"")
+CODE (L"writeInfoLine: \"That sound file lasts \", duration, \" seconds.\"")
 MAN_END
 
 /*
@@ -3482,7 +3481,7 @@ NORMAL (L"You can run scripts from the @ScriptEditor. If you will have to use th
 NORMAL (L"(You can also run scripts from the command line. See @@Scripting 6.9. Calling from the command line|\\SS6.9@)")
 */
 
-MAN_BEGIN (L"Scripting 9.1. Turning a script into a stand-alone program", L"ppgb", 20110831)
+MAN_BEGIN (L"Scripting 9.1. Turning a script into a stand-alone program", L"ppgb", 20140112)
 INTRO (L"You can turn your script into a double-clickable stand-alone program by including it into Praat's #main procedure. "
 	"If you want to try this, you should already know how to compile and link the Praat program on your computer.")
 NORMAL (L"These stand-alone programs do not show the Objects window and the Picture window; "
@@ -3490,7 +3489,7 @@ NORMAL (L"These stand-alone programs do not show the Objects window and the Pict
 CODE (L"\\# include \"praat.h\"")
 CODE (L"")
 CODE (L"const wchar_t myScript [ ] = L\"\"")
-	CODE1 (L"\"demo Text... 0.5 centre 0.5 half Hello world\\bsn\"")
+	CODE1 (L"\"demo Text: 0.5, \\bs\"centre\\bs\", 0.5, \\bs\"half\\bs\", \\bs\"Hello world\\bs\"\\bsn\"")
 	CODE1 (L"\"demoWaitForInput ( )\\bsn\"")
 CODE (L";")
 CODE (L"")
@@ -3511,28 +3510,28 @@ NORMAL (L"Your program can save its preferences in a directory of its choice, "
 	"e.g. in ##'preferencesDirectory\\$ '/../GuineaPigAnalyzer# if your program is called GuineaPigAnalyzer. "
 	"If you want to be less conspicuous and like to use the Praat preferences directory instead, "
 	"please use the ##apps# subdirectory, in this way:")
-CODE (L"createDirectory (preferencesDirectory\\$  + \"/apps\")")
-CODE (L"createDirectory (preferencesDirectory\\$  + \"/apps/GuineaPigAnalyzer\")")
+CODE (L"createDirectory: preferencesDirectory\\$  + \"/apps\"")
+CODE (L"createDirectory: preferencesDirectory\\$  + \"/apps/GuineaPigAnalyzer\"")
 MAN_END
 
-MAN_BEGIN (L"Scripting 9.2. Old functions", L"ppgb", 20130407)
+MAN_BEGIN (L"Scripting 9.2. Old functions", L"ppgb", 20140112)
 INTRO (L"The Praat scripting language improves and changes, but old scripts should continue to work correctly. "
 	"Here are some examples of what you can see in old scripts, and what they mean:")
 NORMAL (L"The meaning of")
 CODE (L"echo Hello, my name is 'name\\$ ' and I am 'age' years old.")
 NORMAL (L"is")
-CODE (L"writeInfoLine (\"Hello, my name is \", name\\$ , \" and I am \", age, \" years old.\")")
+CODE (L"writeInfoLine: \"Hello, my name is \", name\\$ , \" and I am \", age, \" years old.\"")
 NORMAL (L"The meaning of")
 CODE (L"Draw... 0 0 0 0 yes Curve")
 NORMAL (L"is")
-CODE (L"do (\"Draw...\", 0, 0, 0, 0, \"yes\", \"Curve\")")
+CODE (L"Draw: 0, 0, 0, 0, \"yes\", \"Curve\"")
 NORMAL (L"The meaning of")
 CODE (L"Read from file... 'fileName\\$ '")
 NORMAL (L"is")
-CODE (L"do (\"Read from file...\", fileName\\$ )")
+CODE (L"Read from file: fileName\\$ ")
 MAN_END
 
-MAN_BEGIN (L"ScriptEditor", L"ppgb", 20130421)
+MAN_BEGIN (L"ScriptEditor", L"ppgb", 20140107)
 INTRO (L"An aid to @@scripting at .")
 NORMAL (L"The ScriptEditor is a text editor that allows you to edit, save, and run "
 	"any @@Praat script at . You can type such a script from scratch, "
@@ -3551,18 +3550,18 @@ LIST_ITEM (L"2. Click #Play in the dynamic menu.")
 LIST_ITEM (L"3. Click the fixed #Remove button.")
 NORMAL (L"We then choose @@Paste history@ from the #Edit menu in the ScriptEditor (or type Command-H). "
 	"The text will now contain at least the following lines (delete any other lines):")
-CODE (L"do (\"Create Sound as pure tone...\", \"tone\", 1, 0, 0.4, 44100, 440, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"tone\", 1, 0, 0.4, 44100, 440, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 NORMAL (L"We can run this script again by choosing #Run from the #Run menu (or typing Command-R). "
 	"However, this always plays a sine with a frequency of 440 Hz, so we will add the variable \"Frequency\" "
 	"to the script, which then looks like:")
 CODE (L"#form Play a sine wave")
 	CODE1 (L"#positive Frequency")
 CODE (L"#endform")
-CODE (L"do (\"Create Sound as pure tone...\", \"tone\", 1, 0, 0.4, 44100, frequency, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"tone\", 1, 0, 0.4, 44100, frequency, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 NORMAL (L"When we choose #Run, the ScriptEditor will ask us to supply a value for the \"Frequency\" variable. "
 	"We can now play 1-second sine waves with any frequency.")
 NORMAL (L"It is advisable to supply a standard value for each argument in your script. "
@@ -3571,9 +3570,9 @@ CODE (L"#form Play a sine wave")
 	CODE1 (L"#positive Frequency 440")
 	CODE1 (L"#positive Duration 1.0")
 CODE (L"#endform")
-CODE (L"do (\"Create Sound as pure tone...\", \"tone\", 1, 0, duration, 44100, frequency, 0.2, 0.01, 0.01)")
-CODE (L"do (\"Play\")")
-CODE (L"do (\"Remove\")")
+CODE (L"Create Sound as pure tone: \"tone\", 1, 0, duration, 44100, frequency, 0.2, 0.01, 0.01")
+CODE (L"Play")
+CODE (L"Remove")
 NORMAL (L"When you run this script, the ScriptEditor will ask you to supply values for the two variables, "
 	"but the values \"440\" and \"1.0\" are already visible in the form window, "
 	"so that you will get a sensible result if you just click #OK.")
@@ -3594,7 +3593,7 @@ LIST_ITEM (L"1. Select a Sound object.")
 LIST_ITEM (L"2. Click ##To Pitch...# and set the arguments to your personal standard values.")
 LIST_ITEM (L"3. Click #OK. A new #Pitch object will appear.")
 NORMAL (L"We then paste the history into the ScriptEditor, after which this will contain at least a line like (delete all the other lines):")
-CODE (L"do (\"To Pitch...\", 0.01, 150, 900)")
+CODE (L"To Pitch: 0.01, 150, 900")
 NORMAL (L"You can run this script only after selecting one or more Sound objects.")
 NORMAL (L"If this script is useful to you, you may want to put a button for it in the dynamic menu:")
 LIST_ITEM (L"1. Save the script to a file, with #Save from the #File menu.")
@@ -3609,7 +3608,7 @@ LIST_ITEM (L"3. Click #OK and ensure that the button is clickable if you select
 	"to remove it from the dynamic menus, use the @ButtonEditor.")
 MAN_END
 
-MAN_BEGIN (L"undefined", L"ppgb", 20040414)
+MAN_BEGIN (L"undefined", L"ppgb", 20140112)
 INTRO (L"When you give a query command for a numeric value, Praat sometimes writes the numeric value ##--undefined--# "
 	"into the @@Info window@ (two hyphens at both sides of the word). This happens if the value you ask for is not defined, "
 	"as in the following examples:")
@@ -3621,8 +3620,8 @@ LIST_ITEM (L"\\bu You type into the @Calculator the following formula: 10\\^ 400
 ENTRY (L"Usage in a script")
 NORMAL (L"In a Praat script, this value is simply represented as \"undefined\". You use it to test whether "
 	"a query command returned a valid number:")
-CODE (L"select Pitch hallo")
-CODE (L"meanPitch = Get mean... 0.1 0.2 Hertz Parabolic")
+CODE (L"selectObject: \"Pitch hallo\"")
+CODE (L"meanPitch = Get mean: 0.1, 0.2, \"Hertz\", \"Parabolic\"")
 CODE (L"if meanPitch = undefined")
 	CODE1 (L"\\#  Take some exceptional action.")
 CODE (L"else")
@@ -3646,69 +3645,69 @@ LIST_ITEM (L"@@Script for TextGrid boundary drawing")
 LIST_ITEM (L"@@Script for analysing pitch with a TextGrid")
 MAN_END
 
-MAN_BEGIN (L"Script for listing time\\--F0 pairs", L"ppgb", 20130407)
+MAN_BEGIN (L"Script for listing time\\--F0 pairs", L"ppgb", 20140223)
 INTRO (L"\"I wish to have a list of time markers in one column and F0 in the other. "
 	"Those times that have no voiced data should be represented as \\\"l.\\\"r in the F0 column.\"")
-CODE (L"writeInfoLine (\"Time:    Pitch:\")")
-CODE (L"numberOfFrames = do (\"Get number of frames\")")
+CODE (L"writeInfoLine: \"Time:    Pitch:\"")
+CODE (L"numberOfFrames = Get number of frames")
 CODE (L"for iframe to numberOfFrames")
-	CODE1 (L"time = do (\"Get time from frame...\", iframe)")
-	CODE1 (L"pitch = do (\"Get value in frame...\", iframe, \"Hertz\")")
+	CODE1 (L"time = Get time from frame: iframe")
+	CODE1 (L"pitch = Get value in frame: iframe, \"Hertz\"")
 	CODE1 (L"if pitch = undefined")
-		CODE2 (L"appendInfoLine (fixed\\$  (time, 6))")
+		CODE2 (L"appendInfoLine: fixed\\$  (time, 6)")
 	CODE1 (L"else")
-		CODE2 (L"appendInfoLine (fixed\\$  (time, 6), \" \", fixed\\$  (pitch, 3))")
+		CODE2 (L"appendInfoLine: fixed\\$  (time, 6), \" \", fixed\\$  (pitch, 3)")
 	CODE1 (L"endif")
 CODE (L"endfor")
 NORMAL (L"If you want to see this in a text file, you can copy and paste from the Info window, or save the Info window, "
 	"or add a line to the script like")
-CODE (L"fappendinfo out.txt")
+CODE (L"appendFile: \"out.txt\", info\\$ ( )")
 MAN_END
 
-MAN_BEGIN (L"Script for listing time\\--F0\\--intensity", L"ppgb", 20130407)
+MAN_BEGIN (L"Script for listing time\\--F0\\--intensity", L"ppgb", 20140112)
 INTRO (L"\"I want a list of pitch and intensity values at the same times.\"")
 NORMAL (L"Since @@Sound: To Pitch...@ and @@Sound: To Intensity...@ do not give values at the same times, "
 	"you create separate pitch and intensity contours with high time resolution, then interpolate. "
 	"In the following example, you get pitch and intensity values at steps of 0.01 seconds "
 	"by interpolating curves that have a time resolution of 0.001 seconds.")
 CODE (L"sound = selected (\"Sound\")")
-CODE (L"tmin = do (\"Get start time\")")
-CODE (L"tmax = do (\"Get end time\")")
-CODE (L"do (\"To Pitch...\", 0.001, 75, 300)")
-CODE (L"do (\"Rename...\", \"pitch\")")
-CODE (L"select sound")
-CODE (L"do (\"To Intensity...\", 75, 0.001)")
-CODE (L"do (\"Rename...\", \"intensity\")")
-CODE (L"writeInfoLine (\"Here are the results:\")")
+CODE (L"tmin = Get start time")
+CODE (L"tmax = Get end time")
+CODE (L"To Pitch: 0.001, 75, 300")
+CODE (L"Rename: \"pitch\"")
+CODE (L"selectObject: sound")
+CODE (L"To Intensity: 75, 0.001")
+CODE (L"Rename: \"intensity\"")
+CODE (L"writeInfoLine: \"Here are the results:\"")
 CODE (L"for i to (tmax-tmin)/0.01")
 	CODE1 (L"time = tmin + i * 0.01")
-	CODE1 (L"select Pitch pitch")
-	CODE1 (L"pitch = do (\"Get value at time...\", time, \"Hertz\", \"Linear\")")
-	CODE1 (L"select Intensity intensity")
-	CODE1 (L"intensity = do (\"Get value at time...\", time, \"Cubic\")")
-	CODE1 (L"appendInfoLine (fixed\\$  (time, 2), \" \", fixed\\$  (pitch, 3), \" \", fixed\\$  (intensity, 3))")
+	CODE1 (L"selectObject: \"Pitch pitch\"")
+	CODE1 (L"pitch = Get value at time: time, \"Hertz\", \"Linear\"")
+	CODE1 (L"selectObject: \"Intensity intensity\"")
+	CODE1 (L"intensity = Get value at time: time, \"Cubic\"")
+	CODE1 (L"appendInfoLine: fixed\\$  (time, 2), \" \", fixed\\$  (pitch, 3), \" \", fixed\\$  (intensity, 3)")
 CODE (L"endfor")
 MAN_END
 
-MAN_BEGIN (L"Script for listing F0 statistics", L"ppgb", 20130407)
+MAN_BEGIN (L"Script for listing F0 statistics", L"ppgb", 20140112)
 INTRO (L"\"I need to split the wave into 50 msec sections, and then for each of those sections "
 	"get the F0 statistics. That is, for each 50 msec section of speech I want to get the average F0, "
 	"min, max, and standard deviation.\"")
 NORMAL (L"First you create the complete pitch contour, i.e., you select the Sound and choose "
 	"@@Sound: To Pitch...|To Pitch... at . You can then use the commands from the #Query menu in a loop:")
-CODE (L"startTime = do (\"Get start time\")")
-CODE (L"endTime = do (\"Get end time\")")
+CODE (L"startTime = Get start time")
+CODE (L"endTime = Get end time")
 CODE (L"numberOfTimeSteps = (endTime - startTime) / 0.05")
-CODE (L"writeInfoLine (\"   tmin     tmax    mean   fmin   fmax  stdev\")")
+CODE (L"writeInfoLine: \"   tmin     tmax    mean   fmin   fmax  stdev\"")
 CODE (L"for step to numberOfTimeSteps")
 	CODE1 (L"tmin = startTime + (step - 1) * 0.05")
 	CODE1 (L"tmax = tmin + 0.05")
-	CODE1 (L"mean = do (\"Get mean...\", tmin, tmax, \"Hertz\")")
-	CODE1 (L"minimum = do (\"Get minimum...\", tmin, tmax, \"Hertz\", \"Parabolic\")")
-	CODE1 (L"maximum = do (\"Get maximum...\", tmin, tmax, \"Hertz\", \"Parabolic\")")
-	CODE1 (L"stdev = do (\"Get standard deviation...\", tmin, tmax, \"Hertz\")")
-	CODE1 (L"appendInfoLine (fixed\\$  (tmin, 6), \" \", fixed\\$  (tmax, 6), \" \", fixed\\$  (mean, 2),")
-	CODE1 (L"... \" \", fixed\\$  (minimum, 2), \" \", fixed\\$  (maximum, 2), \" \", fixed\\$  (stdev, 2))")
+	CODE1 (L"mean = Get mean: tmin, tmax, \"Hertz\"")
+	CODE1 (L"minimum = Get minimum: tmin, tmax, \"Hertz\", \"Parabolic\"")
+	CODE1 (L"maximum = Get maximum: tmin, tmax, \"Hertz\", \"Parabolic\"")
+	CODE1 (L"stdev = Get standard deviation: tmin, tmax, \"Hertz\"")
+	CODE1 (L"appendInfoLine: fixed\\$  (tmin, 6), \" \", fixed\\$  (tmax, 6), \" \", fixed\\$  (mean, 2),")
+	CODE1 (L"... \" \", fixed\\$  (minimum, 2), \" \", fixed\\$  (maximum, 2), \" \", fixed\\$  (stdev, 2)")
 CODE (L"endfor")
 ENTRY (L"Notes")
 NORMAL (L"One should not cut the sound up into pieces of 50 ms and then do ##To Pitch...# on each of them, "
@@ -3718,34 +3717,35 @@ NORMAL (L"One should not cut the sound up into pieces of 50 ms and then do ##To
 	"In that way, the information loss of windowing only affects the two 20 ms edges of the whole sound.")
 NORMAL (L"The example writes lines to the #Info window. If you want to write to a file instead, "
 	"you start with something like")
-	CODE1 (L"filedelete ~/results/out.txt")
+	CODE1 (L"deleteFile: \"~/results/out.txt\"")
 NORMAL (L"and add lines in the following way:")
-	CODE1 (L"fileappend ~/results/out.txt 'tmin:6' 'tmax:6' 'mean:2'")
-	CODE1 (L"... 'minimum:2' 'maximum:2' 'stdev:2''newline\\$ '")
+	CODE1 (L"appendFileLine: \"~/results/out.txt \", fixed\\$  (tmin, 6), \" \", fixed\\$  (tmax, 6), \" \",")
+	CODE1 (L"... fixed\\$  (mean, 2), \" \", fixed\\$  (minimum, 2), \" \", fixed\\$  (maximum, 2), \" \",")
+	CODE1 (L"... fixed\\$  (stdev, 2)")
 MAN_END
 
-MAN_BEGIN (L"Script for creating a frequency sweep", L"ppgb", 20130407)
+MAN_BEGIN (L"Script for creating a frequency sweep", L"ppgb", 20140107)
 INTRO (L"\"I have to find a formula for a sinewave that sweeps from 1 kHz to 12 kHz in "
 	"60 seconds while ramping the amplitude from 1 to 12 volts in the same amount of time.\"")
 NORMAL (L"The absolute amplitude in volts cannot be handled, of course, but linear crescendo is easy:")
-CODE (L"do (\"Create Sound from formula...\", \"sweep\", 1, 0, 60, 44100,")
-CODE (L"... \"0.05 * (1 + 11 * x/60) * sin (2*pi * (1000 + 11000/2 * x/60) * x)\")")
+CODE (L"Create Sound from formula: \"sweep\", 1, 0, 60, 44100,")
+CODE (L"... \"0.05 * (1 + 11 * x/60) * sin (2*pi * (1000 + 11000/2 * x/60) * x)\"")
 NORMAL (L"Note the \"/2\" in this formula. Here is the derivation of the formula:")
 FORMULA (L"%frequency (%t) = 1000 + 11000 %t / 60")
 FORMULA (L"%phase (%t) = \\in %frequency (%t) %dt = 1000 %t + 11000 (%t^2/2) / 60")
 FORMULA (L"%signal (%t) = sin (%phase (%t))")
 MAN_END
 
-MAN_BEGIN (L"Script for onset detection", L"ppgb", 20130407)
+MAN_BEGIN (L"Script for onset detection", L"ppgb", 20140112)
 INTRO (L"\"Can anybody provide me with a script that detects the onset of sound (i.e. the end of silence).\"")
 NORMAL (L"You can create an Intensity contour and look for the first frame that is above some predefined threshold:")
-CODE (L"do (\"To Intensity...\", 100, 0)")
-CODE (L"n = do (\"Get number of frames\")")
+CODE (L"To Intensity: 100, 0")
+CODE (L"n = Get number of frames")
 CODE (L"for i to n")
-	CODE1 (L"intensity = do (\"Get value in frame...\", i)")
+	CODE1 (L"intensity = Get value in frame: i")
 	CODE1 (L"if intensity > 40")
-		CODE2 (L"time = do (\"Get time from frame...\", i)")
-		CODE2 (L"writeInfoLine (\"Onset of sound at: \", fixed\\$  (time, 3), \" seconds.\")")
+		CODE2 (L"time = Get time from frame: i")
+		CODE2 (L"writeInfoLine: \"Onset of sound at: \", fixed\\$  (time, 3), \" seconds.\"")
 		CODE2 (L"exit")
 	CODE1 (L"endif")
 CODE (L"endfor")
@@ -3753,60 +3753,59 @@ NORMAL (L"Since the intensity is computed with rather long windows, the result m
 	"before the actual start of sound.")
 MAN_END
 
-MAN_BEGIN (L"Script for TextGrid boundary drawing", L"ppgb", 20130407)
+MAN_BEGIN (L"Script for TextGrid boundary drawing", L"ppgb", 20140107)
 INTRO (L"\"I want only the dotted lines of the textgrid marked on top of another analysis (e.g. pitch, intensity or so) "
 	"without the labels being shown below it.\"")
-CODE (L"n = do (\"Get number of intervals...\", 1)")
+CODE (L"n = Get number of intervals: 1")
 CODE (L"for i to n-1")
-CODE1 (L"t = do (\"Get end point...\", 1, i)")
-CODE1 (L"do (\"One mark bottom...\", t, \"no\", \"no\", \"yes\")")
+    CODE1 (L"t = Get end point: 1, i")
+    CODE1 (L"One mark bottom: t, \"no\", \"no\", \"yes\"")
 CODE (L"endfor")
 MAN_END
 
-MAN_BEGIN (L"Script for analysing pitch with a TextGrid", L"ppgb", 20130421)
+MAN_BEGIN (L"Script for analysing pitch with a TextGrid", L"ppgb", 20140112)
 INTRO (L"\"I want the mean pitch of every interval that has a non-empty label on tier 5.\"")
 CODE (L"if numberOfSelected (\"Sound\") <> 1 or numberOfSelected (\"TextGrid\") <> 1")
 	CODE1 (L"exit Please select a Sound and a TextGrid first.")
 CODE (L"endif")
 CODE (L"sound = selected (\"Sound\")")
 CODE (L"textgrid = selected (\"TextGrid\")")
-CODE (L"writeInfoLine (\"Result:\")")
-CODE (L"select sound")
-CODE (L"do (\"To Pitch...\", 0.0, 75, 600)")
+CODE (L"writeInfoLine: \"Result:\"")
+CODE (L"selectObject: sound")
+CODE (L"To Pitch: 0.0, 75, 600")
 CODE (L"pitch = selected (\"Pitch\")")
-CODE (L"select textgrid")
-CODE (L"n = do (\"Get number of intervals...\", 5)")
+CODE (L"selectObject: textgrid")
+CODE (L"n = Get number of intervals: 5")
 CODE (L"for i to n")
-	CODE1 (L"tekst\\$  = do (\"Get label of interval...\", 5, i)")
+	CODE1 (L"tekst\\$  = Get label of interval: 5, i")
 	CODE1 (L"if tekst\\$  <> \"\"")
-		CODE2 (L"t1 = do (\"Get starting point...\", 5, i)")
-		CODE2 (L"t2 = do (\"Get end point...\", 5, i)")
-		CODE2 (L"select pitch")
-		CODE2 (L"f0 = do (\"Get mean...\", t1, t2, \"Hertz\")")
-		CODE2 (L"appendInfoLine (fixed\\$  (t1, 3), \" \", fixed\\$  (t2, 3), \" \", round (f0), \" \", tekst\\$ )")
-		CODE2 (L"select textgrid")
+		CODE2 (L"t1 = Get starting point: 5, i")
+		CODE2 (L"t2 = Get end point: 5, i")
+		CODE2 (L"selectObject: pitch")
+		CODE2 (L"f0 = Get mean: t1, t2, \"Hertz\"")
+		CODE2 (L"appendInfoLine: fixed\\$  (t1, 3), \" \", fixed\\$  (t2, 3), \" \", round (f0), \" \", tekst\\$ ")
+		CODE2 (L"selectObject: textgrid")
 	CODE1 (L"endif")
 CODE (L"endfor")
-CODE (L"select sound")
-CODE (L"plus textgrid")
+CODE (L"selectObject: sound, textgrid")
 MAN_END
 
-MAN_BEGIN (L"Demo window", L"ppgb", 20101204)
+MAN_BEGIN (L"Demo window", L"ppgb", 20140621)
 INTRO (L"The Demo window is a window in which you can draw and ask for user input. "
 	"You can use it for demonstrations, presentations, simulations, adaptive listening experiments, "
 	"and stand-alone programs (see @@Scripting 9.1. Turning a script into a stand-alone program@).")
 NORMAL (L"The Demo window is Praat's least visible window: you can create it only through a script. "
 	"Try the following script after selecting a Sound object:")
-CODE (L"demo Draw... 0 3 -1 1 yes curve")
+CODE (L"demo Draw: 0, 3, -1, 1, \"yes\", \"curve\"")
 NORMAL (L"You see the Demo window turning up on the screen, with the Sound painted into it. "
 	"It works because the ##Draw...# command is available in the Objects window when you select a Sound. Then try:")
-CODE (L"demo Draw line... 0 -1 3 1")
+CODE (L"demo Draw line: 0, -1, 3, 1")
 NORMAL (L"You see a line drawn from (0 seconds, -1 Pa) to (3 seconds, +1 Pascal) in the waveform. "
 	"It works because the ##Draw line...# command is available in the Picture window. Then try:")
 CODE (L"demo Erase all")
 CODE (L"demo Red")
-CODE (L"demo Axes... 0 100 0 100")
-CODE (L"demo Text... 50 centre 50 half Hello")
+CODE (L"demo Axes: 0, 100, 0, 100")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"Hello\"")
 NORMAL (L"You see a text appearing in red, in the centre of the window. "
 	"This works because you are using commands from the Picture window, including the @@Axes...@ command, "
 	"which sets the world coordinates to something else than before (before, the world coordinates were determined by the Sound).")
@@ -3819,32 +3818,32 @@ CODE (L"demo Erase all")
 CODE (L"demo Black")
 CODE (L"demo Times")
 CODE (L"demo 24")
-CODE (L"demo Select outer viewport... 0 100 50 100")
-CODE (L"demo Draw... 0 0 0 0 yes curve")
-CODE (L"demo Select inner viewport... 0 100 0 100")
-CODE (L"demo Axes... 0 10 0 10")
-CODE (L"demo Text... 0 left 0 bottom Left bottom corner")
-CODE (L"demo Text... 10 right 0 bottom Right bottom corner")
+CODE (L"demo Select outer viewport: 0, 100, 50, 100")
+CODE (L"demo Draw: 0, 0, 0, 0, \"yes\", \"curve\"")
+CODE (L"demo Select inner viewport: 0, 100, 0, 100")
+CODE (L"demo Axes: 0, 10, 0, 10")
+CODE (L"demo Text: 0, \"left\", 0, \"bottom\", \"Left\", \"bottom corner\"")
+CODE (L"demo Text: 10, \"right\", 0, \"bottom\", \"Right bottom corner\"")
 NORMAL (L"As the title page of a presentation, you could do:")
 CODE (L"demo Erase all")
-CODE (L"demo Select inner viewport... 0 100 0 100")
-CODE (L"demo Axes... 0 100 0 100")
-CODE (L"demo Paint rectangle... purple 0 100 0 100")
+CODE (L"demo Select inner viewport: 0, 100, 0, 100")
+CODE (L"demo Axes: 0, 100, 0, 100")
+CODE (L"demo Paint rectangle: \"purple\", 0, 100, 0, 100")
 CODE (L"demo Pink")
-CODE (L"demo Text... 50 centre 50 half This is my title")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"This is my title\"")
 ENTRY (L"Getting user input")
 NORMAL (L"For almost all applications, you will want the user (or the participant in an experiment) to be able to click on things in the Demo window, "
 	"or to control the Demo window by pressing keys. Here is a presentation with two screens:")
 CODE (L"demo Erase all")
-CODE (L"demo Select inner viewport... 0 100 0 100")
-CODE (L"demo Axes... 0 100 0 100")
-CODE (L"demo Paint rectangle... purple 0 100 0 100")
+CODE (L"demo Select inner viewport: 0, 100, 0, 100")
+CODE (L"demo Axes: 0, 100, 0, 100")
+CODE (L"demo Paint rectangle: \"purple\", 0, 100, 0, 100")
 CODE (L"demo Pink")
-CODE (L"demo Text... 50 centre 50 half This is the first page")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"This is the first page\"")
 CODE (L"#demoWaitForInput ( )")
 CODE (L"demo Erase all")
-CODE (L"demo Paint rectangle... purple 0 100 0 100")
-CODE (L"demo Text... 50 centre 50 half This is the second page")
+CODE (L"demo Paint rectangle: \"purple\", 0, 100, 0, 100")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"This is the second page\"")
 NORMAL (L"In this example, you go from the first to the second screen either by clicking with the mouse or by pressing any key. "
 	"You will usually want to be more selective in your choice of user actions to respond to. "
 	"The function #demoWaitForInput always returns 1, so that you can use it nicely in a loop, in which you can react selectively:")
@@ -3853,11 +3852,11 @@ CODE (L"demo Erase all")
 CODE (L"demo Black")
 CODE (L"demo Times")
 CODE (L"demo 24")
-CODE (L"demo Select inner viewport... 0 100 0 100")
-CODE (L"demo Axes... 0 100 0 100")
-CODE (L"demo Paint rectangle... purple 0 100 0 100")
+CODE (L"demo Select inner viewport: 0, 100, 0, 100")
+CODE (L"demo Axes: 0, 100, 0, 100")
+CODE (L"demo Paint rectangle: \"purple\", 0, 100, 0, 100")
 CODE (L"demo Pink")
-CODE (L"demo Text... 50 centre 50 half This is the first page")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"This is the first page\"")
 CODE (L"while demoWaitForInput ( )")
 	CODE1 (L"if #demoClicked ( )")
 		CODE2 (L"goto SECOND_SCREEN")
@@ -3869,8 +3868,8 @@ CODE (L"while demoWaitForInput ( )")
 CODE (L"endwhile")
 CODE (L"label SECOND_SCREEN")
 CODE (L"demo Erase all")
-CODE (L"demo Paint rectangle... purple 0 100 0 100")
-CODE (L"demo Text... 50 centre 50 half This is the second page")
+CODE (L"demo Paint rectangle: \"purple\", 0, 100, 0, 100")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"This is the second page\"")
 CODE (L"while demoWaitForInput ( )")
 	CODE1 (L"if demoClicked ( )")
 		CODE2 (L"goto END")
@@ -3889,18 +3888,18 @@ CODE (L"demo Erase all")
 CODE (L"demo Black")
 CODE (L"demo Times")
 CODE (L"demo 24")
-CODE (L"demo Select inner viewport... 0 100 0 100")
-CODE (L"demo Axes... 0 100 0 100")
-CODE (L"demo Paint rectangle... purple 0 100 0 100")
+CODE (L"demo Select inner viewport: 0, 100, 0, 100")
+CODE (L"demo Axes: 0, 100, 0, 100")
+CODE (L"demo Paint rectangle: \"purple\", 0, 100, 0, 100")
 CODE (L"demo Pink")
-CODE (L"demo Text... 50 centre 50 half This is the first page")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"This is the first page\"")
 CODE (L"while demoWaitForInput ( )")
 	CODE1 (L"goto SECOND_SCREEN #demoInput (\"\\bu\\-> \")")
 CODE (L"endwhile")
 CODE (L"label SECOND_SCREEN")
 CODE (L"demo Erase all")
-CODE (L"demo Paint rectangle... purple 0 100 0 100")
-CODE (L"demo Text... 50 centre 50 half This is the second page")
+CODE (L"demo Paint rectangle: \"purple\", 0, 100, 0, 100")
+CODE (L"demo Text: 50, \"centre\", 50, \"half\", \"This is the second page\"")
 CODE (L"while demoWaitForInput ( )")
 	CODE1 (L"goto END demoInput (\"\\bu\\-> \")")
 	CODE1 (L"goto FIRST_SCREEN demoInput (\"\\<-\")")
@@ -3914,24 +3913,45 @@ NORMAL (L"You can use the functions #demoX and #demoY to see where the user has
 	"upper half of the screne in the above example, you do")
 CODE (L"while demoWaitForInput ( )")
 	CODE1 (L"if demoClicked ( )")
-		CODE2 (L"Select outer viewport... 0 100 50 100")
-		CODE2 (L"Axes... 0 3 -1 1")
+		CODE2 (L"Select outer viewport: 0, 100, 50, 100")
+		CODE2 (L"Axes: 0, 3, -1, 1")
 		CODE2 (L"if #demoX ( ) >= 0 and demoX ( ) < 3 and #demoY ( ) >= -1 and demoY ( ) < 1")
 NORMAL (L"The last line can be shortened to:")
 		CODE2 (L"if #demoClickedIn (0, 3, -1, 1)")
 NORMAL (L"Another example of when you want to know the click location is when you test for a click on a button "
 	"that you drew on the screen:")
-CODE (L"demo Paint rounded rectangle... pink 30 70 16 24")
-CODE (L"demo Text... 50 centre 20 half Analyse")
+CODE (L"demo Paint rounded rectangle: \"pink\", 30, 70, 16, 24")
+CODE (L"demo Text: 50, \"centre\", 20, \"half\", \"Analyse\"")
 CODE (L"while demoWaitForInput ( )")
 	CODE1 (L"goto ANALYSE demoClickedIn (30, 70, 16, 24)")
 ENTRY (L"Full-screen viewing")
-NORMAL (L"When you click in the \"zoom box\" (the green button in the title bar of the Demo window on the Mac), "
+NORMAL (L"When you click in the top right corner of the Demo window (64-bit Mac) "
+	"or in the \"zoom box\" (the green button in the title bar of the Demo window on 32-bit Mac), "
 	"the Demo window will zoom out very strongly: it will fill up the whole screen. The menu bar becomes invisible, "
 	"although you can still make it temporarily visible and accessible by moving the mouse to the upper edge of the screen. "
 	"The Dock also becomes invisible, although you can make it temporarily visible and accessible by moving the mouse to the edge "
 	"of the screen (the left, bottom, or right edge, depending on where your Dock normally is). "
 	"When you click the zoom box again, the Demo window is restored to its original size. See also Tips and Tricks below.")
+ENTRY (L"Asynchronous play")
+NORMAL (L"If you select a Sound and execute the command")
+CODE (L"Play")
+NORMAL (L"Praat will play the whole sound before proceeding to the next line of your script. "
+	"You will often instead want Praat to continue running your script while the sound is playing. "
+	"To accomplish that, use the \"asynchronous\" directive:")
+CODE (L"Create Sound as pure tone: \"tone\", 1, 0, 0.2, 44100, 440, 0.2, 0.01, 0.01")
+CODE (L"#asynchronous Play")
+CODE (L"Remove")
+NORMAL (L"The sound will continue to play, even after the Sound object has been removed.")
+NORMAL (L"Please note that a following Play command will interrupt the playing of the first:")
+CODE (L"while demoWaitForInput ( )")
+	CODE1 (L"if demoClicked ( )")
+		CODE2 (L"Create Sound as pure tone: \"tone\", 1, 0, 3.0, 44100,")
+		CODE2 (L"... randomGauss (440, 100), 0.2, 0.01, 0.01")
+		CODE2 (L"asynchronous Play")
+		CODE2 (L"Remove")
+	CODE1 (L"endif")
+CODE (L"endwhile")
+NORMAL (L"The first sound will stop playing soon after the user clicks for the second time.")
 ENTRY (L"Miscellaneous")
 NORMAL (L"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 "
@@ -3942,7 +3962,7 @@ NORMAL (L"To see whether any function keys are pressed (during a mouse click or
 	"you can use ##demoShiftKeyPressed ( )#, ##demoCommandKeyPressed ( )#, ##demoOptionKeyPressed ( )#, and "
 	"##demoExtraControlKeyPressed ( )#.")
 NORMAL (L"To put some text in the title bar of the Demo window, try")
-CODE (L"#demoWindowTitle (\"This is the title of my presentation\")")
+CODE (L"#demoWindowTitle: \"This is the title of my presentation\"")
 ENTRY (L"Tips and Tricks")
 NORMAL (L"If you resize the Demo window with the handle in the bottom left, or if you zoom the window out to the full screen, "
 	"you may see that the relative positions of the contents of the window will change. Also, clicking on buttons and in parts "
@@ -3955,8 +3975,8 @@ NORMAL (L"Your demo can save its preferences in a directory of its choice, "
 	"e.g. in ##'preferencesDirectory\\$ '/../GuineaPigAnalyzer# if your demo is called GuineaPigAnalyzer. "
 	"If you want to be less conspicuous and like to use the Praat preferences directory instead, "
 	"please use the ##apps# subdirectory, in this way:")
-CODE (L"createDirectory (preferencesDirectory\\$  + \"/apps\")")
-CODE (L"createDirectory (preferencesDirectory\\$  + \"/apps/GuineaPigAnalyzer\")")
+CODE (L"createDirectory: preferencesDirectory\\$  + \"/apps\"")
+CODE (L"createDirectory: preferencesDirectory\\$  + \"/apps/GuineaPigAnalyzer\"")
 MAN_END
 
 }
diff --git a/fon/manual_annotation.cpp b/fon/manual_annotation.cpp
index 8089fec..7041dae 100644
--- a/fon/manual_annotation.cpp
+++ b/fon/manual_annotation.cpp
@@ -1,6 +1,6 @@
 /* manual_annotation.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -169,7 +169,7 @@ LIST_ITEM (L"##TextGrid & Pitch: Draw...")
 LIST_ITEM (L"##TextGrid & Pitch: Draw separately...")
 MAN_END
 
-MAN_BEGIN (L"TextGrid: Count labels...", L"ppgb", 19980630)
+MAN_BEGIN (L"TextGrid: Count labels...", L"ppgb", 20140421)
 INTRO (L"A command to ask the selected @TextGrid object how many of the specified labels "
 	"it contains in the specified tier.")
 ENTRY (L"Settings")
@@ -182,8 +182,8 @@ NORMAL (L"The number of intervals or points with label %%Label text% in tier %%T
 	"is written into the @@Info window at . If the specified tier does not exist, the number will be 0.")
 ENTRY (L"Scripting")
 NORMAL (L"You can use this command to put the number into a script variable:")
-CODE (L"select TextGrid hallo")
-CODE (L"number_of_a = Count labels... 1 a")
+CODE (L"selectObject: \"TextGrid hallo\"")
+CODE (L"number_of_a = Count labels: 1, \"a\"")
 NORMAL (L"In this case, the value will not be written into the Info window.")
 MAN_END
  
@@ -282,7 +282,7 @@ NORMAL (L"You can check the spelling of the intervals in your tiers by including
 	"which will search for the next word in the tier or interval that does not occur in the lexicon.")
 MAN_END
 
-MAN_BEGIN (L"WordList", L"ppgb", 20110131)
+MAN_BEGIN (L"WordList", L"ppgb", 20140421)
 INTRO (L"One of the @@types of objects@ in Praat. "
 	"An object of class WordList contains a sorted list of strings in a system-independent format. "
 	"WordList objects can be used for spelling checking after conversion to a @SpellingChecker object.")
@@ -300,11 +300,11 @@ NORMAL (L"You can create a binary (compressed) WordList file from a simple text
 	"Perhaps such a text file has been supplied by a lexicographic institution in your country; "
 	"because of copyright issues, such word lists cannot be distributed with the Praat program. "
 	"To convert the simple text file into a compressed WordList file, you basically take the following steps:")
-CODE (L"Read Strings from raw text file... lexicon.iso")
+CODE (L"Read Strings from raw text file: \"lexicon.iso\"")
 CODE (L"Genericize")
 CODE (L"Sort")
 CODE (L"To WordList")
-CODE (L"Save as binary file... lexicon.WordList")
+CODE (L"Save as binary file: \"lexicon.WordList\"")
 NORMAL (L"I'll explain these steps in detail. "
 	"For instance, a simple text file may contain the following list of words:")
 CODE (L"cook")
diff --git a/fon/manual_formant.cpp b/fon/manual_formant.cpp
index 5646468..432ad72 100644
--- a/fon/manual_formant.cpp
+++ b/fon/manual_formant.cpp
@@ -1,6 +1,6 @@
 /* manual_formant.cpp
  *
- * Copyright (C) 1992-2010 Paul Boersma
+ * Copyright (C) 1992-2010,2014 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
@@ -171,7 +171,7 @@ TAG (L"##Interpolation")
 DEFINITION (L"the interpolation method (#None or #Parabolic). See @@vector peak interpolation at .")
 MAN_END
 
-MAN_BEGIN (L"Formant: Get mean...", L"ppgb", 19991016)
+MAN_BEGIN (L"Formant: Get mean...", L"ppgb", 20140421)
 INTRO (L"A @query to ask the selected @Formant object for the mean value of the specified formant.")
 ENTRY (L"Return value")
 NORMAL (L"the mean, in hertz or Bark.")
@@ -186,8 +186,8 @@ TAG (L"##Units")
 DEFINITION (L"the units of the result (#Hertz or #Bark).")
 ENTRY (L"Scripting")
 NORMAL (L"You can use this command to put the mean into a script variable:")
-CODE (L"select Formant hallo")
-CODE (L"mean = Get mean... 2 0 0 Hertz")
+CODE (L"selectObject: \"Formant hallo\"")
+CODE (L"mean = Get mean: 2, 0, 0, \"Hertz\"")
 NORMAL (L"In this case, the value will not be written into the Info window.")
 MAN_END
 
diff --git a/fon/manual_glossary.cpp b/fon/manual_glossary.cpp
index fe24853..43f984f 100644
--- a/fon/manual_glossary.cpp
+++ b/fon/manual_glossary.cpp
@@ -1,6 +1,6 @@
 /* manual_glossary.cpp
  *
- * Copyright (C) 1992-2008 Paul Boersma
+ * Copyright (C) 1992-2008,2014 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
@@ -61,19 +61,19 @@ INTRO (L"- the interpretation of values in tiers before the first point or after
 ENTRY (L"Example")
 NORMAL (L"The following is a @PitchTier with three points:")
 SCRIPT (4, 3, L""
-	"Create PitchTier... tier 0 0.5\n"
-	"Add point... 0.10 170\n"
-	"Add point... 0.20 180\n"
-	"Add point... 0.45 110\n"
-	"Draw... 0 0 50 250 yes\n"
+	"Create PitchTier: \"tier\", 0, 0.5\n"
+	"Add point: 0.10, 170\n"
+	"Add point: 0.20, 180\n"
+	"Add point: 0.45, 110\n"
+	"Draw: 0, 0, 50, 250, \"yes\"\n"
 	"Remove\n"
-	"One mark left... 100 yes yes yes\n"
-	"One mark left... 150 yes yes yes\n"
-	"One mark left... 200 yes yes yes\n"
-	"One mark bottom... 0.1 yes yes yes\n"
-	"One mark bottom... 0.2 yes yes yes\n"
-	"One mark bottom... 0.3 yes yes yes\n"
-	"One mark bottom... 0.4 yes yes yes\n"
+	"One mark left: 100, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark left: 150, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark left: 200, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.1, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.2, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.3, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.4, \"yes\", \"yes\", \"yes\", \"\"\n"
 )
 NORMAL (L"Between 0.10 and 0.20 seconds, the pitch rises from 170 to 180 Hz, "
 	"and between 0.20 and 0.45 seconds it falls from 180 to 110 Hz. "
@@ -147,19 +147,19 @@ INTRO (L"- the interpretation of values in tiers between the first point and the
 ENTRY (L"Example")
 NORMAL (L"The following is a @PitchTier with three points:")
 SCRIPT (4, 3, L""
-	"Create PitchTier... tier 0 0.5\n"
-	"Add point... 0.10 170\n"
-	"Add point... 0.20 180\n"
-	"Add point... 0.45 110\n"
-	"Draw... 0 0 50 250 yes\n"
+	"Create PitchTier: \"tier\", 0, 0.5\n"
+	"Add point: 0.10, 170\n"
+	"Add point: 0.20, 180\n"
+	"Add point: 0.45, 110\n"
+	"Draw: 0, 0, 50, 250, \"yes\"\n"
 	"Remove\n"
-	"One mark left... 100 yes yes yes\n"
-	"One mark left... 150 yes yes yes\n"
-	"One mark left... 200 yes yes yes\n"
-	"One mark bottom... 0.1 yes yes yes\n"
-	"One mark bottom... 0.2 yes yes yes\n"
-	"One mark bottom... 0.3 yes yes yes\n"
-	"One mark bottom... 0.4 yes yes yes\n"
+	"One mark left: 100, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark left: 150, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark left: 200, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.1, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.2, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.3, \"yes\", \"yes\", \"yes\", \"\"\n"
+	"One mark bottom: 0.4, \"yes\", \"yes\", \"yes\", \"\"\n"
 )
 NORMAL (L"Between 0.10 and 0.20 seconds, the pitch rises linearly from 170 to 180 Hz, "
 	"and between 0.20 and 0.45 seconds it falls linearly from 180 to 110 Hz. "
@@ -452,7 +452,7 @@ NORMAL (L"On a clock, time runs around in circles. In Praat's editor windows, ti
 	"To see another part, you %scroll backward or forward.")
 MAN_END
 
-MAN_BEGIN (L"time domain", L"ppgb", 20110128)
+MAN_BEGIN (L"time domain", L"ppgb", 20140421)
 INTRO (L"This manual page assumes that you have read the @Intro.")
 NORMAL (L"Many objects in Praat are %%functions of time%. Examples are: "
 	"@Sound, @Pitch, @Spectrogram, @Formant, @Intensity, @TextGrid, "
@@ -484,12 +484,12 @@ LIST_ITEM (L"##Get end time")
 LIST_ITEM (L"##Get total duration")
 NORMAL (L"If you choose one of these commands, the Info window will tell you the result, "
 	"expressed in seconds. These commands are most useful in a Praat script. Example:")
-CODE (L"select Pitch hello")
+CODE (L"selectObject: \"Pitch hello\"")
 CODE (L"startTime = Get start time")
 CODE (L"endTime = Get end time")
 CODE (L"centreTime = (startTime + endTime) / 2")
-CODE (L"echo This Pitch runs from 'startTime' to 'endTime' seconds,")
-CODE (L"printline and the centre of its time domain is at 'centreTime' seconds.")
+CODE (L"writeInfoLine: \"This Pitch runs from \", startTime, \" to \", endTime, \" seconds,\"")
+CODE (L"appendInfoLine: \"and the centre of its time domain is at \", centreTime, \" seconds.\"")
 ENTRY (L"Details for hackers")
 NORMAL (L"If you select an object that is a function of time and you click @Inspect, "
 	"you can see how the time domain information is stored in the object: "
diff --git a/fon/manual_references.cpp b/fon/manual_references.cpp
index 5707055..2e2ded2 100644
--- a/fon/manual_references.cpp
+++ b/fon/manual_references.cpp
@@ -31,7 +31,7 @@ MAN_BEGIN (L"Boersma (1993)", L"ppgb", 20030312)
 NORMAL (L"Paul Boersma (1993): \"Accurate short-term analysis of the fundamental frequency "
 	"and the harmonics-to-noise ratio of a sampled sound.\" "
 	"%%Proceedings of the Institute of Phonetic Sciences% #17: 97\\--110. University of Amsterdam.")
-NORMAL (L"Can be downloaded as a PDF file from http://fon.hum.uva.nl/paul/")
+NORMAL (L"Can be downloaded as a PDF file from http://www.fon.hum.uva.nl/paul/")
 MAN_END
 
 MAN_BEGIN (L"Boersma (1997)", L"ppgb", 19981219)
@@ -60,40 +60,40 @@ MAN_END
 MAN_BEGIN (L"Boersma (2009a)", L"ppgb", 20100330)
 NORMAL (L"Paul Boersma (2009): \"Should jitter be measured by peak picking or by waveform matching?\" "
 	"%%Folia Phoniatrica et Logopaedica% #61: 305\\--308.")
-NORMAL (L"Can be downloaded as a PDF file from http://fon.hum.uva.nl/paul/")
+NORMAL (L"Can be downloaded as a PDF file from http://www.fon.hum.uva.nl/paul/")
 MAN_END
 
 MAN_BEGIN (L"Boersma (2009b)", L"ppgb", 20100330)
 NORMAL (L"Paul Boersma (2009b): \"Some correct error-driven versions of the Constraint Demotion algorithm.\" "
 	"%%Linguistic Inquiry% #40: 667\\--686.")
-NORMAL (L"Can be downloaded as a PDF file from http://fon.hum.uva.nl/paul/")
+NORMAL (L"Can be downloaded as a PDF file from http://www.fon.hum.uva.nl/paul/")
 MAN_END
 
 MAN_BEGIN (L"Boersma & Escudero (2008)", L"ppgb", 20100331)
-NORMAL (L"Paul Boersma & Paola Escudero (2009): \"Learning to perceive a smaller L2 vowel inventory: "
+NORMAL (L"Paul Boersma & Paola Escudero (2008): \"Learning to perceive a smaller L2 vowel inventory: "
 	"an Optimality Theory account.\" In Peter Avery, Elan Dresher & Keren Rice (eds.), "
 	"%%Contrast in phonology: theory, perception, acquisition%. Berlin: Mouton De Gruyter. 271\\--301.")
-NORMAL (L"Can be downloaded as a PDF file from http://fon.hum.uva.nl/paul/")
+NORMAL (L"Can be downloaded as a PDF file from http://www.fon.hum.uva.nl/paul/")
 MAN_END
 
 MAN_BEGIN (L"Boersma & Hayes (2001)", L"ppgb", 20020511)
 NORMAL (L"Paul Boersma & Bruce Hayes (2001): \"Empirical tests of the Gradual Learning Algorithm.\" "
 	"%%Linguistic Inquiry% #32: 45\\--86.")
-NORMAL (L"Can be downloaded as a PDF file from http://fon.hum.uva.nl/paul/")
+NORMAL (L"Can be downloaded as a PDF file from http://www.fon.hum.uva.nl/paul/")
 MAN_END
 
 MAN_BEGIN (L"Boersma & Kovacic (2006)", L"ppgb", 20061203)
 NORMAL (L"Paul Boersma & Gordana Kovacic (2006): "
 	"\"Spectral characteristics of three styles of Croatian folk singing.\" "
 	"%%Journal of the Acoustical Society of America% #119: 1805\\--1816.")
-NORMAL (L"Can be downloaded as a PDF file from http://fon.hum.uva.nl/paul/")
+NORMAL (L"Can be downloaded as a PDF file from http://www.fon.hum.uva.nl/paul/")
 MAN_END
 
 MAN_BEGIN (L"Boersma & Pater (2008)", L"ppgb", 20100331)
 NORMAL (L"Paul Boersma & Joe Pater (2008): "
 	"\"Convergence properties of a gradual learning algorithm for Harmonic Grammar.\" "
 	"%%Rutgers Optimality Archive% #970, http://ruccs.rutgers.edu/roa.html.")
-NORMAL (L"Can be downloaded as a PDF file from http://fon.hum.uva.nl/paul/")
+NORMAL (L"Can be downloaded as a PDF file from http://www.fon.hum.uva.nl/paul/")
 MAN_END
 
 MAN_BEGIN (L"Childers (1978)", L"ppgb", 20030515)
@@ -146,8 +146,8 @@ NORMAL (L"The authors show that if we model each vocal cord as two coupled mass-
 	"at least for a male speaker.")
 MAN_END
 
-MAN_BEGIN (L"J\\a\"ger (2003)", L"ppgb", 20070423)
-NORMAL (L"Gerhard J\\a\"ger (2003): \"Maximum Entropy Models and Stochastic Optimality Theory.\" "
+MAN_BEGIN (L"Jäger (2003)", L"ppgb", 20070423)
+NORMAL (L"Gerhard Jäger (2003): \"Maximum Entropy Models and Stochastic Optimality Theory.\" "
 	"To appear in Jane Grimshaw, Joan Maling, Chris Manning, Jane Simpson, and Annie Zaenen (eds.): "
 	"%%Architectures, rules, and preferences: A Festschrift for Joan Bresnan%, "
 	"CSLI Publications, Stanford.")
@@ -164,9 +164,9 @@ NORMAL (L"D.H. Klatt & L.C. Klatt (1990): \"Analysis, synthesis and perception o
 	"%%Journal of the Acoustical Society of America% #87: 820\\--856.")
 MAN_END
 
-MAN_BEGIN (L"Ladefoged (2001)", L"ppgb", 20030316)
+MAN_BEGIN (L"Ladefoged (2001)", L"ppgb", 20140325)
 NORMAL (L"Peter Ladefoged (2001). %%Vowels and consonants%: %%an introduction to the sounds of languages%. "
-	"Oxford: Blackwell.")
+	"Oxford: Blackwell. [second edition: 2005; third edition with Sandra Disner: 2012]")
 NORMAL (L"A very readable introduction to phonetics, mainly acoustic and articulatory. "
 	"Has lots of spectrograms of the sounds of the world's languages. Comes with a CD that has all those "
 	"sounds and includes training material for transcription (from another book).")
diff --git a/fon/manual_sound.cpp b/fon/manual_sound.cpp
index 9ea889b..37d77ce 100644
--- a/fon/manual_sound.cpp
+++ b/fon/manual_sound.cpp
@@ -1,6 +1,6 @@
 /* manual_sound.cpp
  *
- * Copyright (C) 1992-2010 Paul Boersma
+ * Copyright (C) 1992-2010,2014 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
@@ -107,7 +107,7 @@ Create Sound from formula... 'Naam' Mono begintijd eindtijd samplefrequentie
 */
 MAN_END
 
-MAN_BEGIN (L"Create Sound from tone complex...", L"ppgb", 20060202)
+MAN_BEGIN (L"Create Sound from tone complex...", L"ppgb", 20140421)
 INTRO (L"A command in the @@New menu@ to create a @Sound as the sum of a number of sine waves "
 	"with equidistant frequencies.")
 ENTRY (L"Settings")
@@ -140,8 +140,8 @@ ENTRY (L"Example 1: a pulse train")
 NORMAL (L"A series of pulses at regular intervals, "
 	"sampled after low-pass filtering at the Nyquist frequency, "
 	"can be regarded as a sum of cosine waves. For instance, a 100-Hz pulse train, "
-	"sampled at 22050 Hz, can be created with:")
-CODE (L"Create Sound from tone complex... train 0 1 22050 Cosine 100 0 0 0")
+	"sampled at 44100 Hz, can be created with:")
+CODE (L"Create Sound from tone complex: \"train\", 0, 1, 44100, \"Cosine\", 100, 0, 0, 0")
 NORMAL (L"Supplying the value 0 for %firstFrequency yields an unshifted harmonic complex.")
 ENTRY (L"Example 2: a shifted harmonic complex")
 NORMAL (L"Some experiments on human pitch perception (%%residue pitch%) use "
@@ -149,9 +149,9 @@ NORMAL (L"Some experiments on human pitch perception (%%residue pitch%) use "
 	"related frequencies that are all shifted by a constant amount.")
 NORMAL (L"For instance, to get a sum of sine waves with frequencies 105 Hz, 205 Hz, and 305 Hz, "
 	"you would use:")
-CODE (L"Create Sound from tone complex... train 0.3 1 22050 Sine 100 105 0 3")
+CODE (L"Create Sound from tone complex: \"train\", 0.3, 1, 44100, \"Sine\", 100, 105, 0, 3")
 NORMAL (L"or")
-CODE (L"Create Sound from tone complex... train 0.3 1 22050 Sine 100 105 350 0")
+CODE (L"Create Sound from tone complex: \"train\", 0.3, 1, 44100, \"Sine\", 100, 105, 350, 0")
 NORMAL (L"whichever you prefer.")
 NORMAL (L"Some of these experiments are described in @@Plomp (1967)@ and @@Patterson & Wightman (1976)@.")
 ENTRY (L"Algorithm")
@@ -172,16 +172,16 @@ CODE (L"form Add waves with decreasing amplitudes")
 CODE1 (L"natural Number_of_components 19")
 CODE (L"endform")
 CODE (L"\\#  Create a Matrix with frequency and amplitude information in each row:")
-CODE (L"Create simple Matrix... freqAndGain number_of_components 2 0")
-CODE (L"Formula... if col = 1 then row * 100 + 5 else 1 / row fi")
+CODE (L"Create simple Matrix: \"freqAndGain\", number_of_components, 2, \"0\"")
+CODE (L"Formula: \"if col = 1 then row * 100 + 5 else 1 / row fi\"")
 CODE (L"\\#  Create a large Matrix with all the component sine waves:")
-CODE (L"Create Matrix... components 0 1 10000 1e-4 0.5e-4 1 number_of_components number_of_components 1 1 0")
-CODE (L"Formula... Matrix_freqAndGain [2] * sin (2 * pi * Matrix_freqAndGain [1] * x)")
+CODE (L"Create Matrix: \"components\", 0, 1, 10000, 1e-4, 0.5e-4, 1, number_of_components, number_of_components, 1, 1, \"0\"")
+CODE (L"Formula: \"Matrix_freqAndGain [2] * sin (2 * pi * Matrix_freqAndGain [1] * x)\"")
 CODE (L"\\#  Integrate:")
-CODE (L"Formula... self + self [row - 1, col]")
+CODE (L"Formula: \"self + self [row - 1, col]\"")
 CODE (L"\\#  Publish last row:")
-CODE (L"To Sound (slice)... number_of_components")
-CODE (L"Scale amplitudes... 0.99")
+CODE (L"To Sound (slice): number_of_components")
+CODE (L"Scale amplitudes: 0.99")
 MAN_END
 
 MAN_BEGIN (L"Extract one channel...", L"ppgb", 20110129)
@@ -979,7 +979,7 @@ NORMAL (L"A new Sound will appear in the list of objects, "
 	"For instance, the Sound \"hallo\" will give a new Sound \"hallo_10000\".")
 MAN_END
 
-MAN_BEGIN (L"Sound: Set value at sample number...", L"ppgb", 20040420)
+MAN_BEGIN (L"Sound: Set value at sample number...", L"ppgb", 20140421)
 INTRO (L"A command to change a specified sample of the selected @Sound object.")
 ENTRY (L"Settings")
 TAG (L"##Sample number")
@@ -989,8 +989,8 @@ TAG (L"##New value")
 DEFINITION (L"the value that is to be put into the specified sample.")
 ENTRY (L"Scripting")
 NORMAL (L"Example:")
-CODE (L"select Sound hallo")
-CODE (L"Set value at sample number... 100 1/2")
+CODE (L"selectObject: \"Sound hallo\"")
+CODE (L"Set value at sample number: 100, 1/2")
 NORMAL (L"This sets the value of the 100th sample to 0.5.")
 MAN_END
 
diff --git a/fon/manual_soundFiles.cpp b/fon/manual_soundFiles.cpp
index 52cd1c3..b168c7b 100644
--- a/fon/manual_soundFiles.cpp
+++ b/fon/manual_soundFiles.cpp
@@ -1,6 +1,6 @@
 /* manual_soundFiles.cpp
  *
- * Copyright (C) 1992-2008 Paul Boersma
+ * Copyright (C) 1992-2008,2014 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
diff --git a/fon/manual_spectrum.cpp b/fon/manual_spectrum.cpp
index 28c9e03..573bd95 100644
--- a/fon/manual_spectrum.cpp
+++ b/fon/manual_spectrum.cpp
@@ -1,6 +1,6 @@
 /* manual_spectrum.cpp
  *
- * Copyright (C) 1992-2010 Paul Boersma
+ * Copyright (C) 1992-2010,2014 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
@@ -107,7 +107,7 @@ TAG (L"%z__1%i_, %i = 1 ... %n__%x_")
 DEFINITION (L"the power spectral density, expressed in dB. ")
 MAN_END
 
-MAN_BEGIN (L"Ltas: Get bin number from frequency...", L"ppgb", 20041122)
+MAN_BEGIN (L"Ltas: Get bin number from frequency...", L"ppgb", 20140421)
 INTRO (L"A @query to the selected @Ltas object.")
 ENTRY (L"Return bin")
 NORMAL (L"the band number belonging to the specified frequency, expressed as a real number.")
@@ -116,8 +116,8 @@ NORMAL (L"If the Ltas has a bin width of 1000 Hz, and the lowest frequency is 0
 	"the bin number associated with a frequency of 1800 Hz is 2.3.")
 ENTRY (L"Scripting")
 NORMAL (L"You can use this command to put the nearest bin centre into a script variable:")
-CODE (L"select Ltas hallo")
-CODE (L"bin = Get bin number from frequency... 1800")
+CODE (L"selectObject: \"Ltas hallo\"")
+CODE (L"bin = Get bin number from frequency: 1800")
 CODE (L"nearestBin = round (bin)")
 NORMAL (L"In this case, the value will not be written into the Info window. To round down or up, use")
 CODE (L"leftBin = floor (bin)")
@@ -288,7 +288,7 @@ TAG (L"##Bin number")
 DEFINITION (L"the bin whose value is to be looked up.")
 MAN_END
 
-MAN_BEGIN (L"Sound: To Spectrogram...", L"ppgb", 20081112)
+MAN_BEGIN (L"Sound: To Spectrogram...", L"ppgb", 20140421)
 INTRO (L"A command that creates a @Spectrogram from every selected @Sound object. "
 	"It performs a %%short-term spectral analysis%, which means that for a number of time points in the Sound, "
 	"Praat computes an approximation of the spectrum at that time. Each such spectrum is called an %%analysis frame%.")
@@ -335,57 +335,57 @@ NORMAL (L"For purposes of computation speed, Praat may decide to change the time
 ENTRY (L"Tests of the bandwidth")
 NORMAL (L"You can check the bandwidth formula with the following procedure:")
 CODE (L"! create a 1000-Hz sine wave, windowed by a 0.2-seconds Gaussian window.")
-CODE (L"Create Sound from formula... gauss Mono 0 1 22050 sin(2*pi*1000*x) * exp(-3*((x-0.5)/0.1)\\^ 2)")
+CODE (L"Create Sound from formula: \"gauss\", 1, 0.0, 1.0, 44100, \"sin(2*pi*1000*x) * exp(-3*((x-0.5)/0.1)\\^ 2)\"")
 CODE (L"! compute its spectrum and look at its bandwidth")
-CODE (L"To Spectrum... yes")
-CODE (L"Draw... 980 1020 20 80 yes")
-CODE (L"Marks bottom every... 1 2 yes yes yes")
-CODE (L"Marks left every... 1 2 yes yes yes")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Draw: 980, 1020, 20, 80, \"yes\"")
+CODE (L"Marks bottom every: 1, 2, \"yes\", \"yes\", \"yes\"")
+CODE (L"Marks left every: 1, 2, \"yes\", \"yes\", \"yes\"")
 CODE (L"! now you should see a peak at 1000 Hz with a 3 dB bandwidth of 7 Hz (20 dB: 17 Hz)")
 CODE (L"! more precise: compute the position and width of the peak, and write them to the console")
-CODE (L"Formula... if x<980 or x>1020 then 0 else self fi")
-CODE (L"To Formant (peaks)... 20")
+CODE (L"Formula: \"if x<980 or x>1020 then 0 else self fi\"")
+CODE (L"To Formant (peaks): 20")
 CODE (L"Write to console")
 CODE (L"! now you should be able to read that a peak was found at 999.99982 Hz")
 CODE (L"! with a bandwidth of 6.497 Hz; the theory above predicted 6.491 Hz")
 CODE (L"")
 CODE (L"! The same, windowed by a 0.1-seconds Hamming window.")
-CODE (L"Create Sound from formula... Hamming Mono 0 1 22050 if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(0.54+0.46*cos(pi*(x-0.5)/0.1)) fi")
-CODE (L"To Spectrum... yes")
-CODE (L"Formula... if x<970 or x>1030 then 0 else self fi")
-CODE (L"To Formant (peaks)... 20")
+CODE (L"Create Sound from formula: \"Hamming\", 1, 0.0, 1.0, 44100, \"if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(0.54+0.46*cos(pi*(x-0.5)/0.1)) fi\"")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Formula: \"if x<970 or x>1030 then 0 else self fi\"")
+CODE (L"To Formant (peaks): 20")
 CODE (L"Write to console")
 CODE (L"! peak at 999.99817 Hz, 3 dB bw 6.518 Hz, 20 dB bw 15 Hz, zero bw 20 Hz, sidelobe -42 dB")
 CODE (L"")
 CODE (L"! The same, windowed by a 0.1-seconds rectangular window.")
-CODE (L"Create Sound from formula... rectangular Mono 0 1 22050 if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x) fi")
-CODE (L"To Spectrum... yes")
-CODE (L"Formula... if x<970 or x>1030 then 0 else self fi")
-CODE (L"To Formant (peaks)... 20")
+CODE (L"Create Sound from formula: \"rectangular\", 1, 0.0, 1.0, 44100, \"if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x) fi\"")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Formula: \"if x<970 or x>1030 then 0 else self fi\"")
+CODE (L"To Formant (peaks): 20")
 CODE (L"Write to console")
 CODE (L"! peak at 999.99506 Hz, 3 dB bw 4.440 Hz, 20 dB bw 27 Hz, zero bw 10 Hz, sidelobe -14 dB")
 CODE (L"")
 CODE (L"! The same, windowed by a 0.1-seconds Hanning window.")
-CODE (L"Create Sound from formula... Hanning Mono 0 1 22050 if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(0.5+0.5*cos(pi*(x-0.5)/0.1)) fi")
-CODE (L"To Spectrum... yes")
-CODE (L"Formula... if x<970 or x>1030 then 0 else self fi")
-CODE (L"To Formant (peaks)... 20")
+CODE (L"Create Sound from formula: \"Hanning\", 1, 0.0, 1.0, 44100, \"if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(0.5+0.5*cos(pi*(x-0.5)/0.1)) fi\"")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Formula: \"if x<970 or x>1030 then 0 else self fi\"")
+CODE (L"To Formant (peaks): 20")
 CODE (L"Write to console")
 CODE (L"! peak at 999.99945 Hz, 3 dB bw 7.212 Hz, 20 dB bw 16 Hz, zero bw 20 Hz, sidelobe -31 dB")
 CODE (L"")
 CODE (L"! The same, windowed by a 0.1-seconds triangular window.")
-CODE (L"Create Sound from formula... triangular Mono 0 1 22050 if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(1-abs((x-0.5)/0.1)) fi")
-CODE (L"To Spectrum... yes")
-CODE (L"Formula... if x<970 or x>1030 then 0 else self fi")
-CODE (L"To Formant (peaks)... 20")
+CODE (L"Create Sound from formula: \"triangular\", 1, 0.0, 1.0, 44100, \"if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(1-abs((x-0.5)/0.1)) fi\"")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Formula: \"if x<970 or x>1030 then 0 else self fi\"")
+CODE (L"To Formant (peaks): 20")
 CODE (L"Write to console")
 CODE (L"! peak at 999.99933 Hz, 3 dB bw 6.384 Hz, 20 dB bw 15 Hz, zero bw 20 Hz, sidelobe -26 dB")
 CODE (L"")
 CODE (L"! The same, windowed by a 0.1-seconds parabolic window.")
-CODE (L"Create Sound from formula... parabolic Mono 0 1 22050 if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(1-((x-0.5)/0.1)^2) fi")
-CODE (L"To Spectrum... yes")
-CODE (L"Formula... if x<970 or x>1030 then 0 else self fi")
-CODE (L"To Formant (peaks)... 20")
+CODE (L"Create Sound from formula: \"parabolic\", 1, 0.0, 1.0, 44100, \"if x<0.4 or x>0.6 then 0 else sin(2*pi*1000*x)*(1-((x-0.5)/0.1)^2) fi\"")
+CODE (L"To Spectrum: \"yes\"")
+CODE (L"Formula: \"if x<970 or x>1030 then 0 else self fi\"")
+CODE (L"To Formant (peaks): 20")
 CODE (L"Write to console")
 CODE (L"! peak at 999.99921 Hz, 3 dB bw 5.786 Hz, 20 dB bw 12 Hz, zero bw 15 Hz, sidelobe -21 dB")
 MAN_END
diff --git a/fon/manual_tutorials.cpp b/fon/manual_tutorials.cpp
index 28cb3ee..c5a5b58 100644
--- a/fon/manual_tutorials.cpp
+++ b/fon/manual_tutorials.cpp
@@ -1,6 +1,6 @@
 /* manual_tutorials.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -23,9 +23,95 @@
 void manual_tutorials_init (ManPages me);
 void manual_tutorials_init (ManPages me) {
 
-MAN_BEGIN (L"What's new?", L"ppgb", 20130927)
+MAN_BEGIN (L"What's new?", L"ppgb", 20140726)
 INTRO (L"Latest changes in Praat.")
 /*LIST_ITEM (L"\\bu Manual page about @@drawing a vowel triangle at .")*/
+NORMAL (L"##5.3.82# (26 July 2014)")
+LIST_ITEM (L"\\bu Audio playback: if the sound has more channels than the audio hardware, distribute them evenly.")
+LIST_ITEM (L"\\bu Pause forms: more consistent appearance of the Revert button.")
+LIST_ITEM (L"\\bu Scripting: pauseScript ( ) function.")
+NORMAL (L"##5.3.81# (2 July 2014)")
+LIST_ITEM (L"\\bu EEG: can work with status %numbers instead of only with status %bits.")
+LIST_ITEM (L"\\bu Windows: repaired a bug that could cause Praat to crash if there was a 96-dpi printer.")
+NORMAL (L"##5.3.80# (29 June 2014)")
+LIST_ITEM (L"\\bu Praat preferences: choice between Chinese and Japanese style for Han characters.")
+NORMAL (L"##5.3.79# (21 June 2014)")
+LIST_ITEM (L"\\bu Can now play sounds over more than two channels.")
+LIST_ITEM (L"\\bu Asynchronous play in scripts (see @@Demo window@).")
+LIST_ITEM (L"\\bu EEG: blue-to-red colour scale for scalp distributions.")
+NORMAL (L"##5.3.78# (12 June 2014)")
+LIST_ITEM (L"\\bu Multithreading can now speed up pitch analysis by a factor of 4 or so, "
+	"depending on the number of cores in your processor.")
+LIST_ITEM (L"\\bu Linux: can now open and save Photo objects (from PNG files) "
+	"and use @@Insert picture from file... at .")
+LIST_ITEM (L"\\bu Open WAV files that are in the \"extensible\" format (previously \"unsupported format -2\").")
+LIST_ITEM (L"\\bu Windows: support for dropping more than one file on the Praat icon.")
+LIST_ITEM (L"\\bu Scripting: can now use the #editor command with an object's ID instead of only with its name.")
+LIST_ITEM (L"\\bu Windows: removed a bug that sometimes disallowed saving more than one JPEG file.")
+LIST_ITEM (L"\\bu Linux audio: created a workaround that reduces the chances of a freeze that is due to a potential deadlock "
+	"in the collaboration between Alsa and PulseAudio that can occur when the playback of a sound is cancelled.")
+NORMAL (L"##5.3.77# (18 May 2014)")
+LIST_ITEM (L"\\bu EEG: more facilities for EDF+ files.")
+NORMAL (L"##5.3.76# (8 May 2014)")
+LIST_ITEM (L"\\bu One can determine the size of \"speckles\" (filled circles) with ##Speckle size...# in the #Pen menu. "
+	"Speckles are used in drawing Formant, PitchTier, and several other kinds of objects.")
+NORMAL (L"##5.3.75# (30 April 2014)")
+LIST_ITEM (L"\\bu Linux Matrix graphics bug fix: corrected working of ##Draw cells...#.")
+LIST_ITEM (L"\\bu Scripting bug fix: ability to use x and y as indexed variables.")
+LIST_ITEM (L"\\bu PowerCepstrogram bug fix: made old version of Paint command available again for scripts.")
+NORMAL (L"##5.3.74# (24 April 2014)")
+LIST_ITEM (L"\\bu EEG: more interpretation of triggers in EDF+ files.")
+NORMAL (L"##5.3.73# (21 April 2014)")
+LIST_ITEM (L"\\bu EEG: understand more EGI/NetStation files.")
+NORMAL (L"##5.3.72# (17 April 2014)")
+LIST_ITEM (L"\\bu Windows: repaired a bug that caused two black edges in PNG files.")
+LIST_ITEM (L"\\bu Windows: repaired a bug that could cause Praat to crash if a metafile resolution was 360 dpi.")
+LIST_ITEM (L"\\bu Linux: repaired a bug that caused Praat to crash when cutting or pasting a sound in the Sound window.")
+NORMAL (L"##5.3.71# (9 April 2014)")
+LIST_ITEM (L"\\bu Windows: brought more unity in the style of Chinese characters.")
+NORMAL (L"##5.3.70# (2 April 2014)")
+LIST_ITEM (L"\\bu Added some query commands for DurationTier objects.")
+LIST_ITEM (L"\\bu Repaired a bug that caused Praat not to run as a console app.")
+NORMAL (L"##5.3.69# (28 March 2014)")
+LIST_ITEM (L"\\bu Picture window: can save to 300-dpi and 600-dpi PNG files.")
+LIST_ITEM (L"\\bu Graphics: sub-pixel precision line drawing on Mac and Linux.")
+LIST_ITEM (L"\\bu Repaired a bug that could show spurious buttons in the Objects window if a plug-in created objects.")
+NORMAL (L"##5.3.68# (20 March 2014)")
+LIST_ITEM (L"\\bu Mac: corrected a bug introduced in 5.3.67 that could cause crashes when drawing a spectrogram.")
+LIST_ITEM (L"\\bu Mac and Linux: @@Create Strings as file list...@ handles broken symbolic links more leniently.")
+NORMAL (L"##5.3.67# (19 March 2014)")
+LIST_ITEM (L"\\bu Corrected a bug that would create strange PNG files if the selection did not touch the upper left corner of the Picture window.")
+LIST_ITEM (L"\\bu Mac: can save the Picture window to PNG file.")
+LIST_ITEM (L"\\bu EEG: understand trigger letters in BDF/EDF files.")
+NORMAL (L"##5.3.66# (9 March 2014)")
+LIST_ITEM (L"\\bu Windows and Linux: can save the Picture window to PNG file.")
+LIST_ITEM (L"\\bu Windows: opening, modifying and saving PNG, TIFF or JPEG files (the Photo object, as on the Mac).")
+NORMAL (L"##5.3.65# (27 February 2014)")
+LIST_ITEM (L"\\bu Scripting language: removed some bugs from runScript.")
+LIST_ITEM (L"\\bu Linux: can save the Picture window to PDF file.")
+NORMAL (L"##5.3.64# (12 February 2014)")
+LIST_ITEM (L"\\bu Scripting language: writeInfo, procedure, exitScript, runScript: all with colons.")
+LIST_ITEM (L"\\bu 64-bit Mac graphics: better highlighting and unhighlighting of selection.")
+LIST_ITEM (L"\\bu 64-bit Mac graphics: full screen.")
+NORMAL (L"##5.3.63# (24 January 2014)")
+LIST_ITEM (L"\\bu Scripting language: easier menu command invocation using the colon \":\".")
+LIST_ITEM (L"\\bu 64-bit Mac graphics: better handling of any absence of Doulos SIL or Charis SIL.")
+LIST_ITEM (L"\\bu Windows scripting: can now use \"~\" in file names to refer to home directory, as on Mac and Linux.")
+NORMAL (L"##5.3.62# (2 January 2014)")
+LIST_ITEM (L"\\bu 64-bit Mac: removed a bug introduced in 5.3.61 that could cause text containing \"ff\" to become invisible.")
+NORMAL (L"##5.3.61# (1 January 2014)")
+LIST_ITEM (L"\\bu EEG: understand status registers that contain text.")
+LIST_ITEM (L"\\bu KlattGrid: removed a bug introduced in May 2009 that could make Praat crash after editing an oral formant grid.")
+NORMAL (L"##5.3.60# (8 December 2013)")
+LIST_ITEM (L"\\bu Mac 64-bit: implemented swiping (to scroll with the trackpad) and pinching (to zoom with the trackpad).")
+LIST_ITEM (L"\\bu Scripting: backslashTrigraphsToUnicode () and unicodeToBackslashTrigraphs ().")
+NORMAL (L"##5.3.59# (20 November 2013)")
+LIST_ITEM (L"\\bu EEG: faster reading of BDF and EDF files.")
+LIST_ITEM (L"\\bu Batch scripting: made ##appendInfo()# write to the console in the same way as #print.")
+LIST_ITEM (L"\\bu Removed a bug introduced in 5.3.57 whereby some Praat text files could not be read.")
+NORMAL (L"##5.3.58# (17 November 2013)")
+LIST_ITEM (L"\\bu EEG: support for 16-bit (next to 24-bit) BDF files and for 16-bit (next to 8-bit) statuses.")
+LIST_ITEM (L"\\bu Mac: 64-bit beta version.")
 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.")
@@ -37,7 +123,7 @@ LIST_ITEM (L"\\bu Corrected a bug introduced in 5.3.54 by which you couldn't sel
 NORMAL (L"##5.3.54# (1 September 2013)")
 LIST_ITEM (L"\\bu Sound window: removed a bug introduced in 5.3.42 by which you couldn't ask for an odd number of poles in Formant Settings "
 	"(by e.g. specifying \"5.5\" for the number of formants).")
-LIST_ITEM (L"Linux: improved dragging of selections in the Picture window and the Sound window.")
+LIST_ITEM (L" Linux: improved dragging of selections in the Picture window and the Sound window.")
 NORMAL (L"##5.3.53# (9 July 2013)")
 LIST_ITEM (L"\\bu Table: more drawing commands.")
 NORMAL (L"##5.3.52# (12 June 2013)")
@@ -800,7 +886,7 @@ LIST_ITEM (L"\\bu Macintosh: improved drawing of spectrograms for printing and c
 	"(this was crippled in 4.5.18, but now it is better than before 4.5.18).")
 NORMAL (L"##4.5.21# (24 April 2007)")
 LIST_ITEM (L"\\bu OT learning: corrected HarmonicGrammar (and LinearOT) learning procedure "
-	"to the stochastic gradient ascent method applied by @@J\\a\"ger (2003)@ to MaxEnt grammars.")
+	"to the stochastic gradient ascent method applied by @@Jäger (2003)@ to MaxEnt grammars.")
 LIST_ITEM (L"\\bu Scripting: removed a bug that could make selection checking (in command windows) unreliable after a script was run.")
 NORMAL (L"##4.5.20# (19 April 2007)")
 LIST_ITEM (L"\\bu Scripting: allow assignments like $$pitch = To Pitch... 0 75 600$.")
@@ -1780,23 +1866,23 @@ NORMAL (L"If you choose ##Move cursor to maximum pitch#, then choose ##Get pitch
 	"lower values.")
 MAN_END
 
-MAN_BEGIN (L"FAQ: Scripts", L"ppgb", 20130421)
+MAN_BEGIN (L"FAQ: Scripts", L"ppgb", 20140120)
 NORMAL (L"#Question: how do I do something to all the files in a directory?")
 NORMAL (L"Answer: look at @@Create Strings as file list... at .")
 NORMAL (L"")
 NORMAL (L"#Question: why doesn't the editor window react to my commands?")
 NORMAL (L"Your commands are probably something like:")
-CODE (L"do (\"Read from file...\", \"hello.wav\")")
-CODE (L"do (\"View & Edit\")")
-CODE (L"do (\"Zoom...\", 0.3, 0.5)")
+CODE (L"Read from file: \"hello.wav\"")
+CODE (L"View & Edit")
+CODE (L"Zoom: 0.3, 0.5")
 NORMAL (L"Answer: Praat doesn't know it has to send the #Zoom command to the editor "
-	"window called ##Sound hello#. There could be several Sound editor windows on your "
+	"window called ##14. Sound hello#. There could be several Sound editor windows on your "
 	"screen. According to @@Scripting 7.1. Scripting an editor from a shell script@, "
 	"you will have to say this explicitly:")
-CODE (L"do (\"Read from file...\", \"hello.wav\")")
-CODE (L"do (\"View & Edit\")")
-CODE (L"editor Sound hello")
-CODE (L"do (\"Zoom...\", 0.3, 0.5)")
+CODE (L"Read from file: \"hello.wav\"")
+CODE (L"View & Edit")
+CODE (L"editor: \"Sound hello\"")
+CODE (L"Zoom: 0.3, 0.5")
 NORMAL (L"")
 NORMAL (L"#Problem: a line like \"Number = 1\" does not work.")
 NORMAL (L"Solution: names of variables should start with a lower-case letter.")
@@ -2722,7 +2808,7 @@ NORMAL (L"For instance, suppose you want to have a pitch that falls from 350 to
 	"You can put this PitchTier into a Manipulation object in the way described above.")
 MAN_END
 
-MAN_BEGIN (L"Intro 8.2. Manipulation of duration", L"ppgb", 20110128)
+MAN_BEGIN (L"Intro 8.2. Manipulation of duration", L"ppgb", 20140421)
 INTRO (L"You can use Praat to modify the relative durations in an existing sound.")
 NORMAL (L"First, you select a @Sound object and click \"To Manipulation\". "
 	"A @Manipulation object will then appear in the list. "
@@ -2747,11 +2833,11 @@ NORMAL (L"In your first 85 ms, your relative duration should be 70/85, "
 	"and during the last 270 ms, it should be 200/270. "
 	"The DurationTier does linear interpolation, so it can only be approximate these precise times, "
 	"but fortunately to any precision you like:")
-CODE (L"Create DurationTier... shorten 0 0.085+0.270")
-CODE (L"Add point... 0.000 70/85")
-CODE (L"Add point... 0.084999 70/85")
-CODE (L"Add point... 0.085001 200/270")
-CODE (L"Add point... 0.355 200/270")
+CODE (L"Create DurationTier: \"shorten\", 0, 0.085 + 0.270")
+CODE (L"Add point: 0.000 70/85")
+CODE (L"Add point: 0.084999, 70/85")
+CODE (L"Add point: 0.085001, 200/270")
+CODE (L"Add point: 0.355, 200/270")
 NORMAL (L"To put this DurationTier back into a Manipulation object, you select the two objects together "
 	"(e.g. a click on the DurationTier and a Command-click on the Manipulation), "
 	"and choose ##Replace duration tier#.")
@@ -2835,7 +2921,7 @@ NORMAL (L"To create new objects from files on disk, use the @@Open menu@ instead
 	"Objects can also often be create from other objects, with commands that start with ##To#.")
 MAN_END
 
-MAN_BEGIN (L"Object window", L"ppgb", 20030528)
+MAN_BEGIN (L"Object window", L"ppgb", 20140212)
 INTRO (L"One of the two main windows in the Praat program.")
 ENTRY (L"Subdivision")
 LIST_ITEM (L"To the left: the @@List of Objects at .")
@@ -2853,7 +2939,6 @@ LIST_ITEM (L"The Object window contains several fixed menus: "
 	"It also contains the #Save menu, whose contents vary with the kinds of selected objects, "
 	"and must, therefore, be considered part of the dynamic menu.")
 ENTRY (L"The Praat menu")
-LIST_ITEM (L"\\bu (@@Run script...@)")
 LIST_ITEM (L"\\bu @@New Praat script@: creates an empty @@ScriptEditor@")
 LIST_ITEM (L"\\bu @@Open Praat script...@: creates a @@ScriptEditor@ with a script from disk")
 LIST_ITEM (L"\\bu The ##Goodies submenu#: for doing things (like using the Calculator) "
@@ -3118,7 +3203,7 @@ LIST_ITEM (L"@@Source-filter synthesis 3. The ba-da continuum")
 LIST_ITEM (L"@@Source-filter synthesis 4. Using existing sounds")
 MAN_END
 
-MAN_BEGIN (L"Source-filter synthesis 1. Creating a source from pitch targets", L"ppgb", 20110128)
+MAN_BEGIN (L"Source-filter synthesis 1. Creating a source from pitch targets", L"ppgb", 20140421)
 INTRO (L"Creating a glottal source signal for speech synthesis involves creating a @PointProcess, "
 	"which is a series of time points that should represent the exact moments of glottal closure.")
 NORMAL (L"You may want to start with creating a well-defined pitch contour. "
@@ -3196,21 +3281,19 @@ NORMAL (L"The glottal source signal sounds as a voice without a vocal tract. "
 	"The following section describes how you add vocal-tract resonances, i.e. the %filter.")
 ENTRY (L"Automation")
 NORMAL (L"In a clean Praat script, the procedure described above will look as follows:")
-CODE (L"pitchTier = Create PitchTier... source 0 0.5")
-CODE (L"Add point... 0.0 150")
-CODE (L"Add point... 0.5 100")
+CODE (L"pitchTier = Create PitchTier: \"source\", 0, 0.5")
+CODE (L"Add point: 0.0, 150")
+CODE (L"Add point: 0.5, 100")
 CODE (L"pulses = To PointProcess")
-CODE (L"Remove points between... 0 0.02")
-CODE (L"Remove points between... 0.24 0.31")
-CODE (L"Remove points between... 0.48 0.5")
-CODE (L"source = To Sound (phonation)... 44100 0.6 0.05 0.7 0.03 3.0 4.0")
-CODE (L"select pitchTier")
-CODE (L"plus pulses")
-CODE (L"Remove")
-CODE (L"select source")
+CODE (L"Remove points between: 0, 0.02")
+CODE (L"Remove points between: 0.24, 0.31")
+CODE (L"Remove points between: 0.48, 0.5")
+CODE (L"source = To Sound (phonation): 44100, 0.6, 0.05, 0.7, 0.03, 3.0, 4.0")
+CODE (L"removeObject: pitchTier, pulses")
+CODE (L"selectObject: source")
 MAN_END
 
-MAN_BEGIN (L"Source-filter synthesis 2. Filtering a source", L"ppgb", 20080427)
+MAN_BEGIN (L"Source-filter synthesis 2. Filtering a source", L"ppgb", 20140421)
 INTRO (L"Once you have a glottal source signal, you are ready to create a filter that represents "
 	"the resonances of the vocal tract, as a function of time. In other words, you create a @FormantGrid object.")
 NORMAL (L"For a vowel spoken by an average (i.e. adult female) human voice, tradition assumes five formants in the range "
@@ -3220,19 +3303,19 @@ NORMAL (L"For a vowel spoken by an average (i.e. adult female) human voice, trad
 	"which, with a sound velocity of 352 m/s, means a resonance frequency of 352/0.64 = 550 hertz. "
 	"The other resonances will be at 1650, 2750, 3850, and 4950 hertz.")
 NORMAL (L"You can create a @FormantGrid object with @@Create FormantGrid...@ from the #New menu (submenu #Tiers):")
-CODE (L"Create FormantGrid... filter 0 0.5 10 550 1100 60 50")
+CODE (L"Create FormantGrid: \"filter\", 0, 0.5, 10, 550, 1100, 60, 50")
 NORMAL (L"This creates a FormantGrid with 10 formants and a single frequency value for each formant: %F__1_ is 550 Hz "
 	"and the higher formants are spaced 1100 Hz apart, i.e., they are "
 	"1650, 2750, 3850, 4950, 6050, 7150, 8250, 9350, and 10450 hertz; "
 	"the ten bandwidths start at 60 Hz and have a spacing of 50 Hz, "
 	"i.e., they are 60, 110, 160, 210, 260, 310, 360, 410, 460, and 510 hertz.")
 NORMAL (L"You can then create formant contours with @@FormantGrid: Add formant point...@:")
-CODE (L"Remove formant points between... 1 0 0.5")
-CODE (L"Add formant point... 1 0.00 100")
-CODE (L"Add formant point... 1 0.05 700")
-CODE (L"Remove formant points between... 2 0 0.5")
-CODE (L"Add formant point... 2 0.00 500")
-CODE (L"Add formant point... 2 0.05 1100")
+CODE (L"Remove formant points between: 1, 0, 0.5")
+CODE (L"Add formant point: 1, 0.00, 100")
+CODE (L"Add formant point: 1, 0.05, 700")
+CODE (L"Remove formant points between: 2, 0, 0.5")
+CODE (L"Add formant point: 2, 0.00, 500")
+CODE (L"Add formant point: 2, 0.05, 1100")
 NORMAL (L"This example creates a spectral specification whose %F__1_ rises from 100 to 700 hertz during the "
 	"first 50 milliseconds (as for any obstruent), and whose %F__2_ rises from 500 to 1100 hertz. "
 	"The other eight formants keep their original values, as do the ten bandwidths. "
@@ -3244,50 +3327,45 @@ NORMAL (L"The resulting sound will have a fairly straight intensity contour. You
 	"acoustic result with an @Intensity or @IntensityTier object.")
 MAN_END
 
-MAN_BEGIN (L"Source-filter synthesis 3. The ba-da continuum", L"ppgb", 20091012)
+MAN_BEGIN (L"Source-filter synthesis 3. The ba-da continuum", L"ppgb", 20140421)
 INTRO (L"As an example, we are going to create a male [ba]-[da] continuum in six steps. The acoustic difference "
 	"between [ba] and [da] is the initial %F__2_, which is 500 Hz for [ba], and 2500 Hz for [da].")
 NORMAL (L"We use the same @PitchTier throughout, to model a falling intonation contour:")
-CODE (L"Create PitchTier... f0 0.00 0.50")
-CODE (L"Add point... 0.00 150")
-CODE (L"Add point... 0.50 100")
+CODE (L"Create PitchTier: \"f0\", 0.00, 0.50")
+CODE (L"Add point: 0.00, 150")
+CODE (L"Add point: 0.50, 100")
 NORMAL (L"The first and last 50 milliseconds are voiceless:")
 CODE (L"To PointProcess")
-CODE (L"Remove points between... 0.00 0.05")
-CODE (L"Remove points between... 0.45 0.50")
+CODE (L"Remove points between: 0.00, 0.05")
+CODE (L"Remove points between: 0.45, 0.50")
 NORMAL (L"Generate the glottal source signal:")
-CODE (L"To Sound (phonation)... 44100 0.6 0.05 0.7 0.03 3.0 4.0")
+CODE (L"To Sound (phonation): 44100, 0.6, 0.05, 0.7, 0.03, 3.0, 4.0")
 NORMAL (L"During the labial or coronal closure, the sound is almost silent, so we use an @IntensityTier "
 	"that models this:")
-CODE (L"Create IntensityTier... intens 0.00 0.50")
-CODE (L"Add point... 0.05 60")
-CODE (L"Add point... 0.10 80")
+CODE (L"Create IntensityTier: \"intens\", 0.00, 0.50")
+CODE (L"Add point: 0.05, 60")
+CODE (L"Add point: 0.10, 80")
 NORMAL (L"Generate the source signal:")
-CODE (L"#plus Sound f0")
+CODE (L"#plusObject: \"Sound f0\"")
 CODE (L"Multiply")
-CODE (L"Rename... source")
+CODE (L"Rename: \"source\"")
 NORMAL (L"The ten sounds are generated in a loop:")
 CODE (L"#for i #from 1 #to 10")
-CODE (L"   f2_locus = 500 + (2500/9) * (i - 1) ; variable names start with lower case!")
-CODE (L"   Create FormantGrid... filter 0.0 0.5 9 800 1000 60 80")
-CODE (L"   Remove formant points between... 1 0.0 0.5")
-CODE (L"   Add formant point... 1 0.05 100")
-CODE (L"   Add bandwidth point... 1 0.05 50")
-CODE (L"   Add formant point... 2 0.05 f2_locus")
-CODE (L"   Add bandwidth point... 2 0.05 100")
-CODE (L"   #plus Sound source")
-CODE (L"   Filter (no scale)")
-CODE (L"   Rename... bada'i'")
-CODE (L"   #select FormantGrid filter")
-CODE (L"   Remove")
+	CODE1 (L"f2_locus = 500 + (2500/9) * (i - 1) ; variable names start with lower case!")
+	CODE1 (L"Create FormantGrid: \"filter\", 0.0, 0.5, 9, 800, 1000, 60, 80")
+	CODE1 (L"Remove formant points between: 1, 0.0, 0.5")
+	CODE1 (L"Add formant point: 1, 0.05, 100")
+	CODE1 (L"Add bandwidth point: 1, 0.05, 50")
+	CODE1 (L"Add formant point: 2, 0.05, f2_locus")
+	CODE1 (L"Add bandwidth point: 2, 0.05, 100")
+	CODE1 (L"#plusObject: \"Sound source\"")
+	CODE1 (L"Filter (no scale)")
+	CODE1 (L"Rename: \"bada\" + string\\$  (i)")
+	CODE1 (L"#removeObject: \"FormantGrid filter\"")
 CODE (L"#endfor")
 NORMAL (L"Clean up:")
-CODE (L"#select Sound source")
-CODE (L"#plus Sound f0")
-CODE (L"#plus IntensityTier intens")
-CODE (L"#plus PointProcess f0")
-CODE (L"#plus PitchTier f0")
-CODE (L"Remove")
+CODE (L"#removeObject: \"Sound source\", \"Sound f0\", \"IntensityTier intens\",")
+CODE (L"... \"PointProcess f0\", \"PitchTier f0\"")
 NORMAL (L"In this example, filtering was done without automatic scaling, so that "
 	"the resulting signals have equal intensities in the areas where they have "
 	"equal formants. You will probably want to multiply all these signals with "
@@ -3295,7 +3373,7 @@ NORMAL (L"In this example, filtering was done without automatic scaling, so that
 	"between -1 and +1 Pascal.")
 MAN_END
 
-MAN_BEGIN (L"Source-filter synthesis 4. Using existing sounds", L"ppgb", 20111018)
+MAN_BEGIN (L"Source-filter synthesis 4. Using existing sounds", L"ppgb", 20140421)
 ENTRY (L"1. How to extract the %filter from an existing speech sound")
 NORMAL (L"You can separate source and filter with the help of the technique of %%linear prediction% "
 	"(see @@Sound: LPC analysis@). This technique tries to approximate a given frequency spectrum with "
@@ -3315,12 +3393,12 @@ NORMAL (L"To perform the resampling, you use @@Sound: Resample...@: "
 	"you select a @Sound object, and click ##Resample...#. "
 	"In the rest of this tutorial, I will use the syntax that you would use in a script, "
 	"though you will usually do these things by clicking on objects and buttons. Thus:")
-CODE (L"#select Sound hallo")
-CODE (L"Resample... 11000 50")
+CODE (L"#selectObject: \"Sound hallo\"")
+CODE (L"Resample: 11000, 50")
 NORMAL (L"You can then perform a linear-prediction analysis on the resampled sound "
 	"with @@Sound: To LPC (burg)...@:")
-CODE (L"#select Sound hallo_11000")
-CODE (L"To LPC (burg)... 10 0.025 0.005 50")
+CODE (L"#selectObject: \"Sound hallo_11000\"")
+CODE (L"To LPC (burg): 10, 0.025, 0.005, 50")
 NORMAL (L"This says that your analysis is done with 10 linear-prediction parameters "
 	"(which will yield at most five formant-bandwidth pairs), with an analysis window "
 	"effectively 25 milliseconds long, with time steps of 5 milliseconds (so that the windows "
@@ -3331,16 +3409,16 @@ NORMAL (L"As a result, an object called \"LPC hallo\" will appear in the list of
 	"These coefficients are rather opaque even to the expert (try to view them with @Inspect), "
 	"but they are the raw material from which formant and bandwidth values can be computed. "
 	"To see the smoothed @Spectrogram associated with the LPC object, choose @@LPC: To Spectrogram...@:")
-CODE (L"#select LPC hallo_11000")
-CODE (L"To Spectrogram... 20 0 50")
-CODE (L"Paint... 0 0 0 0 50 0 0 yes")
+CODE (L"#selectObject: \"LPC hallo_11000\"")
+CODE (L"To Spectrogram: 20, 0, 50")
+CODE (L"Paint: 0, 0, 0, 0, 50, 0, 0, \"yes\"")
 NORMAL (L"Note that when drawing this Spectrogram, you will want to set the pre-emphasis to zero "
 	"(the fifth 0 in the last line), because pre-emphasis has already been applied in the analysis.")
 NORMAL (L"You can get and draw the formant-bandwidth pairs from the LPC object, "
 	"with @@LPC: To Formant@ and @@Formant: Speckle...@:")
-CODE (L"#select LPC hallo_11000")
+CODE (L"#selectObject: \"LPC hallo_11000\"")
 CODE (L"To Formant")
-CODE (L"Speckle... 0 0 5500 30 yes")
+CODE (L"Speckle: 0, 0, 5500, 30, \"yes\"")
 NORMAL (L"Note that in converting the @LPC into a @Formant object, you may have lost some "
 	"information about spectral peaks at very low frequencies (below 50 Hz) or at very high "
 	"frequencies (near the @@Nyquist frequency@ of 5500 Hz. Such peaks usually try to fit "
@@ -3349,8 +3427,8 @@ NORMAL (L"Note that in converting the @LPC into a @Formant object, you may have
 	"For resynthesis purposes, they might still be important.")
 NORMAL (L"Instead of using the intermediate LPC object, you could have done a formant analysis "
 	"directly on the original Sound, with @@Sound: To Formant (burg)...@:")
-CODE (L"#select Sound hallo")
-CODE (L"To Formant (burg)... 0.005 5 5500 0.025 50")
+CODE (L"#selectObject: \"Sound hallo\"")
+CODE (L"To Formant (burg): 0.005, 5, 5500, 0.025, 50")
 NORMAL (L"A @Formant object has a fixed sampling (time step, frame length), and for every "
 	"%%formant frame%, it contains a number of formant-bandwidth pairs.")
 NORMAL (L"From a Formant object, you can create a @FormantGrid with @@Formant: Down to FormantGrid at . "
@@ -3361,8 +3439,7 @@ ENTRY (L"2. How to extract the %source from an existing speech sound")
 NORMAL (L"If you are only interested in the %filter characteristics, you can get by with @Formant objects. "
 	"To get at the %source signal, however, you need the raw @LPC object: "
 	"you select it together with the resampled @Sound, and apply %%inverse filtering%:")
-CODE (L"#select Sound hallo_11000")
-CODE (L"#plus LPC hallo_11000")
+CODE (L"#selectObject: \"Sound hallo_11000\", \"LPC hallo_11000\"")
 CODE (L"Filter (inverse)")
 NORMAL (L"A new Sound named \"hallo_11000\" will appear in the list of objects "
 	"(you could rename it to \"source\"). "
@@ -3380,40 +3457,36 @@ NORMAL (L"Note that with inverse filtering you cannot measure the actual spectra
 ENTRY (L"3. How to do the synthesis")
 NORMAL (L"You can create a new Sound from a source Sound and a filter, in at least four ways.")
 NORMAL (L"If your filter is an @LPC object, you select it and the source, and choose @@LPC & Sound: Filter...@:")
-CODE (L"#select Sound source")
-CODE (L"#plus LPC filter")
-CODE (L"Filter... no")
+CODE (L"#selectObject: \"Sound source\", \"LPC filter\"")
+CODE (L"Filter: \"no\"")
 NORMAL (L"If you had computed the source and filter from an LPC analysis, this procedure should give "
 	"you back the original Sound, except that windowing has caused 25 milliseconds at the beginning "
 	"and end of the signal to be set to zero.")
 NORMAL (L"If your filter is a @Formant object, you select it and the source, and choose @@Sound & Formant: Filter@:")
-CODE (L"#select Sound source")
-CODE (L"#plus Formant filter")
+CODE (L"#selectObject: \"Sound source\", \"Formant filter\"")
 CODE (L"Filter")
 NORMAL (L"If you had computed the source and filter from an LPC analysis, this procedure will not generally give "
 	"you back the original Sound, because some linear-prediction coefficients will have been ignored "
 	"in the conversion to formant-bandwidth pairs.")
 NORMAL (L"If your filter is a @FormantGrid object, you select it and the source, and choose @@Sound & FormantGrid: Filter@:")
-CODE (L"#select Sound source")
-CODE (L"#plus FormantGrid filter")
+CODE (L"#selectObject: \"Sound source\", \"FormantGrid filter\"")
 CODE (L"Filter")
 NORMAL (L"Finally, you could just know the %%impulse response% of your filter (in a @Sound object). "
 	"You then select both Sound objects, and choose @@Sounds: Convolve...@:")
-CODE (L"#select Sound source")
-CODE (L"#plus Sound filter")
-CODE (L"Convolve... integral zero")
+CODE (L"#selectObject: \"Sound source\", \"Sound filter\"")
+CODE (L"Convolve: \"integral\", \"zero\"")
 NORMAL (L"As a last step, you may want to bring the resulting sound within the [-1; +1] range:")
-CODE (L"Scale peak... 0.99")
+CODE (L"Scale peak: 0.99")
 ENTRY (L"4. How to manipulate the filter")
 NORMAL (L"You can hardly change the values in an @LPC object in a meaningful way: "
 	"you would have to manually change its rather opaque data with the help of @Inspect.")
 NORMAL (L"A @Formant object can be changed in a friendlier way, with @@Formant: Formula (frequencies)...@ "
 	"and @@Formant: Formula (bandwidths)... at . For instance, to multiply all formant frequencies by 0.9, "
 	"you do")
-CODE (L"#select Formant filter")
-CODE (L"Formula (frequencies)... self * 0.9")
+CODE (L"#selectObject: \"Formant filter\"")
+CODE (L"Formula (frequencies): \"self * 0.9\"")
 NORMAL (L"To add 200 hertz to all values of %F__2_, you do")
-CODE (L"Formula (frequencies)... if row = 2 then self + 200 else self fi")
+CODE (L"Formula (frequencies): \"if row = 2 then self + 200 else self fi\"")
 NORMAL (L"A @FormantGrid object can be changed by adding or removing points:")
 LIST_ITEM (L"@@FormantGrid: Add formant point...@")
 LIST_ITEM (L"@@FormantGrid: Add bandwidth point...@")
diff --git a/fon/manual_voice.cpp b/fon/manual_voice.cpp
index 6a5de5f..4c4be5c 100644
--- a/fon/manual_voice.cpp
+++ b/fon/manual_voice.cpp
@@ -1,6 +1,6 @@
 /* manual_voice.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -215,7 +215,7 @@ NORMAL (L"The difference between Praat's and MDVP's jitter measures is due to a
 	"For detailed illustrations, see @@Boersma (2009a)@.")
 MAN_END
 
-MAN_BEGIN (L"Voice 6. Automating voice analysis with a script", L"ppgb", 20061028)
+MAN_BEGIN (L"Voice 6. Automating voice analysis with a script", L"ppgb", 20140421)
 INTRO (L"In a Praat script you usually do not want to raise a Sound window. "
 	"Instead, you probably want to work with objects in the Objects window only. "
 	"This page tells you how to do that for voice analysis.")
@@ -251,10 +251,10 @@ NORMAL (L"If you select the Sound, the Pitch, and the PointProcess together (all
 	"will be written to the Info window. This is identical to the voice report in the Sound window, "
 	"although you will have to specify the time range by manually typing it.")
 NORMAL (L"In a script, you can get the jitter and shimmer from the voice report by doing something like:")
-CODE (L"voiceReport\\$  = Voice report... 0 0 75 500 1.3 1.6 0.03 0.45")
+CODE (L"voiceReport\\$  = Voice report: 0, 0, 75, 500, 1.3, 1.6, 0.03, 0.45")
 CODE (L"jitter = extractNumber (voiceReport\\$ , \"Jitter (local): \")")
 CODE (L"shimmer = extractNumber (voiceReport\\$ , \"Shimmer (local): \")")
-CODE (L"echo Jitter = 'jitter:3\\% ', shimmer = 'shimmer:3\\% '")
+CODE (L"writeInfoLine: \"Jitter = \", percent\\$  (jitter, 3), \", shimmer = \", percent\\$  (shimmer, 3)")
 ENTRY (L"5. Disadvantage of automating voice analysis")
 NORMAL (L"In all the commands mentioned above, you have to guess the time range, "
 	"and you would usually supply \"0.0\" and \"0.0\", in which case "
diff --git a/fon/praat_Fon.cpp b/fon/praat_Fon.cpp
index 7c29051..a6cf3e3 100644
--- a/fon/praat_Fon.cpp
+++ b/fon/praat_Fon.cpp
@@ -1,6 +1,6 @@
 /* praat_Fon.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -1142,6 +1142,11 @@ END
 DIRECT (Formant_PointProcess_to_FormantTier)
 	Formant formant = NULL;
 	PointProcess point = NULL;
+	LOOP {
+		if (CLASS == classFormant) formant = (Formant) OBJECT;
+		if (CLASS == classPointProcess) point = (PointProcess) OBJECT;
+		if (formant && point) break;
+	}
 	autoFormantTier thee = Formant_PointProcess_to_FormantTier (formant, point);
 	praat_new (thee.transfer(), formant -> name, L"_", point -> name);
 END
@@ -2097,7 +2102,7 @@ FORM (Ltas_getBinNumberFromFrequency, L"Ltas: Get band from frequency", L"Ltas:
 	OK
 DO
 	Ltas me = FIRST (Ltas);
-	double binNumber = Sampled_xToIndex (me, GET_REAL (L"Frequency"));
+	double binNumber = my f_xToIndex (GET_REAL (L"Frequency"));
 	Melder_informationReal (binNumber, NULL);
 END
 
@@ -2111,7 +2116,7 @@ FORM (Ltas_getFrequencyFromBinNumber, L"Ltas: Get frequency from bin number", L"
 	OK
 DO
 	Ltas me = FIRST (Ltas);
-	double frequency = Sampled_indexToX (me, GET_INTEGER (L"Bin number"));
+	double frequency = my f_indexToX (GET_INTEGER (L"Bin number"));
 	Melder_informationReal (frequency, L"hertz");
 END
 
@@ -3011,7 +3016,7 @@ DO
 	LOOP {
 		iam (Movie);
 		autoPraatPicture picture;
-		my f_paintOneImage (GRAPHICS, GET_INTEGER (L"Frame number"),
+		Movie_paintOneImage (me, GRAPHICS, GET_INTEGER (L"Frame number"),
 			GET_REAL (L"From x ="), GET_REAL (L"To x ="), GET_REAL (L"From y ="), GET_REAL (L"To y ="));
 	}
 END
@@ -3065,11 +3070,11 @@ FORM (Photo_create, L"Create Photo", L"Create Photo...")
 	POSITIVE (L"dy", L"1.0")
 	REAL (L"y1", L"1.0")
 	LABEL (L"", L"Red formula:")
-	TEXTFIELD (L"redFormula", L"x*y")
+	TEXTFIELD (L"redFormula", L"x*y/100")
 	LABEL (L"", L"Green formula:")
-	TEXTFIELD (L"greenFormula", L"x*y")
+	TEXTFIELD (L"greenFormula", L"x*y/1000")
 	LABEL (L"", L"Blue formula:")
-	TEXTFIELD (L"blueFormula", L"x*y")
+	TEXTFIELD (L"blueFormula", L"x*y/100")
 	OK
 DO
 	double xmin = GET_REAL (L"xmin"), xmax = GET_REAL (L"xmax");
@@ -3090,11 +3095,11 @@ FORM (Photo_createSimple, L"Create simple Photo", L"Create simple Photo...")
 	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")
+	TEXTFIELD (L"redFormula", L"x*y/100")
 	LABEL (L"", L"Green formula:")
-	TEXTFIELD (L"greenFormula", L"x*y")
+	TEXTFIELD (L"greenFormula", L"x*y/1000")
 	LABEL (L"", L"Blue formula:")
-	TEXTFIELD (L"blueFormula", L"x*y")
+	TEXTFIELD (L"blueFormula", L"x*y/100")
 	OK
 DO
 	autoPhoto me = Photo_createSimple (GET_INTEGER (L"Number of rows"), GET_INTEGER (L"Number of columns"));
@@ -5348,7 +5353,7 @@ FORM (Spectrum_getBinFromFrequency, L"Spectrum: Get bin from frequency", 0)
 DO
 	LOOP {
 		iam (Spectrum);
-		double bin = Sampled_xToIndex (me, GET_REAL (L"Frequency"));
+		double bin = my f_xToIndex (GET_REAL (L"Frequency"));
 		Melder_informationReal (bin, NULL);
 	}
 END
@@ -5389,7 +5394,7 @@ FORM (Spectrum_getFrequencyFromBin, L"Spectrum: Get frequency from bin", 0)
 DO
 	LOOP {
 		iam (Spectrum);
-		double frequency = Sampled_indexToX (me, GET_INTEGER (L"Band number"));
+		double frequency = my f_indexToX (GET_INTEGER (L"Band number"));
 		Melder_informationReal (frequency, L"hertz");
 	}
 END
@@ -5947,7 +5952,7 @@ FORM (TimeFrameSampled_getFrameFromTime, L"Get frame number from time", L"Get fr
 DO
 	LOOP {
 		iam (Sampled);
-		double frame = Sampled_xToIndex (me, GET_REAL (L"Time"));
+		double frame = my f_xToIndex (GET_REAL (L"Time"));
 		Melder_informationReal (frame, NULL);
 	}
 END
@@ -5966,7 +5971,7 @@ FORM (TimeFrameSampled_getTimeFromFrame, L"Get time from frame number", L"Get ti
 DO
 	LOOP {
 		iam (Sampled);
-		double time = Sampled_indexToX (me, GET_INTEGER (L"Frame number"));
+		double time = my f_indexToX (GET_INTEGER (L"Frame number"));
 		Melder_informationReal (time, L"seconds");
 	}
 END
@@ -6211,7 +6216,7 @@ FORM (Praat_test, L"Praat test", 0)
 	SENTENCE (L"arg4", L"")
 	OK
 DO
-	Praat_tests (GET_INTEGER (L"Test"), GET_STRING (L"arg1"),
+	Praat_tests (GET_ENUM (kPraatTests, L"Test"), GET_STRING (L"arg1"),
 		GET_STRING (L"arg2"), GET_STRING (L"arg3"), GET_STRING (L"arg4"));
 END
 
@@ -6262,8 +6267,17 @@ static Any chronologicalTextGridTextFileRecognizer (int nread, const char *heade
 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")) {
+	if (Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".jpg") ||
+	    Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".JPG") ||
+	    Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".jpeg") ||
+		Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".JPEG") ||
+	    Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".png") ||
+		Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".PNG") ||
+	    Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".tiff") ||
+		Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".TIFF") ||
+		Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".tif") ||
+		Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".TIF"))
+	{
 		return Photo_readFromImageFile (file);
 	}
 	return NULL;
@@ -6465,6 +6479,8 @@ praat_addAction1 (classDistributions, 0, L"Learn", 0, 0, 0);
 	praat_addAction1 (classDurationTier, 0, L"Query -", 0, 0, 0);
 		praat_TimeTier_query_init (classDurationTier);
 		praat_addAction1 (classDurationTier, 1, L"-- get content --", 0, 1, 0);
+		praat_addAction1 (classDurationTier, 1, L"Get value at time...", 0, 1, DO_DurationTier_getValueAtTime);
+		praat_addAction1 (classDurationTier, 1, L"Get value at index...", 0, 1, DO_DurationTier_getValueAtIndex);
 		praat_addAction1 (classDurationTier, 1, L"Get target duration...", 0, 1, DO_DurationTier_getTargetDuration);
 	praat_addAction1 (classDurationTier, 0, L"Modify -", 0, 0, 0);
 		praat_TimeTier_modify_init (classDurationTier);
@@ -6761,13 +6777,17 @@ praat_addAction1 (classMatrix, 0, L"Analyse", 0, 0, 0);
 		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);
+	#if defined (macintosh) || defined (_WIN32)
+		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 lossy JPEG file...", 0, 0, DO_Photo_saveAsJPEG);
+	#endif
+	#if defined (macintosh)
+		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);
+	#endif
 
 	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);
diff --git a/fon/praat_Sound_init.cpp b/fon/praat_Sound_init.cpp
index fb779ea..211991a 100644
--- a/fon/praat_Sound_init.cpp
+++ b/fon/praat_Sound_init.cpp
@@ -1,6 +1,6 @@
 /* praat_Sound_init.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -50,77 +50,77 @@ void praat_TimeFunction_modify_init (ClassInfo klas);
 
 /***** LONGSOUND *****/
 
-DIRECT (LongSound_concatenate)
+DIRECT2 (LongSound_concatenate) {
 	Melder_information (L"To concatenate LongSound objects, select them in the list\nand choose \"Save as WAV file...\" or a similar command.\n"
 		"The result will be a sound file that contains\nthe concatenation of the selected sounds.");
-END
+END2 }
 
-FORM (LongSound_extractPart, L"LongSound: Extract part", 0)
+FORM (LongSound_extractPart, L"LongSound: Extract part", 0) {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"1.0")
 	BOOLEAN (L"Preserve times", 1)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (LongSound);
 		autoSound thee = LongSound_extractPart (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Preserve times"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (LongSound_getIndexFromTime, L"LongSound: Get sample index from time", L"Sound: Get index from time...")
+FORM (LongSound_getIndexFromTime, L"LongSound: Get sample index from time", L"Sound: Get index from time...") {
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (LongSound);
-		double index = Sampled_xToIndex (me, GET_REAL (L"Time"));
+		double index = my f_xToIndex (GET_REAL (L"Time"));
 		Melder_informationReal (index, NULL);
 	}
-END
+END2 }
 
-DIRECT (LongSound_getSamplePeriod)
+DIRECT2 (LongSound_getSamplePeriod) {
 	LOOP {
 		iam (LongSound);
 		Melder_informationReal (my dx, L"seconds");
 	}
-END
+END2 }
 
-DIRECT (LongSound_getSampleRate)
+DIRECT2 (LongSound_getSampleRate) {
 	LOOP {
 		iam (LongSound);
 		Melder_informationReal (1.0 / my dx, L"Hz");
 	}
-END
+END2 }
 
-FORM (LongSound_getTimeFromIndex, L"LongSound: Get time from sample index", L"Sound: Get time from index...")
+FORM (LongSound_getTimeFromIndex, L"LongSound: Get time from sample index", L"Sound: Get time from index...") {
 	INTEGER (L"Sample index", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (LongSound);
-		Melder_informationReal (Sampled_indexToX (me, GET_INTEGER (L"Sample index")), L"seconds");
+		Melder_informationReal (my f_indexToX (GET_INTEGER (L"Sample index")), L"seconds");
 	}
-END
+END2 }
 
-DIRECT (LongSound_getNumberOfSamples)
+DIRECT2 (LongSound_getNumberOfSamples) {
 	LOOP {
 		iam (LongSound);
 		Melder_information (Melder_integer (my nx), L" samples");
 	}
-END
+END2 }
 
-DIRECT (LongSound_help) Melder_help (L"LongSound"); END
+DIRECT2 (LongSound_help) { Melder_help (L"LongSound"); END2 }
 
-FORM_READ (LongSound_open, L"Open long sound file", 0, true)
+FORM_READ2 (LongSound_open, L"Open long sound file", 0, true) {
 	autoLongSound me = LongSound_open (file);
 	praat_new (me.transfer(), MelderFile_name (file));
-END
+END2 }
 
-FORM (LongSound_playPart, L"LongSound: Play part", 0)
+FORM (LongSound_playPart, L"LongSound: Play part", 0) {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
-	OK
+	OK2
 DO
 	int n = 0;
 	LOOP n ++;
@@ -137,9 +137,9 @@ DO
 		}
 		MelderAudio_setOutputMaximumAsynchronicity (kMelder_asynchronicityLevel_ASYNCHRONOUS);
 	}
-END
+END2 }
 
-FORM (LongSound_writePartToAudioFile, L"LongSound: Save part as audio file", 0)
+FORM (LongSound_writePartToAudioFile, L"LongSound: Save part as audio file", 0) {
 	LABEL (L"", L"Audio file:")
 	TEXTFIELD (L"Audio file", L"")
 	RADIO (L"Type", 3)
@@ -148,7 +148,7 @@ FORM (LongSound_writePartToAudioFile, L"LongSound: Save part as audio file", 0)
 	}}
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"10.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (LongSound);
@@ -157,206 +157,207 @@ DO
 		LongSound_writePartToAudioFile (me, GET_INTEGER (L"Type"),
 			GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), & file);
 	}
-END
+END2 }
 	
-FORM (LongSound_to_TextGrid, L"LongSound: To TextGrid...", L"LongSound: To TextGrid...")
+FORM (LongSound_to_TextGrid, L"LongSound: To TextGrid...", L"LongSound: To TextGrid...") {
 	SENTENCE (L"Tier names", L"Mary John bell")
 	SENTENCE (L"Point tiers", L"bell")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (LongSound);
 		autoTextGrid thee = TextGrid_create (my xmin, my xmax, GET_STRING (L"Tier names"), GET_STRING (L"Point tiers"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (LongSound_view)
+DIRECT2 (LongSound_view) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot view or edit a LongSound from batch.");
 	LOOP {
 		iam (LongSound);
 		autoSoundEditor editor = SoundEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.transfer(), IOBJECT);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeToAifcFile, L"Save as AIFC file", 0, L"aifc")
+FORM_WRITE2 (LongSound_writeToAifcFile, L"Save as AIFC file", 0, L"aifc") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_AIFC, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_writeToAiffFile, L"Save as AIFF file", 0, L"aiff")
+FORM_WRITE2 (LongSound_writeToAiffFile, L"Save as AIFF file", 0, L"aiff") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_AIFF, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_writeToNextSunFile, L"Save as NeXT/Sun file", 0, L"au")
+FORM_WRITE2 (LongSound_writeToNextSunFile, L"Save as NeXT/Sun file", 0, L"au") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_NEXT_SUN, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_writeToNistFile, L"Save as NIST file", 0, L"nist")
+FORM_WRITE2 (LongSound_writeToNistFile, L"Save as NIST file", 0, L"nist") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_NIST, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_writeToFlacFile, L"Save as FLAC file", 0, L"flac")
+FORM_WRITE2 (LongSound_writeToFlacFile, L"Save as FLAC file", 0, L"flac") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_FLAC, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_writeToWavFile, L"Save as WAV file", 0, L"wav")
+FORM_WRITE2 (LongSound_writeToWavFile, L"Save as WAV file", 0, L"wav") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_WAV, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_writeLeftChannelToAifcFile, L"Save left channel as AIFC file", 0, L"aifc")
+FORM_WRITE2 (LongSound_writeLeftChannelToAifcFile, L"Save left channel as AIFC file", 0, L"aifc") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_AIFC, 0, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeLeftChannelToAiffFile, L"Save left channel as AIFF file", 0, L"aiff")
+FORM_WRITE2 (LongSound_writeLeftChannelToAiffFile, L"Save left channel as AIFF file", 0, L"aiff") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_AIFF, 0, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeLeftChannelToNextSunFile, L"Save left channel as NeXT/Sun file", 0, L"au")
+FORM_WRITE2 (LongSound_writeLeftChannelToNextSunFile, L"Save left channel as NeXT/Sun file", 0, L"au") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_NEXT_SUN, 0, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeLeftChannelToNistFile, L"Save left channel as NIST file", 0, L"nist")
+FORM_WRITE2 (LongSound_writeLeftChannelToNistFile, L"Save left channel as NIST file", 0, L"nist") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_NIST, 0, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeLeftChannelToFlacFile, L"Save left channel as FLAC file", 0, L"flac")
+FORM_WRITE2 (LongSound_writeLeftChannelToFlacFile, L"Save left channel as FLAC file", 0, L"flac") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_FLAC, 0, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeLeftChannelToWavFile, L"Save left channel as WAV file", 0, L"wav")
+FORM_WRITE2 (LongSound_writeLeftChannelToWavFile, L"Save left channel as WAV file", 0, L"wav") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_WAV, 0, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeRightChannelToAifcFile, L"Save right channel as AIFC file", 0, L"aifc")
+FORM_WRITE2 (LongSound_writeRightChannelToAifcFile, L"Save right channel as AIFC file", 0, L"aifc") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_AIFC, 1, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeRightChannelToAiffFile, L"Save right channel as AIFF file", 0, L"aiff")
+FORM_WRITE2 (LongSound_writeRightChannelToAiffFile, L"Save right channel as AIFF file", 0, L"aiff") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_AIFF, 1, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeRightChannelToNextSunFile, L"Save right channel as NeXT/Sun file", 0, L"au")
+FORM_WRITE2 (LongSound_writeRightChannelToNextSunFile, L"Save right channel as NeXT/Sun file", 0, L"au") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_NEXT_SUN, 1, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeRightChannelToNistFile, L"Save right channel as NIST file", 0, L"nist")
+FORM_WRITE2 (LongSound_writeRightChannelToNistFile, L"Save right channel as NIST file", 0, L"nist") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_NIST, 1, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeRightChannelToFlacFile, L"Save right channel as FLAC file", 0, L"flac")
+FORM_WRITE2 (LongSound_writeRightChannelToFlacFile, L"Save right channel as FLAC file", 0, L"flac") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_FLAC, 1, file);
 	}
-END
+END2 }
 
-FORM_WRITE (LongSound_writeRightChannelToWavFile, L"Save right channel as WAV file", 0, L"wav")
+FORM_WRITE2 (LongSound_writeRightChannelToWavFile, L"Save right channel as WAV file", 0, L"wav") {
 	LOOP {
 		iam (LongSound);
 		LongSound_writeChannelToAudioFile (me, Melder_WAV, 1, file);
 	}
-END
+END2 }
 
-FORM (LongSoundPrefs, L"LongSound preferences", L"LongSound")
+FORM (LongSoundPrefs, L"LongSound preferences", L"LongSound") {
 	LABEL (L"", L"This setting determines the maximum number of seconds")
 	LABEL (L"", L"for viewing the waveform and playing a sound in the LongSound window.")
 	LABEL (L"", L"The LongSound window can become very slow if you set it too high.")
 	NATURAL (L"Maximum viewable part (seconds)", L"60")
 	LABEL (L"", L"Note: this setting works for the next long sound file that you open,")
 	LABEL (L"", L"not for currently existing LongSound objects.")
-	OK
+	OK2
 SET_INTEGER (L"Maximum viewable part", LongSound_getBufferSizePref_seconds ())
 DO
 	LongSound_setBufferSizePref_seconds (GET_INTEGER (L"Maximum viewable part"));
-END
+END2 }
 
 /********** LONGSOUND & SOUND **********/
 
-FORM_WRITE (LongSound_Sound_writeToAifcFile, L"Save as AIFC file", 0, L"aifc")
+FORM_WRITE2 (LongSound_Sound_writeToAifcFile, L"Save as AIFC file", 0, L"aifc") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_AIFC, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_Sound_writeToAiffFile, L"Save as AIFF file", 0, L"aiff")
+FORM_WRITE2 (LongSound_Sound_writeToAiffFile, L"Save as AIFF file", 0, L"aiff") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_AIFF, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_Sound_writeToNextSunFile, L"Save as NeXT/Sun file", 0, L"au")
+FORM_WRITE2 (LongSound_Sound_writeToNextSunFile, L"Save as NeXT/Sun file", 0, L"au") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_NEXT_SUN, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_Sound_writeToNistFile, L"Save as NIST file", 0, L"nist")
+FORM_WRITE2 (LongSound_Sound_writeToNistFile, L"Save as NIST file", 0, L"nist") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_NIST, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_Sound_writeToFlacFile, L"Save as FLAC file", 0, L"flac")
+FORM_WRITE2 (LongSound_Sound_writeToFlacFile, L"Save as FLAC file", 0, L"flac") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_FLAC, 16);
-END
+END2 }
 
-FORM_WRITE (LongSound_Sound_writeToWavFile, L"Save as WAV file", 0, L"wav")
+FORM_WRITE2 (LongSound_Sound_writeToWavFile, L"Save as WAV file", 0, L"wav") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_WAV, 16);
-END
+END2 }
 
 /********** SOUND **********/
 
-FORM (Sound_add, L"Sound: Add", 0)
-	LABEL (L"", L"The following number will be added to the amplitudes of all samples of the sound.")
+FORM (Sound_add, L"Sound: Add", 0) {
+	LABEL (L"", L"The following number will be added to the amplitudes of ")
+	LABEL (L"", L"all samples of the sound.")
 	REAL (L"Number", L"0.1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Vector_addScalar (me, GET_REAL (L"Number"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_autoCorrelate, L"Sound: autocorrelate", L"Sound: Autocorrelate...")
+FORM (Sound_autoCorrelate, L"Sound: autocorrelate", L"Sound: Autocorrelate...") {
 	RADIO_ENUM (L"Amplitude scaling", kSounds_convolve_scaling, DEFAULT)
 	RADIO_ENUM (L"Signal outside time domain is...", kSounds_convolve_signalOutsideTimeDomain, DEFAULT)
- 	OK
+ 	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -365,31 +366,31 @@ DO
 			GET_ENUM (kSounds_convolve_signalOutsideTimeDomain, L"Signal outside time domain is..."));
 		praat_new (thee.transfer(), L"ac_", my name);
 	}
-END
+END2 }
 
-DIRECT (Sounds_combineToStereo)
+DIRECT2 (Sounds_combineToStereo) {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound result = Sounds_combineToStereo (set.peek());
 	long numberOfChannels = result -> ny;   // dereference before transferring
 	praat_new (result.transfer(), L"combined_", Melder_integer (numberOfChannels));
-END
+END2 }
 
-DIRECT (Sounds_concatenate)
+DIRECT2 (Sounds_concatenate) {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound result = Sounds_concatenate_e (set.peek(), 0.0);
 	praat_new (result.transfer(), L"chain");
-END
+END2 }
 
-FORM (Sounds_concatenateWithOverlap, L"Sounds: Concatenate with overlap", L"Sounds: Concatenate with overlap...")
+FORM (Sounds_concatenateWithOverlap, L"Sounds: Concatenate with overlap", L"Sounds: Concatenate with overlap...") {
 	POSITIVE (L"Overlap (s)", L"0.01")
-	OK
+	OK2
 DO
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound result = Sounds_concatenate_e (set.peek(), GET_REAL (L"Overlap"));
 	praat_new (result.transfer(), L"chain");
-END
+END2 }
 
-DIRECT (Sounds_concatenateRecoverably)
+DIRECT2 (Sounds_concatenateRecoverably) {
 	long numberOfChannels = 0, nx = 0, iinterval = 0;
 	double dx = 0.0, tmin = 0.0;
 	LOOP {
@@ -426,25 +427,25 @@ DIRECT (Sounds_concatenateRecoverably)
 	}
 	praat_new (thee.transfer(), L"chain");
 	praat_new (him.transfer(), L"chain");
-END
+END2 }
 
-DIRECT (Sound_convertToMono)
+DIRECT2 (Sound_convertToMono) {
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_convertToMono (me);
 		praat_new (thee.transfer(), my name, L"_mono");
 	}
-END
+END2 }
 
-DIRECT (Sound_convertToStereo)
+DIRECT2 (Sound_convertToStereo) {
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_convertToStereo (me);
 		praat_new (thee.transfer(), my name, L"_stereo");
 	}
-END
+END2 }
 
-DIRECT (Sounds_convolve_old)
+DIRECT2 (Sounds_convolve_old) {
 	Sound s1 = NULL, s2 = NULL;
 	LOOP {
 		iam (Sound);
@@ -453,12 +454,12 @@ DIRECT (Sounds_convolve_old)
 	Melder_assert (s1 != NULL && s2 != NULL);
 	autoSound thee = Sounds_convolve (s1, s2, kSounds_convolve_scaling_SUM, kSounds_convolve_signalOutsideTimeDomain_ZERO);
 	praat_new (thee.transfer(), s1 -> name, L"_", s2 -> name);
-END
+END2 }
 
-FORM (Sounds_convolve, L"Sounds: Convolve", L"Sounds: Convolve...")
+FORM (Sounds_convolve, L"Sounds: Convolve", L"Sounds: Convolve...") {
 	RADIO_ENUM (L"Amplitude scaling", kSounds_convolve_scaling, DEFAULT)
 	RADIO_ENUM (L"Signal outside time domain is...", kSounds_convolve_signalOutsideTimeDomain, DEFAULT)
-	OK
+	OK2
 DO
 	Sound s1 = NULL, s2 = NULL;
 	LOOP {
@@ -470,7 +471,7 @@ DO
 		GET_ENUM (kSounds_convolve_scaling, L"Amplitude scaling"),
 		GET_ENUM (kSounds_convolve_signalOutsideTimeDomain, L"Signal outside time domain is..."));
 	praat_new (thee.transfer(), s1 -> name, L"_", s2 -> name);
-END
+END2 }
 
 static void common_Sound_create (void *dia, Interpreter interpreter, bool allowMultipleChannels) {
 	long numberOfChannels = allowMultipleChannels ? GET_INTEGER (L"Number of channels") : 1;
@@ -532,19 +533,19 @@ static void common_Sound_create (void *dia, Interpreter interpreter, bool allowM
 	//praat_updateSelection ();
 }
 
-FORM (Sound_create, L"Create mono Sound", L"Create Sound from formula...")
+FORM (Sound_create, L"Create mono Sound", L"Create Sound from formula...") {
 	WORD (L"Name", L"sineWithNoise")
 	REAL (L"Start time (s)", L"0.0")
 	REAL (L"End time (s)", L"1.0")
 	REAL (L"Sampling frequency (Hz)", L"44100")
 	LABEL (L"", L"Formula:")
 	TEXTFIELD (L"formula", L"1/2 * sin(2*pi*377*x) + randomGauss(0,0.1)")
-	OK
+	OK2
 DO
 	common_Sound_create (dia, interpreter, false);
-END
+END2 }
 
-FORM (Sound_createFromFormula, L"Create Sound from formula", L"Create Sound from formula...")
+FORM (Sound_createFromFormula, L"Create Sound from formula", L"Create Sound from formula...") {
 	WORD (L"Name", L"sineWithNoise")
 	CHANNEL (L"Number of channels", L"1 (= mono)")
 	REAL (L"Start time (s)", L"0.0")
@@ -552,12 +553,12 @@ FORM (Sound_createFromFormula, L"Create Sound from formula", L"Create Sound from
 	REAL (L"Sampling frequency (Hz)", L"44100")
 	LABEL (L"", L"Formula:")
 	TEXTFIELD (L"formula", L"1/2 * sin(2*pi*377*x) + randomGauss(0,0.1)")
-	OK
+	OK2
 DO
 	common_Sound_create (dia, interpreter, true);
-END
+END2 }
 
-FORM (Sound_createAsPureTone, L"Create Sound as pure tone", L"Create Sound as pure tone...")
+FORM (Sound_createAsPureTone, L"Create Sound as pure tone", L"Create Sound as pure tone...") {
 	WORD (L"Name", L"tone")
 	CHANNEL (L"Number of channels", L"1 (= mono)")
 	REAL (L"Start time (s)", L"0.0")
@@ -567,15 +568,15 @@ FORM (Sound_createAsPureTone, L"Create Sound as pure tone", L"Create Sound as pu
 	POSITIVE (L"Amplitude (Pa)", L"0.2")
 	POSITIVE (L"Fade-in duration (s)", L"0.01")
 	POSITIVE (L"Fade-out duration (s)", L"0.01")
-	OK
+	OK2
 DO
 	autoSound me = Sound_createAsPureTone (GET_INTEGER (L"Number of channels"), GET_REAL (L"Start time"), GET_REAL (L"End time"),
 		GET_REAL (L"Sampling frequency"), GET_REAL (L"Tone frequency"), GET_REAL (L"Amplitude"),
 		GET_REAL (L"Fade-in duration"), GET_REAL (L"Fade-out duration"));
 	praat_new (me.transfer(), GET_STRING (L"Name"));
-END
+END2 }
 
-FORM (Sound_createFromToneComplex, L"Create Sound from tone complex", L"Create Sound from tone complex...")
+FORM (Sound_createFromToneComplex, L"Create Sound from tone complex", L"Create Sound from tone complex...") {
 	WORD (L"Name", L"toneComplex")
 	REAL (L"Start time (s)", L"0.0")
 	REAL (L"End time (s)", L"1.0")
@@ -587,19 +588,19 @@ FORM (Sound_createFromToneComplex, L"Create Sound from tone complex", L"Create S
 	REAL (L"First frequency (Hz)", L"0 (= frequency step)")
 	REAL (L"Ceiling (Hz)", L"0 (= Nyquist)")
 	INTEGER (L"Number of components", L"0 (= maximum)")
-	OK
+	OK2
 DO
 	autoSound me = Sound_createFromToneComplex (GET_REAL (L"Start time"), GET_REAL (L"End time"),
 		GET_REAL (L"Sampling frequency"), GET_INTEGER (L"Phase") - 1, GET_REAL (L"Frequency step"),
 		GET_REAL (L"First frequency"), GET_REAL (L"Ceiling"), GET_INTEGER (L"Number of components"));
 	praat_new (me.transfer(), GET_STRING (L"Name"));
-END
+END2 }
 
-FORM (old_Sounds_crossCorrelate, L"Cross-correlate (short)", 0)
+FORM (old_Sounds_crossCorrelate, L"Cross-correlate (short)", 0) {
 	REAL (L"From lag (s)", L"-0.1")
 	REAL (L"To lag (s)", L"0.1")
 	BOOLEAN (L"Normalize", 1)
-	OK
+	OK2
 DO
 	Sound s1 = NULL, s2 = NULL;
 	LOOP {
@@ -608,12 +609,12 @@ DO
 	}
 	autoSound thee = Sounds_crossCorrelate_short (s1, s2, GET_REAL (L"From lag"), GET_REAL (L"To lag"), GET_INTEGER (L"Normalize"));
 	praat_new (thee.transfer(), L"cc_", s1 -> name, L"_", s2 -> name);
-END
+END2 }
 
-FORM (Sounds_crossCorrelate, L"Sounds: Cross-correlate", L"Sounds: Cross-correlate...")
+FORM (Sounds_crossCorrelate, L"Sounds: Cross-correlate", L"Sounds: Cross-correlate...") {
 	RADIO_ENUM (L"Amplitude scaling", kSounds_convolve_scaling, DEFAULT)
 	RADIO_ENUM (L"Signal outside time domain is...", kSounds_convolve_signalOutsideTimeDomain, DEFAULT)
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sounds_crossCorrelate)
 	Sound s1 = NULL, s2 = NULL;
 	LOOP {
@@ -625,11 +626,11 @@ DO_ALTERNATIVE (old_Sounds_crossCorrelate)
 		GET_ENUM (kSounds_convolve_scaling, L"Amplitude scaling"),
 		GET_ENUM (kSounds_convolve_signalOutsideTimeDomain, L"Signal outside time domain is..."));
 	praat_new (thee.transfer(), s1 -> name, L"_", s2 -> name);
-END
+END2 }
 
-FORM (Sound_deemphasizeInline, L"Sound: De-emphasize (in-line)", L"Sound: De-emphasize (in-line)...")
+FORM (Sound_deemphasizeInline, L"Sound: De-emphasize (in-line)", L"Sound: De-emphasize (in-line)...") {
 	REAL (L"From frequency (Hz)", L"50.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -637,16 +638,16 @@ DO
 		Vector_scale (me, 0.99);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_deepenBandModulation, L"Deepen band modulation", L"Sound: Deepen band modulation...")
+FORM (Sound_deepenBandModulation, L"Deepen band modulation", L"Sound: Deepen band modulation...") {
 	POSITIVE (L"Enhancement (dB)", L"20")
 	POSITIVE (L"From frequency (Hz)", L"300")
 	POSITIVE (L"To frequency (Hz)", L"8000")
 	POSITIVE (L"Slow modulation (Hz)", L"3")
 	POSITIVE (L"Fast modulation (Hz)", L"30")
 	POSITIVE (L"Band smoothing (Hz)", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -655,15 +656,15 @@ DO
 			GET_REAL (L"Slow modulation"), GET_REAL (L"Fast modulation"), GET_REAL (L"Band smoothing"));
 		praat_new (thee.transfer(), my name, L"_", Melder_integer ((long) GET_REAL (L"Enhancement")));
 	}
-END
+END2 }
 
-FORM (old_Sound_draw, L"Sound: Draw", 0)
+FORM (old_Sound_draw, L"Sound: Draw", 0) {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range", L"0.0 (= all)")
 	REAL (L"left Vertical range", L"0.0")
 	REAL (L"right Vertical range", L"0.0 (= auto)")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -671,9 +672,9 @@ DO
 		Sound_draw (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
 			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"), GET_INTEGER (L"Garnish"), L"curve");
 	}
-END
+END2 }
 
-FORM (Sound_draw, L"Sound: Draw", 0)
+FORM (Sound_draw, L"Sound: Draw", 0) {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range", L"0.0 (= all)")
 	REAL (L"left Vertical range", L"0.0")
@@ -685,7 +686,7 @@ FORM (Sound_draw, L"Sound: Draw", 0)
 		OPTION (L"Bars")
 		OPTION (L"Poles")
 		OPTION (L"Speckles")
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_draw)
 	autoPraatPicture picture;
 	LOOP {
@@ -693,7 +694,7 @@ DO_ALTERNATIVE (old_Sound_draw)
 		Sound_draw (me, GRAPHICS, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"),
 			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"), GET_INTEGER (L"Garnish"), GET_STRING (L"Drawing method"));
 	}
-END
+END2 }
 
 static void cb_SoundEditor_publication (Editor editor, void *closure, Data publication) {
 	(void) editor;
@@ -716,7 +717,7 @@ static void cb_SoundEditor_publication (Editor editor, void *closure, Data publi
 		Melder_flushError (NULL);
 	}
 }
-DIRECT (Sound_edit)
+DIRECT2 (Sound_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot view or edit a Sound from batch.");
 	LOOP {
 		iam (Sound);
@@ -724,9 +725,9 @@ DIRECT (Sound_edit)
 		editor -> setPublicationCallback (cb_SoundEditor_publication, NULL);
 		praat_installEditor (editor.transfer(), IOBJECT);
 	}
-END
+END2 }
 
-DIRECT (Sound_extractAllChannels)
+DIRECT2 (Sound_extractAllChannels) {
 	LOOP {
 		iam (Sound);
 		for (long channel = 1; channel <= my ny; channel ++) {
@@ -734,11 +735,11 @@ DIRECT (Sound_extractAllChannels)
 			praat_new (thee.transfer(), my name, L"_ch", Melder_integer (channel));
 		}
 	}
-END
+END2 }
 
-FORM (Sound_extractChannel, L"Sound: Extract channel", 0)
+FORM (Sound_extractChannel, L"Sound: Extract channel", 0) {
 	CHANNEL (L"Channel (number, Left, or Right)", L"1")
-	OK
+	OK2
 DO
 	long channel = GET_INTEGER (L"Channel");
 	LOOP {
@@ -746,23 +747,23 @@ DO
 		autoSound thee = Sound_extractChannel (me, channel);
 		praat_new (thee.transfer(), my name, L"_ch", Melder_integer (channel));
 	}
-END
+END2 }
 
-DIRECT (Sound_extractLeftChannel)
+DIRECT2 (Sound_extractLeftChannel) {
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_extractChannel (me, 1);
 		praat_new (thee.transfer(), my name, L"_left");
 	}
-END
+END2 }
 
-FORM (Sound_extractPart, L"Sound: Extract part", 0)
+FORM (Sound_extractPart, L"Sound: Extract part", 0) {
 	REAL (L"left Time range (s)", L"0")
 	REAL (L"right Time range (s)", L"0.1")
 	OPTIONMENU_ENUM (L"Window shape", kSound_windowShape, DEFAULT)
 	POSITIVE (L"Relative width", L"1.0")
 	BOOLEAN (L"Preserve times", 0)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -772,13 +773,13 @@ DO
 			GET_INTEGER (L"Preserve times"));
 		praat_new (thee.transfer(), my name, L"_part");
 	}
-END
+END2 }
 
-FORM (Sound_extractPartForOverlap, L"Sound: Extract part for overlap", 0)
+FORM (Sound_extractPartForOverlap, L"Sound: Extract part for overlap", 0) {
 	REAL (L"left Time range (s)", L"0")
 	REAL (L"right Time range (s)", L"0.1")
 	POSITIVE (L"Overlap (s)", L"0.01")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -787,68 +788,68 @@ DO
 			GET_REAL (L"Overlap"));
 		praat_new (thee.transfer(), my name, L"_part");
 	}
-END
+END2 }
 
-DIRECT (Sound_extractRightChannel)
+DIRECT2 (Sound_extractRightChannel) {
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_extractChannel (me, 2);
 		praat_new (thee.transfer(), my name, L"_right");
 	}
-END
+END2 }
 
-FORM (Sound_filter_deemphasis, L"Sound: Filter (de-emphasis)", L"Sound: Filter (de-emphasis)...")
+FORM (Sound_filter_deemphasis, L"Sound: Filter (de-emphasis)", L"Sound: Filter (de-emphasis)...") {
 	REAL (L"From frequency (Hz)", L"50.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_filter_deemphasis (me, GET_REAL (L"From frequency"));
 		praat_new (thee.transfer(), my name, L"_deemp");
 	}
-END
+END2 }
 
-FORM (Sound_filter_formula, L"Sound: Filter (formula)...", L"Formula...")
+FORM (Sound_filter_formula, L"Sound: Filter (formula)...", L"Formula...") {
 	LABEL (L"", L"Frequency-domain filtering with a formula (uses Sound-to-Spectrum and Spectrum-to-Sound): x is frequency in hertz")
 	TEXTFIELD (L"formula", L"if x<500 or x>1000 then 0 else self fi; rectangular band filter")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_filter_formula (me, GET_STRING (L"formula"), interpreter);
 		praat_new (thee.transfer(), my name, L"_filt");
 	}
-END
+END2 }
 
-FORM (Sound_filter_oneFormant, L"Sound: Filter (one formant)", L"Sound: Filter (one formant)...")
+FORM (Sound_filter_oneFormant, L"Sound: Filter (one formant)", L"Sound: Filter (one formant)...") {
 	REAL (L"Frequency (Hz)", L"1000")
 	POSITIVE (L"Bandwidth (Hz)", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_filter_oneFormant (me, GET_REAL (L"Frequency"), GET_REAL (L"Bandwidth"));
 		praat_new (thee.transfer(), my name, L"_filt");
 	}
-END
+END2 }
 
-FORM (Sound_filterWithOneFormantInline, L"Sound: Filter with one formant (in-line)", L"Sound: Filter with one formant (in-line)...")
+FORM (Sound_filterWithOneFormantInline, L"Sound: Filter with one formant (in-line)", L"Sound: Filter with one formant (in-line)...") {
 	REAL (L"Frequency (Hz)", L"1000")
 	POSITIVE (L"Bandwidth (Hz)", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Sound_filterWithOneFormantInline (me, GET_REAL (L"Frequency"), GET_REAL (L"Bandwidth"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_filter_passHannBand, L"Sound: Filter (pass Hann band)", L"Sound: Filter (pass Hann band)...")
+FORM (Sound_filter_passHannBand, L"Sound: Filter (pass Hann band)", L"Sound: Filter (pass Hann band)...") {
 	REAL (L"From frequency (Hz)", L"500")
 	REAL (L"To frequency (Hz)", L"1000")
 	POSITIVE (L"Smoothing (Hz)", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -856,33 +857,33 @@ DO
 			GET_REAL (L"From frequency"), GET_REAL (L"To frequency"), GET_REAL (L"Smoothing"));
 		praat_new (thee.transfer(), my name, L"_band");
 	}
-END
+END2 }
 
-FORM (Sound_filter_preemphasis, L"Sound: Filter (pre-emphasis)", L"Sound: Filter (pre-emphasis)...")
+FORM (Sound_filter_preemphasis, L"Sound: Filter (pre-emphasis)", L"Sound: Filter (pre-emphasis)...") {
 	REAL (L"From frequency (Hz)", L"50.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_filter_preemphasis (me, GET_REAL (L"From frequency"));
 		praat_new (thee.transfer(), my name, L"_preemp");
 	}
-END
+END2 }
 
-FORM (Sound_filter_stopHannBand, L"Sound: Filter (stop Hann band)", L"Sound: Filter (stop Hann band)...")
+FORM (Sound_filter_stopHannBand, L"Sound: Filter (stop Hann band)", L"Sound: Filter (stop Hann band)...") {
 	REAL (L"From frequency (Hz)", L"500")
 	REAL (L"To frequency (Hz)", L"1000")
 	POSITIVE (L"Smoothing (Hz)", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoSound thee = Sound_filter_stopHannBand (me, GET_REAL (L"From frequency"), GET_REAL (L"To frequency"), GET_REAL (L"Smoothing"));
 		praat_new (thee.transfer(), my name, L"_band");
 	}
-END
+END2 }
 
-FORM (Sound_formula, L"Sound: Formula", L"Sound: Formula...")
+FORM (Sound_formula, L"Sound: Formula", L"Sound: Formula...") {
 	LABEL (L"label1", L"! `x' is the time in seconds, `col' is the sample number.")
 	LABEL (L"label2", L"x = x1   ! time associated with first sample")
 	LABEL (L"label3", L"for col from 1 to ncol")
@@ -890,7 +891,7 @@ FORM (Sound_formula, L"Sound: Formula", L"Sound: Formula...")
 	TEXTFIELD (L"formula", L"self")
 	LABEL (L"label5", L"   x = x + dx")
 	LABEL (L"label6", L"endfor")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -902,15 +903,15 @@ DO
 			throw;
 		}
 	}
-END
+END2 }
 
-FORM (Sound_formula_part, L"Sound: Formula (part)", L"Sound: Formula...")
+FORM (Sound_formula_part, L"Sound: Formula (part)", L"Sound: Formula...") {
 	REAL (L"From time", L"0.0")
 	REAL (L"To time", L"0.0 (= all)")
 	NATURAL (L"From channel", L"1")
 	NATURAL (L"To channel", L"2")
 	TEXTFIELD (L"formula", L"2 * self")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -925,9 +926,9 @@ DO
 			throw;
 		}
 	}
-END
+END2 }
 
-FORM (Sound_getAbsoluteExtremum, L"Sound: Get absolute extremum", L"Sound: Get absolute extremum...")
+FORM (Sound_getAbsoluteExtremum, L"Sound: Get absolute extremum", L"Sound: Get absolute extremum...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
 	RADIO (L"Interpolation", 4)
@@ -936,7 +937,7 @@ FORM (Sound_getAbsoluteExtremum, L"Sound: Get absolute extremum", L"Sound: Get a
 		RADIOBUTTON (L"Cubic")
 		RADIOBUTTON (L"Sinc70")
 		RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -944,48 +945,48 @@ DO
 			GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Interpolation") - 1);
 		Melder_informationReal (absoluteExtremum, L"Pascal");
 	}
-END
+END2 }
 
-FORM (Sound_getEnergy, L"Sound: Get energy", L"Sound: Get energy...")
+FORM (Sound_getEnergy, L"Sound: Get energy", L"Sound: Get energy...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double energy = Sound_getEnergy (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"));
 		Melder_informationReal (energy, L"Pa2 sec");
 	}
-END
+END2 }
 
-DIRECT (Sound_getEnergyInAir)
+DIRECT2 (Sound_getEnergyInAir) {
 	LOOP {
 		iam (Sound);
 		double energyInAir = Sound_getEnergyInAir (me);
 		Melder_informationReal (energyInAir, L"Joule/m2");
 	}
-END
+END2 }
 
-FORM (Sound_getIndexFromTime, L"Get sample number from time", L"Get sample number from time...")
+FORM (Sound_getIndexFromTime, L"Get sample number from time", L"Get sample number from time...") {
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
-		double realIndex = Sampled_xToIndex (me, GET_REAL (L"Time"));
+		double realIndex = my f_xToIndex (GET_REAL (L"Time"));
 		Melder_informationReal (realIndex, NULL);
 	}
-END
+END2 }
 
-DIRECT (Sound_getIntensity_dB)
+DIRECT2 (Sound_getIntensity_dB) {
 	LOOP {
 		iam (Sound);
 		double intensity = Sound_getIntensity_dB (me);
 		Melder_informationReal (intensity, L"dB");
 	}
-END
+END2 }
 
-FORM (Sound_getMaximum, L"Sound: Get maximum", L"Sound: Get maximum...")
+FORM (Sound_getMaximum, L"Sound: Get maximum", L"Sound: Get maximum...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
 	RADIO (L"Interpolation", 4)
@@ -994,32 +995,32 @@ FORM (Sound_getMaximum, L"Sound: Get maximum", L"Sound: Get maximum...")
 	RADIOBUTTON (L"Cubic")
 	RADIOBUTTON (L"Sinc70")
 	RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double maximum = Vector_getMaximum (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Interpolation") - 1);
 		Melder_informationReal (maximum, L"Pascal");
 	}
-END
+END2 }
 
-FORM (old_Sound_getMean, L"Sound: Get mean", L"Sound: Get mean...")
+FORM (old_Sound_getMean, L"Sound: Get mean", L"Sound: Get mean...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double mean = Vector_getMean (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), Vector_CHANNEL_AVERAGE);
 		Melder_informationReal (mean, L"Pascal");
 	}
-END
+END2 }
 
-FORM (Sound_getMean, L"Sound: Get mean", L"Sound: Get mean...")
+FORM (Sound_getMean, L"Sound: Get mean", L"Sound: Get mean...") {
 	CHANNEL (L"Channel", L"0 (= all)")
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_getMean)
 	LOOP {
 		iam (Sound);
@@ -1028,9 +1029,9 @@ DO_ALTERNATIVE (old_Sound_getMean)
 		double mean = Vector_getMean (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), channel);
 		Melder_informationReal (mean, L"Pascal");
 	}
-END
+END2 }
 
-FORM (Sound_getMinimum, L"Sound: Get minimum", L"Sound: Get minimum...")
+FORM (Sound_getMinimum, L"Sound: Get minimum", L"Sound: Get minimum...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
 	RADIO (L"Interpolation", 4)
@@ -1039,18 +1040,18 @@ FORM (Sound_getMinimum, L"Sound: Get minimum", L"Sound: Get minimum...")
 	RADIOBUTTON (L"Cubic")
 	RADIOBUTTON (L"Sinc70")
 	RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double minimum = Vector_getMinimum (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Interpolation") - 1);
 		Melder_informationReal (minimum, L"Pascal");
 	}
-END
+END2 }
 
-FORM (old_Sound_getNearestZeroCrossing, L"Sound: Get nearest zero crossing", L"Sound: Get nearest zero crossing...")
+FORM (old_Sound_getNearestZeroCrossing, L"Sound: Get nearest zero crossing", L"Sound: Get nearest zero crossing...") {
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1058,12 +1059,12 @@ DO
 		double zeroCrossing = Sound_getNearestZeroCrossing (me, GET_REAL (L"Time"), 1);
 		Melder_informationReal (zeroCrossing, L"seconds");
 	}
-END
+END2 }
 
-FORM (Sound_getNearestZeroCrossing, L"Sound: Get nearest zero crossing", L"Sound: Get nearest zero crossing...")
+FORM (Sound_getNearestZeroCrossing, L"Sound: Get nearest zero crossing", L"Sound: Get nearest zero crossing...") {
 	CHANNEL (L"Channel (number, Left, or Right)", L"1")
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_getNearestZeroCrossing)
 	LOOP {
 		iam (Sound);
@@ -1072,89 +1073,89 @@ DO_ALTERNATIVE (old_Sound_getNearestZeroCrossing)
 		double zeroCrossing = Sound_getNearestZeroCrossing (me, GET_REAL (L"Time"), channel);
 		Melder_informationReal (zeroCrossing, L"seconds");
 	}
-END
+END2 }
 
-DIRECT (Sound_getNumberOfChannels)
+DIRECT2 (Sound_getNumberOfChannels) {
 	LOOP {
 		iam (Sound);
 		long numberOfChannels = my ny;
 		Melder_information (Melder_integer (numberOfChannels), numberOfChannels == 1 ? L" channel (mono)" : numberOfChannels == 2 ? L" channels (stereo)" : L"channels");
 	}
-END
+END2 }
 
-DIRECT (Sound_getNumberOfSamples)
+DIRECT2 (Sound_getNumberOfSamples) {
 	LOOP {
 		iam (Sound);
 		long numberOfSamples = my nx;
 		Melder_information (Melder_integer (numberOfSamples), L" samples");
 	}
-END
+END2 }
 
-FORM (Sound_getPower, L"Sound: Get power", L"Sound: Get power...")
+FORM (Sound_getPower, L"Sound: Get power", L"Sound: Get power...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double power = Sound_getPower (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"));
 		Melder_informationReal (power, L"Pa2");
 	}
-END
+END2 }
 
-DIRECT (Sound_getPowerInAir)
+DIRECT2 (Sound_getPowerInAir) {
 	LOOP {
 		iam (Sound);
 		double powerInAir = Sound_getPowerInAir (me);
 		Melder_informationReal (powerInAir, L"Watt/m2");
 	}
-END
+END2 }
 
-FORM (Sound_getRootMeanSquare, L"Sound: Get root-mean-square", L"Sound: Get root-mean-square...")
+FORM (Sound_getRootMeanSquare, L"Sound: Get root-mean-square", L"Sound: Get root-mean-square...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double rootMeanSquare = Sound_getRootMeanSquare (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"));
 		Melder_informationReal (rootMeanSquare, L"Pascal");
 	}
-END
+END2 }
 
-DIRECT (Sound_getSamplePeriod)
+DIRECT2 (Sound_getSamplePeriod) {
 	LOOP {
 		iam (Sound);
 		double samplePeriod = my dx;
 		Melder_informationReal (samplePeriod, L"seconds");
 	}
-END
+END2 }
 
-DIRECT (Sound_getSampleRate)
+DIRECT2 (Sound_getSampleRate) {
 	LOOP {
 		iam (Sound);
 		double samplingFrequency = 1.0 / my dx;
 		Melder_informationReal (samplingFrequency, L"Hz");
 	}
-END
+END2 }
 
-FORM (old_Sound_getStandardDeviation, L"Sound: Get standard deviation", L"Sound: Get standard deviation...")
+FORM (old_Sound_getStandardDeviation, L"Sound: Get standard deviation", L"Sound: Get standard deviation...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double stdev = Vector_getStandardDeviation (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), Vector_CHANNEL_AVERAGE);
 		Melder_informationReal (stdev, L"Pascal");
 	}
-END
+END2 }
 
-FORM (Sound_getStandardDeviation, L"Sound: Get standard deviation", L"Sound: Get standard deviation...")
+FORM (Sound_getStandardDeviation, L"Sound: Get standard deviation", L"Sound: Get standard deviation...") {
 	CHANNEL (L"Channel", L"0 (= average)")
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_getStandardDeviation)
 	LOOP {
 		iam (Sound);
@@ -1163,20 +1164,20 @@ DO_ALTERNATIVE (old_Sound_getStandardDeviation)
 		double stdev = Vector_getStandardDeviation (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), channel);
 		Melder_informationReal (stdev, L"Pascal");
 	}
-END
+END2 }
 
-FORM (Sound_getTimeFromIndex, L"Get time from sample number", L"Get time from sample number...")
+FORM (Sound_getTimeFromIndex, L"Get time from sample number", L"Get time from sample number...") {
 	INTEGER (L"Sample number", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
-		double time = Sampled_indexToX (me, GET_INTEGER (L"Sample number"));
+		double time = my f_indexToX (GET_INTEGER (L"Sample number"));
 		Melder_informationReal (time, L"seconds");
 	}
-END
+END2 }
 
-FORM (Sound_getTimeOfMaximum, L"Sound: Get time of maximum", L"Sound: Get time of maximum...")
+FORM (Sound_getTimeOfMaximum, L"Sound: Get time of maximum", L"Sound: Get time of maximum...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
 	RADIO (L"Interpolation", 4)
@@ -1185,16 +1186,16 @@ FORM (Sound_getTimeOfMaximum, L"Sound: Get time of maximum", L"Sound: Get time o
 		RADIOBUTTON (L"Cubic")
 		RADIOBUTTON (L"Sinc70")
 		RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double time = Vector_getXOfMaximum (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Interpolation") - 1);
 		Melder_informationReal (time, L"seconds");
 	}
-END
+END2 }
 
-FORM (Sound_getTimeOfMinimum, L"Sound: Get time of minimum", L"Sound: Get time of minimum...")
+FORM (Sound_getTimeOfMinimum, L"Sound: Get time of minimum", L"Sound: Get time of minimum...") {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
 	RADIO (L"Interpolation", 4)
@@ -1203,18 +1204,18 @@ FORM (Sound_getTimeOfMinimum, L"Sound: Get time of minimum", L"Sound: Get time o
 	RADIOBUTTON (L"Cubic")
 	RADIOBUTTON (L"Sinc70")
 	RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double time = Vector_getXOfMinimum (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Interpolation") - 1);
 		Melder_informationReal (time, L"seconds");
 	}
-END
+END2 }
 
-FORM (old_Sound_getValueAtIndex, L"Sound: Get value at sample number", L"Sound: Get value at sample number...")
+FORM (old_Sound_getValueAtIndex, L"Sound: Get value at sample number", L"Sound: Get value at sample number...") {
 	INTEGER (L"Sample number", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1222,12 +1223,12 @@ DO
 		Melder_informationReal (sampleIndex < 1 || sampleIndex > my nx ? NUMundefined :
 			my ny == 1 ? my z [1] [sampleIndex] : 0.5 * (my z [1] [sampleIndex] + my z [2] [sampleIndex]), L"Pascal");
 	}
-END
+END2 }
 
-FORM (Sound_getValueAtIndex, L"Sound: Get value at sample number", L"Sound: Get value at sample number...")
+FORM (Sound_getValueAtIndex, L"Sound: Get value at sample number", L"Sound: Get value at sample number...") {
 	CHANNEL (L"Channel", L"0 (= average)")
 	INTEGER (L"Sample number", L"100")
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_getValueAtIndex)
 	LOOP {
 		iam (Sound);
@@ -1237,9 +1238,9 @@ DO_ALTERNATIVE (old_Sound_getValueAtIndex)
 		Melder_informationReal (sampleIndex < 1 || sampleIndex > my nx ? NUMundefined :
 			Sampled_getValueAtSample (me, sampleIndex, channel, 0), L"Pascal");
 	}
-END
+END2 }
 
-FORM (old_Sound_getValueAtTime, L"Sound: Get value at time", L"Sound: Get value at time...")
+FORM (old_Sound_getValueAtTime, L"Sound: Get value at time", L"Sound: Get value at time...") {
 	REAL (L"Time (s)", L"0.5")
 	RADIO (L"Interpolation", 4)
 		RADIOBUTTON (L"Nearest")
@@ -1247,16 +1248,16 @@ FORM (old_Sound_getValueAtTime, L"Sound: Get value at time", L"Sound: Get value
 		RADIOBUTTON (L"Cubic")
 		RADIOBUTTON (L"Sinc70")
 		RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		double value = Vector_getValueAtX (me, GET_REAL (L"Time"), Vector_CHANNEL_AVERAGE, GET_INTEGER (L"Interpolation") - 1);
 		Melder_informationReal (value, L"Pascal");
 	}
-END
+END2 }
 
-FORM (Sound_getValueAtTime, L"Sound: Get value at time", L"Sound: Get value at time...")
+FORM (Sound_getValueAtTime, L"Sound: Get value at time", L"Sound: Get value at time...") {
 	CHANNEL (L"Channel", L"0 (= average)")
 	REAL (L"Time (s)", L"0.5")
 	RADIO (L"Interpolation", 4)
@@ -1265,7 +1266,7 @@ FORM (Sound_getValueAtTime, L"Sound: Get value at time", L"Sound: Get value at t
 		RADIOBUTTON (L"Cubic")
 		RADIOBUTTON (L"Sinc70")
 		RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_getValueAtTime)
 	LOOP {
 		iam (Sound);
@@ -1274,15 +1275,15 @@ DO_ALTERNATIVE (old_Sound_getValueAtTime)
 		double value = Vector_getValueAtX (me, GET_REAL (L"Time"), channel, GET_INTEGER (L"Interpolation") - 1);
 		Melder_informationReal (value, L"Pascal");
 	}
-END
+END2 }
 
-DIRECT (Sound_help) Melder_help (L"Sound"); END
+DIRECT2 (Sound_help) { Melder_help (L"Sound"); END2 }
 
-FORM (Sound_lengthen_overlapAdd, L"Sound: Lengthen (overlap-add)", L"Sound: Lengthen (overlap-add)...")
+FORM (Sound_lengthen_overlapAdd, L"Sound: Lengthen (overlap-add)", L"Sound: Lengthen (overlap-add)...") {
 	POSITIVE (L"Minimum pitch (Hz)", L"75")
 	POSITIVE (L"Maximum pitch (Hz)", L"600")
 	POSITIVE (L"Factor", L"1.5")
-	OK
+	OK2
 DO
 	double minimumPitch = GET_REAL (L"Minimum pitch"), maximumPitch = GET_REAL (L"Maximum pitch");
 	double factor = GET_REAL (L"Factor");
@@ -1292,42 +1293,42 @@ DO
 		autoSound thee = Sound_lengthen_overlapAdd (me, minimumPitch, maximumPitch, factor);
 		praat_new (thee.transfer(), my name, L"_", Melder_fixed (factor, 2));
 	}
-END
+END2 }
 
-FORM (Sound_multiply, L"Sound: Multiply", 0)
+FORM (Sound_multiply, L"Sound: Multiply", 0) {
 	REAL (L"Multiplication factor", L"1.5")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Vector_multiplyByScalar (me, GET_REAL (L"Multiplication factor"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_multiplyByWindow, L"Sound: Multiply by window", 0)
+FORM (Sound_multiplyByWindow, L"Sound: Multiply by window", 0) {
 	OPTIONMENU_ENUM (L"Window shape", kSound_windowShape, HANNING)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Sound_multiplyByWindow (me, GET_ENUM (kSound_windowShape, L"Window shape"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_overrideSamplingFrequency, L"Sound: Override sampling frequency", 0)
+FORM (Sound_overrideSamplingFrequency, L"Sound: Override sampling frequency", 0) {
 	POSITIVE (L"New sampling frequency (Hz)", L"16000.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Sound_overrideSamplingFrequency (me, GET_REAL (L"New sampling frequency"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (Sound_play)
+DIRECT2 (Sound_play) {
 	int n = 0;
 	LOOP {
 		n ++;
@@ -1341,15 +1342,15 @@ DIRECT (Sound_play)
 		MelderAudio_setOutputMaximumAsynchronicity (kMelder_asynchronicityLevel_INTERRUPTABLE);
 		LOOP {
 			iam (Sound);
-			Sound_play (me, NULL, NULL);
+			Sound_play (me, NULL, NULL);   // BUG: exception-safe?
 		}
 		MelderAudio_setOutputMaximumAsynchronicity (kMelder_asynchronicityLevel_ASYNCHRONOUS);
 	}
-END
+END2 }
 
-FORM (Sound_preemphasizeInline, L"Sound: Pre-emphasize (in-line)", L"Sound: Pre-emphasize (in-line)...")
+FORM (Sound_preemphasizeInline, L"Sound: Pre-emphasize (in-line)", L"Sound: Pre-emphasize (in-line)...") {
 	REAL (L"From frequency (Hz)", L"50.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1357,9 +1358,9 @@ DO
 		Vector_scale (me, 0.99);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM_READ (Sound_readSeparateChannelsFromSoundFile, L"Read separate channels from sound file", 0, true)
+FORM_READ2 (Sound_readSeparateChannelsFromSoundFile, L"Read separate channels from sound file", 0, true) {
 	autoSound sound = Sound_readFromSoundFile (file);
 	wchar_t name [300];
 	wcscpy (name, MelderFile_name (file));
@@ -1371,12 +1372,12 @@ FORM_READ (Sound_readSeparateChannelsFromSoundFile, L"Read separate channels fro
 		autoSound thee = Sound_extractChannel (sound.peek(), ichan);
 		praat_new (thee.transfer(), name, L"_ch", Melder_integer (ichan));
 	}
-END
+END2 }
 
-FORM_READ (Sound_readFromRawAlawFile, L"Read Sound from raw Alaw file", 0, true)
+FORM_READ2 (Sound_readFromRawAlawFile, L"Read Sound from raw Alaw file", 0, true) {
 	autoSound me = Sound_readFromRawAlawFile (file);
 	praat_new (me.transfer(), MelderFile_name (file));
-END
+END2 }
 
 static SoundRecorder theSoundRecorder;   // only one at a time can exist
 static int thePreviousNumberOfChannels;
@@ -1412,14 +1413,14 @@ static void do_Sound_record (int numberOfChannels) {
 	}
 	thePreviousNumberOfChannels = numberOfChannels;
 }
-DIRECT (Sound_record_mono)
+DIRECT2 (Sound_record_mono) {
 	do_Sound_record (1);
-END
-DIRECT (Sound_record_stereo)
+END2 }
+DIRECT2 (Sound_record_stereo) {
 	do_Sound_record (2);
-END
+END2 }
 
-FORM (Sound_recordFixedTime, L"Record Sound", 0)
+FORM (Sound_recordFixedTime, L"Record Sound", 0) {
 	RADIO (L"Input source", 1)
 		RADIOBUTTON (L"Microphone")
 		RADIOBUTTON (L"Line")
@@ -1445,18 +1446,18 @@ FORM (Sound_recordFixedTime, L"Record Sound", 0)
 		RADIOBUTTON (L"48000")
 		RADIOBUTTON (L"96000")
 	POSITIVE (L"Duration (seconds)", L"1.0")
-	OK
+	OK2
 DO
 	autoSound me = Sound_recordFixedTime (GET_INTEGER (L"Input source"),
 		GET_REAL (L"Gain"), GET_REAL (L"Balance"),
 		wcstod (GET_STRING (L"Sampling frequency"), NULL), GET_REAL (L"Duration"));
 	praat_new (me.transfer(), L"untitled");
-END
+END2 }
 
-FORM (Sound_resample, L"Sound: Resample", L"Sound: Resample...")
+FORM (Sound_resample, L"Sound: Resample", L"Sound: Resample...") {
 	POSITIVE (L"New sampling frequency (Hz)", L"10000")
 	NATURAL (L"Precision (samples)", L"50")
-	OK
+	OK2
 DO
 	double samplingFrequency = GET_REAL (L"New sampling frequency");
 	LOOP {
@@ -1464,52 +1465,52 @@ DO
 		autoSound thee = Sound_resample (me, samplingFrequency, GET_INTEGER (L"Precision"));
 		praat_new (thee.transfer(), my name, L"_", Melder_integer ((long) round (samplingFrequency)));
 	}
-END
+END2 }
 
-DIRECT (Sound_reverse)
+DIRECT2 (Sound_reverse) {
 	LOOP {
 		iam (Sound);
 		Sound_reverse (me, 0, 0);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAs24BitWavFile, L"Save as 24-bit WAV file", 0, L"wav")
+FORM_WRITE2 (Sound_saveAs24BitWavFile, L"Save as 24-bit WAV file", 0, L"wav") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_WAV, 24);
-END
+END2 }
 
-FORM_WRITE (Sound_saveAs32BitWavFile, L"Save as 32-bit WAV file", 0, L"wav")
+FORM_WRITE2 (Sound_saveAs32BitWavFile, L"Save as 32-bit WAV file", 0, L"wav") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_WAV, 32);
-END
+END2 }
 
-FORM (Sound_scalePeak, L"Sound: Scale peak", L"Sound: Scale peak...")
+FORM (Sound_scalePeak, L"Sound: Scale peak", L"Sound: Scale peak...") {
 	POSITIVE (L"New absolute peak", L"0.99")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Vector_scale (me, GET_REAL (L"New absolute peak"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_scaleIntensity, L"Sound: Scale intensity", L"Sound: Scale intensity")
+FORM (Sound_scaleIntensity, L"Sound: Scale intensity", L"Sound: Scale intensity") {
 	POSITIVE (L"New average intensity (dB SPL)", L"70.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Sound_scaleIntensity (me, GET_REAL (L"New average intensity"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (old_Sound_setValueAtIndex, L"Sound: Set value at sample number", L"Sound: Set value at sample number...")
+FORM (old_Sound_setValueAtIndex, L"Sound: Set value at sample number", L"Sound: Set value at sample number...") {
 	NATURAL (L"Sample number", L"100")
 	REAL (L"New value", L"0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1520,13 +1521,13 @@ DO
 			my z [channel] [index] = GET_REAL (L"New value");
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_setValueAtIndex, L"Sound: Set value at sample number", L"Sound: Set value at sample number...")
+FORM (Sound_setValueAtIndex, L"Sound: Set value at sample number", L"Sound: Set value at sample number...") {
 	CHANNEL (L"Channel", L"0 (= all)")
 	NATURAL (L"Sample number", L"100")
 	REAL (L"New value", L"0")
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_setValueAtIndex)
 	LOOP {
 		iam (Sound);
@@ -1544,36 +1545,36 @@ DO_ALTERNATIVE (old_Sound_setValueAtIndex)
 		}
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_setPartToZero, L"Sound: Set part to zero", 0)
+FORM (Sound_setPartToZero, L"Sound: Set part to zero", 0) {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"0.0 (= all)")
 	RADIO (L"Cut", 2)
 		OPTION (L"at exactly these times")
 		OPTION (L"at nearest zero crossing")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		Sound_setZero (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Cut") - 1);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (Sound_subtractMean)
+DIRECT2 (Sound_subtractMean) {
 	LOOP {
 		iam (Sound);
 		Vector_subtractMean (me);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Sound_to_Manipulation, L"Sound: To Manipulation", L"Manipulation")
+FORM (Sound_to_Manipulation, L"Sound: To Manipulation", L"Manipulation") {
 	POSITIVE (L"Time step (s)", L"0.01")
 	POSITIVE (L"Minimum pitch (Hz)", L"75")
 	POSITIVE (L"Maximum pitch (Hz)", L"600")
-	OK
+	OK2
 DO
 	double fmin = GET_REAL (L"Minimum pitch"), fmax = GET_REAL (L"Maximum pitch");
 	if (fmax <= fmin) Melder_throw ("Maximum pitch must be greater than minimum pitch.");
@@ -1582,14 +1583,14 @@ DO
 		autoManipulation thee = Sound_to_Manipulation (me, GET_REAL (L"Time step"), fmin, fmax);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Cochleagram, L"Sound: To Cochleagram", 0)
+FORM (Sound_to_Cochleagram, L"Sound: To Cochleagram", 0) {
 	POSITIVE (L"Time step (s)", L"0.01")
 	POSITIVE (L"Frequency resolution (Bark)", L"0.1")
 	POSITIVE (L"Window length (s)", L"0.03")
 	REAL (L"Forward-masking time (s)", L"0.03")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1597,9 +1598,9 @@ DO
 			GET_REAL (L"Frequency resolution"), GET_REAL (L"Window length"), GET_REAL (L"Forward-masking time"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Cochleagram_edb, L"Sound: To Cochleagram (De Boer, Meddis & Hewitt)", 0)
+FORM (Sound_to_Cochleagram_edb, L"Sound: To Cochleagram (De Boer, Meddis & Hewitt)", 0) {
 	POSITIVE (L"Time step (s)", L"0.01")
 	POSITIVE (L"Frequency resolution (Bark)", L"0.1")
 	BOOLEAN (L"Has synapse", 1)
@@ -1608,7 +1609,7 @@ FORM (Sound_to_Cochleagram_edb, L"Sound: To Cochleagram (De Boer, Meddis & Hewit
 	POSITIVE (L"   loss rate (/sec)", L"2500")
 	POSITIVE (L"   return rate (/sec)", L"6580")
 	POSITIVE (L"   reprocessing rate (/sec)", L"66.31")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1618,15 +1619,15 @@ DO
 			GET_REAL (L"   return rate"), GET_REAL (L"   reprocessing rate"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Formant_burg, L"Sound: To Formant (Burg method)", L"Sound: To Formant (burg)...")
+FORM (Sound_to_Formant_burg, L"Sound: To Formant (Burg method)", L"Sound: To Formant (burg)...") {
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	POSITIVE (L"Max. number of formants", L"5")
 	REAL (L"Maximum formant (Hz)", L"5500 (= adult female)")
 	POSITIVE (L"Window length (s)", L"0.025")
 	POSITIVE (L"Pre-emphasis from (Hz)", L"50")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1635,15 +1636,15 @@ DO
 			GET_REAL (L"Window length"), GET_REAL (L"Pre-emphasis from"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Formant_keepAll, L"Sound: To Formant (keep all)", L"Sound: To Formant (keep all)...")
+FORM (Sound_to_Formant_keepAll, L"Sound: To Formant (keep all)", L"Sound: To Formant (keep all)...") {
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	POSITIVE (L"Max. number of formants", L"5")
 	REAL (L"Maximum formant (Hz)", L"5500 (= adult female)")
 	POSITIVE (L"Window length (s)", L"0.025")
 	POSITIVE (L"Pre-emphasis from (Hz)", L"50")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1652,15 +1653,15 @@ DO
 			GET_REAL (L"Window length"), GET_REAL (L"Pre-emphasis from"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Formant_willems, L"Sound: To Formant (split Levinson (Willems))", L"Sound: To Formant (sl)...")
+FORM (Sound_to_Formant_willems, L"Sound: To Formant (split Levinson (Willems))", L"Sound: To Formant (sl)...") {
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	POSITIVE (L"Number of formants", L"5")
 	REAL (L"Maximum formant (Hz)", L"5500 (= adult female)")
 	POSITIVE (L"Window length (s)", L"0.025")
 	POSITIVE (L"Pre-emphasis from (Hz)", L"50")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1669,14 +1670,14 @@ DO
 			GET_REAL (L"Window length"), GET_REAL (L"Pre-emphasis from"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Harmonicity_ac, L"Sound: To Harmonicity (ac)", L"Sound: To Harmonicity (ac)...")
+FORM (Sound_to_Harmonicity_ac, L"Sound: To Harmonicity (ac)", L"Sound: To Harmonicity (ac)...") {
 	POSITIVE (L"Time step (s)", L"0.01")
 	POSITIVE (L"Minimum pitch (Hz)", L"75")
 	REAL (L"Silence threshold", L"0.1")
 	POSITIVE (L"Periods per window", L"4.5")
-	OK
+	OK2
 DO
 	double periodsPerWindow = GET_REAL (L"Periods per window");
 	if (periodsPerWindow < 3.0) Melder_throw ("Number of periods per window must be at least 3.0.");
@@ -1686,14 +1687,14 @@ DO
 			GET_REAL (L"Minimum pitch"), GET_REAL (L"Silence threshold"), periodsPerWindow);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Harmonicity_cc, L"Sound: To Harmonicity (cc)", L"Sound: To Harmonicity (cc)...")
+FORM (Sound_to_Harmonicity_cc, L"Sound: To Harmonicity (cc)", L"Sound: To Harmonicity (cc)...") {
 	POSITIVE (L"Time step (s)", L"0.01")
 	POSITIVE (L"Minimum pitch (Hz)", L"75")
 	REAL (L"Silence threshold", L"0.1")
 	POSITIVE (L"Periods per window", L"1.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1702,14 +1703,14 @@ DO
 			GET_REAL (L"Periods per window"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Harmonicity_gne, L"Sound: To Harmonicity (gne)", 0)
+FORM (Sound_to_Harmonicity_gne, L"Sound: To Harmonicity (gne)", 0) {
 	POSITIVE (L"Minimum frequency (Hz)", L"500")
 	POSITIVE (L"Maximum frequency (Hz)", L"4500")
 	POSITIVE (L"Bandwidth (Hz)", L"1000")
 	POSITIVE (L"Step (Hz)", L"80")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1718,12 +1719,12 @@ DO
 			GET_REAL (L"Step"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (old_Sound_to_Intensity, L"Sound: To Intensity", L"Sound: To Intensity...")
+FORM (old_Sound_to_Intensity, L"Sound: To Intensity", L"Sound: To Intensity...") {
 	POSITIVE (L"Minimum pitch (Hz)", L"100")
 	REAL (L"Time step (s)", L"0.0 (= auto)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1731,13 +1732,13 @@ DO
 			GET_REAL (L"Minimum pitch"), GET_REAL (L"Time step"), FALSE);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Intensity, L"Sound: To Intensity", L"Sound: To Intensity...")
+FORM (Sound_to_Intensity, L"Sound: To Intensity", L"Sound: To Intensity...") {
 	POSITIVE (L"Minimum pitch (Hz)", L"100")
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	BOOLEAN (L"Subtract mean", 1)
-	OK
+	OK2
 DO_ALTERNATIVE (old_Sound_to_Intensity)
 	LOOP {
 		iam (Sound);
@@ -1745,13 +1746,13 @@ DO_ALTERNATIVE (old_Sound_to_Intensity)
 			GET_REAL (L"Minimum pitch"), GET_REAL (L"Time step"), GET_INTEGER (L"Subtract mean"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_IntensityTier, L"Sound: To IntensityTier", NULL)
+FORM (Sound_to_IntensityTier, L"Sound: To IntensityTier", NULL) {
 	POSITIVE (L"Minimum pitch (Hz)", L"100")
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	BOOLEAN (L"Subtract mean", 1)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1759,28 +1760,28 @@ DO
 			GET_REAL (L"Minimum pitch"), GET_REAL (L"Time step"), GET_INTEGER (L"Subtract mean"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (Sound_to_IntervalTier)
+DIRECT2 (Sound_to_IntervalTier) {
 	LOOP {
 		iam (Sound);
 		autoIntervalTier thee = IntervalTier_create (my xmin, my xmax);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Ltas, L"Sound: To long-term average spectrum", 0)
+FORM (Sound_to_Ltas, L"Sound: To long-term average spectrum", 0) {
 	POSITIVE (L"Bandwidth (Hz)", L"100")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoLtas thee = Sound_to_Ltas (me, GET_REAL (L"Bandwidth"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Ltas_pitchCorrected, L"Sound: To Ltas (pitch-corrected)", L"Sound: To Ltas (pitch-corrected)...")
+FORM (Sound_to_Ltas_pitchCorrected, L"Sound: To Ltas (pitch-corrected)", L"Sound: To Ltas (pitch-corrected)...") {
 	POSITIVE (L"Minimum pitch (Hz)", L"75")
 	POSITIVE (L"Maximum pitch (Hz)", L"600")
 	POSITIVE (L"Maximum frequency (Hz)", L"5000")
@@ -1788,7 +1789,7 @@ FORM (Sound_to_Ltas_pitchCorrected, L"Sound: To Ltas (pitch-corrected)", L"Sound
 	REAL (L"Shortest period (s)", L"0.0001")
 	REAL (L"Longest period (s)", L"0.02")
 	POSITIVE (L"Maximum period factor", L"1.3")
-	OK
+	OK2
 DO
 	double fmin = GET_REAL (L"Minimum pitch"), fmax = GET_REAL (L"Maximum pitch");
 	if (fmax <= fmin) Melder_throw ("Maximum pitch must be greater than minimum pitch.");
@@ -1799,17 +1800,17 @@ DO
 			GET_REAL (L"Shortest period"), GET_REAL (L"Longest period"), GET_REAL (L"Maximum period factor"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (Sound_to_Matrix)
+DIRECT2 (Sound_to_Matrix) {
 	LOOP {
 		iam (Sound);
 		autoMatrix thee = Sound_to_Matrix (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (Sounds_to_ParamCurve)
+DIRECT2 (Sounds_to_ParamCurve) {
 	Sound s1 = NULL, s2 = NULL;
 	LOOP {
 		iam (Sound);
@@ -1817,22 +1818,22 @@ DIRECT (Sounds_to_ParamCurve)
 	}
 	autoParamCurve thee = ParamCurve_create (s1, s2);
 	praat_new (thee.transfer(), s1 -> name, L"_", s2 -> name);
-END
+END2 }
 
-FORM (Sound_to_Pitch, L"Sound: To Pitch", L"Sound: To Pitch...")
+FORM (Sound_to_Pitch, L"Sound: To Pitch", L"Sound: To Pitch...") {
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	POSITIVE (L"Pitch floor (Hz)", L"75.0")
 	POSITIVE (L"Pitch ceiling (Hz)", L"600.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoPitch thee = Sound_to_Pitch (me, GET_REAL (L"Time step"), GET_REAL (L"Pitch floor"), GET_REAL (L"Pitch ceiling"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Pitch_ac, L"Sound: To Pitch (ac)", L"Sound: To Pitch (ac)...")
+FORM (Sound_to_Pitch_ac, L"Sound: To Pitch (ac)", L"Sound: To Pitch (ac)...") {
 	LABEL (L"", L"Finding the candidates")
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	POSITIVE (L"Pitch floor (Hz)", L"75.0")
@@ -1845,7 +1846,7 @@ FORM (Sound_to_Pitch_ac, L"Sound: To Pitch (ac)", L"Sound: To Pitch (ac)...")
 	REAL (L"Octave-jump cost", L"0.35")
 	REAL (L"Voiced / unvoiced cost", L"0.14")
 	POSITIVE (L"Pitch ceiling (Hz)", L"600.0")
-	OK
+	OK2
 DO
 	long maxnCandidates = GET_INTEGER (L"Max. number of candidates");
 	if (maxnCandidates <= 1) Melder_throw ("Maximum number of candidates must be greater than 1.");
@@ -1858,9 +1859,9 @@ DO
 			GET_REAL (L"Voiced / unvoiced cost"), GET_REAL (L"Pitch ceiling"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Pitch_cc, L"Sound: To Pitch (cc)", L"Sound: To Pitch (cc)...")
+FORM (Sound_to_Pitch_cc, L"Sound: To Pitch (cc)", L"Sound: To Pitch (cc)...") {
 	LABEL (L"", L"Finding the candidates")
 	REAL (L"Time step (s)", L"0.0 (= auto)")
 	POSITIVE (L"Pitch floor (Hz)", L"75")
@@ -1873,7 +1874,7 @@ FORM (Sound_to_Pitch_cc, L"Sound: To Pitch (cc)", L"Sound: To Pitch (cc)...")
 	REAL (L"Octave-jump cost", L"0.35")
 	REAL (L"Voiced / unvoiced cost", L"0.14")
 	POSITIVE (L"Pitch ceiling (Hz)", L"600")
-	OK
+	OK2
 DO
 	long maxnCandidates = GET_INTEGER (L"Max. number of candidates");
 	if (maxnCandidates <= 1) Melder_throw ("Maximum number of candidates must be greater than 1.");
@@ -1886,9 +1887,9 @@ DO
 			GET_REAL (L"Voiced / unvoiced cost"), GET_REAL (L"Pitch ceiling"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_PointProcess_extrema, L"Sound: To PointProcess (extrema)", 0)
+FORM (Sound_to_PointProcess_extrema, L"Sound: To PointProcess (extrema)", 0) {
 	CHANNEL (L"Channel (number, Left, or Right)", L"1")
 	BOOLEAN (L"Include maxima", 1)
 	BOOLEAN (L"Include minima", 0)
@@ -1898,7 +1899,7 @@ FORM (Sound_to_PointProcess_extrema, L"Sound: To PointProcess (extrema)", 0)
 	RADIOBUTTON (L"Cubic")
 	RADIOBUTTON (L"Sinc70")
 	RADIOBUTTON (L"Sinc700")
-	OK
+	OK2
 DO
 	long channel = GET_INTEGER (L"Channel");
 	LOOP {
@@ -1907,12 +1908,12 @@ DO
 			GET_INTEGER (L"Include maxima"), GET_INTEGER (L"Include minima"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_PointProcess_periodic_cc, L"Sound: To PointProcess (periodic, cc)", L"Sound: To PointProcess (periodic, cc)...")
+FORM (Sound_to_PointProcess_periodic_cc, L"Sound: To PointProcess (periodic, cc)", L"Sound: To PointProcess (periodic, cc)...") {
 	POSITIVE (L"Minimum pitch (Hz)", L"75")
 	POSITIVE (L"Maximum pitch (Hz)", L"600")
-	OK
+	OK2
 DO
 	double fmin = GET_REAL (L"Minimum pitch"), fmax = GET_REAL (L"Maximum pitch");
 	if (fmax <= fmin) Melder_throw ("Maximum pitch must be greater than minimum pitch.");
@@ -1921,14 +1922,14 @@ DO
 		autoPointProcess thee = Sound_to_PointProcess_periodic_cc (me, fmin, fmax);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_PointProcess_periodic_peaks, L"Sound: To PointProcess (periodic, peaks)", L"Sound: To PointProcess (periodic, peaks)...")
+FORM (Sound_to_PointProcess_periodic_peaks, L"Sound: To PointProcess (periodic, peaks)", L"Sound: To PointProcess (periodic, peaks)...") {
 	POSITIVE (L"Minimum pitch (Hz)", L"75")
 	POSITIVE (L"Maximum pitch (Hz)", L"600")
 	BOOLEAN (L"Include maxima", 1)
 	BOOLEAN (L"Include minima", 0)
-	OK
+	OK2
 DO
 	double fmin = GET_REAL (L"Minimum pitch"), fmax = GET_REAL (L"Maximum pitch");
 	if (fmax <= fmin) Melder_throw ("Maximum pitch must be greater than minimum pitch.");
@@ -1937,13 +1938,13 @@ DO
 		autoPointProcess thee = Sound_to_PointProcess_periodic_peaks (me, fmin, fmax, GET_INTEGER (L"Include maxima"), GET_INTEGER (L"Include minima"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_PointProcess_zeroes, L"Get zeroes", 0)
+FORM (Sound_to_PointProcess_zeroes, L"Get zeroes", 0) {
 	CHANNEL (L"Channel (number, Left, or Right)", L"1")
 	BOOLEAN (L"Include raisers", 1)
 	BOOLEAN (L"Include fallers", 0)
-	OK
+	OK2
 DO
 	long channel = GET_INTEGER (L"Channel");
 	LOOP {
@@ -1951,15 +1952,15 @@ DO
 		autoPointProcess thee = Sound_to_PointProcess_zeroes (me, channel > my ny ? 1 : channel, GET_INTEGER (L"Include raisers"), GET_INTEGER (L"Include fallers"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Spectrogram, L"Sound: To Spectrogram", L"Sound: To Spectrogram...")
+FORM (Sound_to_Spectrogram, L"Sound: To Spectrogram", L"Sound: To Spectrogram...") {
 	POSITIVE (L"Window length (s)", L"0.005")
 	POSITIVE (L"Maximum frequency (Hz)", L"5000")
 	POSITIVE (L"Time step (s)", L"0.002")
 	POSITIVE (L"Frequency step (Hz)", L"20")
 	RADIO_ENUM (L"Window shape", kSound_to_Spectrogram_windowShape, DEFAULT)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
@@ -1968,66 +1969,66 @@ DO
 			GET_REAL (L"Frequency step"), GET_ENUM (kSound_to_Spectrogram_windowShape, L"Window shape"), 8.0, 8.0);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_Spectrum, L"Sound: To Spectrum", L"Sound: To Spectrum...")
+FORM (Sound_to_Spectrum, L"Sound: To Spectrum", L"Sound: To Spectrum...") {
 	BOOLEAN (L"Fast", 1)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoSpectrum thee = Sound_to_Spectrum (me, GET_INTEGER (L"Fast"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (Sound_to_Spectrum_dft)
+DIRECT2 (Sound_to_Spectrum_dft) {
 	LOOP {
 		iam (Sound);
 		autoSpectrum thee = Sound_to_Spectrum (me, FALSE);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (Sound_to_Spectrum_fft)
+DIRECT2 (Sound_to_Spectrum_fft) {
 	LOOP {
 		iam (Sound);
 		autoSpectrum thee = Sound_to_Spectrum (me, TRUE);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Sound_to_TextGrid, L"Sound: To TextGrid", L"Sound: To TextGrid...")
+FORM (Sound_to_TextGrid, L"Sound: To TextGrid", L"Sound: To TextGrid...") {
 	SENTENCE (L"All tier names", L"Mary John bell")
 	SENTENCE (L"Which of these are point tiers?", L"bell")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Sound);
 		autoTextGrid thee = TextGrid_create (my xmin, my xmax, GET_STRING (L"All tier names"), GET_STRING (L"Which of these are point tiers?"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (Sound_to_TextTier)
+DIRECT2 (Sound_to_TextTier) {
 	LOOP {
 		iam (Sound);
 		autoTextTier thee = TextTier_create (my xmin, my xmax);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (SoundInputPrefs, L"Sound recording preferences", L"SoundRecorder")
+FORM (SoundInputPrefs, L"Sound recording preferences", L"SoundRecorder") {
 	NATURAL (L"Buffer size (MB)", L"20")
-	OK
+	OK2
 SET_INTEGER (L"Buffer size", SoundRecorder_getBufferSizePref_MB ())
 DO
 	long size = GET_INTEGER (L"Buffer size");
 	if (size > 1000) Melder_throw ("Buffer size cannot exceed 1000 megabytes.");
 	SoundRecorder_setBufferSizePref_MB (size);
-END
+END2 }
 
-FORM (SoundOutputPrefs, L"Sound playing preferences", 0)
+FORM (SoundOutputPrefs, L"Sound playing preferences", 0) {
 	LABEL (L"", L"The following determines how sounds are played.")
 	LABEL (L"", L"Between parentheses, you find what you can do simultaneously.")
 	LABEL (L"", L"Decrease asynchronicity if sound plays with discontinuities.")
@@ -2036,7 +2037,7 @@ FORM (SoundOutputPrefs, L"Sound playing preferences", 0)
 	#define str(s) #s
 	REAL (L"Silence before (s)", L"" xstr (kMelderAudio_outputSilenceBefore_DEFAULT))
 	REAL (L"Silence after (s)", L"" xstr (kMelderAudio_outputSilenceAfter_DEFAULT))
-	OK
+	OK2
 SET_ENUM (L"Maximum asynchronicity", kMelder_asynchronicityLevel, MelderAudio_getOutputMaximumAsynchronicity ())
 SET_REAL (L"Silence before", MelderAudio_getOutputSilenceBefore ())
 SET_REAL (L"Silence after", MelderAudio_getOutputSilenceAfter ())
@@ -2045,159 +2046,159 @@ DO
 	MelderAudio_setOutputMaximumAsynchronicity (GET_ENUM (kMelder_asynchronicityLevel, L"Maximum asynchronicity"));
 	MelderAudio_setOutputSilenceBefore (GET_REAL (L"Silence before"));
 	MelderAudio_setOutputSilenceAfter (GET_REAL (L"Silence after"));
-END
+END2 }
 
-FORM_WRITE (Sound_writeToAifcFile, L"Save as AIFC file", 0, L"aifc")
+FORM_WRITE2 (Sound_writeToAifcFile, L"Save as AIFC file", 0, L"aifc") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_AIFC, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToAiffFile, L"Save as AIFF file", 0, L"aiff")
+FORM_WRITE2 (Sound_writeToAiffFile, L"Save as AIFF file", 0, L"aiff") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_AIFF, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToFlacFile, L"Save as FLAC file", 0, L"flac")
+FORM_WRITE2 (Sound_writeToFlacFile, L"Save as FLAC file", 0, L"flac") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_FLAC, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToKayFile, L"Save as Kay sound file", 0, L"kay")
+FORM_WRITE2 (Sound_writeToKayFile, L"Save as Kay sound file", 0, L"kay") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToKayFile (me, file);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_writeToNextSunFile, L"Save as NeXT/Sun file", 0, L"au")
+FORM_WRITE2 (Sound_writeToNextSunFile, L"Save as NeXT/Sun file", 0, L"au") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_NEXT_SUN, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToNistFile, L"Save as NIST file", 0, L"nist")
+FORM_WRITE2 (Sound_writeToNistFile, L"Save as NIST file", 0, L"nist") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_NIST, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw8bitSignedFile, L"Save as raw 8-bit signed sound file", 0, L"8sig")
+FORM_WRITE2 (Sound_saveAsRaw8bitSignedFile, L"Save as raw 8-bit signed sound file", 0, L"8sig") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_8_SIGNED);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw8bitUnsignedFile, L"Save as raw 8-bit unsigned sound file", 0, L"8uns")
+FORM_WRITE2 (Sound_saveAsRaw8bitUnsignedFile, L"Save as raw 8-bit unsigned sound file", 0, L"8uns") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_8_UNSIGNED);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw16bitBigEndianFile, L"Save as raw 16-bit big-endian sound file", 0, L"16be")
+FORM_WRITE2 (Sound_saveAsRaw16bitBigEndianFile, L"Save as raw 16-bit big-endian sound file", 0, L"16be") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_16_BIG_ENDIAN);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw16bitLittleEndianFile, L"Save as raw 16-bit little-endian sound file", 0, L"16le")
+FORM_WRITE2 (Sound_saveAsRaw16bitLittleEndianFile, L"Save as raw 16-bit little-endian sound file", 0, L"16le") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_16_LITTLE_ENDIAN);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw24bitBigEndianFile, L"Save as raw 24-bit big-endian sound file", 0, L"24be")
+FORM_WRITE2 (Sound_saveAsRaw24bitBigEndianFile, L"Save as raw 24-bit big-endian sound file", 0, L"24be") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_24_BIG_ENDIAN);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw24bitLittleEndianFile, L"Save as raw 24-bit little-endian sound file", 0, L"24le")
+FORM_WRITE2 (Sound_saveAsRaw24bitLittleEndianFile, L"Save as raw 24-bit little-endian sound file", 0, L"24le") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_24_LITTLE_ENDIAN);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw32bitBigEndianFile, L"Save as raw 32-bit big-endian sound file", 0, L"32be")
+FORM_WRITE2 (Sound_saveAsRaw32bitBigEndianFile, L"Save as raw 32-bit big-endian sound file", 0, L"32be") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_32_BIG_ENDIAN);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_saveAsRaw32bitLittleEndianFile, L"Save as raw 32-bit little-endian sound file", 0, L"32le")
+FORM_WRITE2 (Sound_saveAsRaw32bitLittleEndianFile, L"Save as raw 32-bit little-endian sound file", 0, L"32le") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToRawSoundFile (me, file, Melder_LINEAR_32_LITTLE_ENDIAN);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_writeToSesamFile, L"Save as Sesam file", 0, L"sdf")
+FORM_WRITE2 (Sound_writeToSesamFile, L"Save as Sesam file", 0, L"sdf") {
 	LOOP {
 		iam (Sound);
 		Sound_writeToSesamFile (me, file);
 	}
-END
+END2 }
 
-FORM_WRITE (Sound_writeToStereoAifcFile, L"Save as stereo AIFC file", 0, L"aifc")
+FORM_WRITE2 (Sound_writeToStereoAifcFile, L"Save as stereo AIFC file", 0, L"aifc") {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound stereo = Sounds_combineToStereo (set.peek());
 	Sound_writeToAudioFile (stereo.peek(), file, Melder_AIFC, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToStereoAiffFile, L"Save as stereo AIFF file", 0, L"aiff")
+FORM_WRITE2 (Sound_writeToStereoAiffFile, L"Save as stereo AIFF file", 0, L"aiff") {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound stereo = Sounds_combineToStereo (set.peek());
 	Sound_writeToAudioFile (stereo.peek(), file, Melder_AIFF, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToStereoNextSunFile, L"Save as stereo NeXT/Sun file", 0, L"au")
+FORM_WRITE2 (Sound_writeToStereoNextSunFile, L"Save as stereo NeXT/Sun file", 0, L"au") {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound stereo = Sounds_combineToStereo (set.peek());
 	Sound_writeToAudioFile (stereo.peek(), file, Melder_NEXT_SUN, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToStereoNistFile, L"Save as stereo NIST file", 0, L"nist")
+FORM_WRITE2 (Sound_writeToStereoNistFile, L"Save as stereo NIST file", 0, L"nist") {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound stereo = Sounds_combineToStereo (set.peek());
 	Sound_writeToAudioFile (stereo.peek(), file, Melder_NIST, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToStereoFlacFile, L"Save as stereo FLAC file", 0, L"flac")
+FORM_WRITE2 (Sound_writeToStereoFlacFile, L"Save as stereo FLAC file", 0, L"flac") {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound stereo = Sounds_combineToStereo (set.peek());
 	Sound_writeToAudioFile (stereo.peek(), file, Melder_FLAC, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToStereoWavFile, L"Save as stereo WAV file", 0, L"wav")
+FORM_WRITE2 (Sound_writeToStereoWavFile, L"Save as stereo WAV file", 0, L"wav") {
 	autoCollection set = praat_getSelectedObjects ();
 	autoSound stereo = Sounds_combineToStereo (set.peek());
 	Sound_writeToAudioFile (stereo.peek(), file, Melder_WAV, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToSunAudioFile, L"Save as NeXT/Sun file", 0, L"au")
+FORM_WRITE2 (Sound_writeToSunAudioFile, L"Save as NeXT/Sun file", 0, L"au") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_NEXT_SUN, 16);
-END
+END2 }
 
-FORM_WRITE (Sound_writeToWavFile, L"Save as WAV file", 0, L"wav")
+FORM_WRITE2 (Sound_writeToWavFile, L"Save as WAV file", 0, L"wav") {
 	autoCollection set = praat_getSelectedObjects ();
 	LongSound_concatenate (set.peek(), file, Melder_WAV, 16);
-END
+END2 }
 
 /***** STOP *****/
 
-DIRECT (stopPlayingSound)
+DIRECT2 (stopPlayingSound) {
 	MelderAudio_stopPlaying (MelderAudio_IMPLICIT);
-END
+END2 }
 
 /***** Help menus *****/
 
-DIRECT (AnnotationTutorial) Melder_help (L"Intro 7. Annotation"); END
-DIRECT (FilteringTutorial) Melder_help (L"Filtering"); END
+DIRECT2 (AnnotationTutorial) { Melder_help (L"Intro 7. Annotation"); END2 }
+DIRECT2 (FilteringTutorial) { Melder_help (L"Filtering"); END2 }
 
 /***** file recognizers *****/
 
@@ -2215,7 +2216,9 @@ static Any soundFileRecognizer (int nread, const char *header, MelderFile file)
 	if (strnequ (header, ".snd", 4)) return Sound_readFromSoundFile (file);
 	if (strnequ (header, "NIST_1A", 7)) return Sound_readFromSoundFile (file);
 	if (strnequ (header, "fLaC", 4)) return Sound_readFromSoundFile (file);   // Erez Volk, March 2007
-	if ((wcsstr (MelderFile_name (file), L".mp3") || wcsstr (MelderFile_name (file), L".MP3")) && mp3_recognize (nread, header)) return Sound_readFromSoundFile (file);   // Erez Volk, May 2007
+	if ((Melder_stringMatchesCriterion (MelderFile_name (file), kMelder_string_ENDS_WITH, L".mp3") ||
+	     Melder_stringMatchesCriterion (MelderFile_name (file), kMelder_string_ENDS_WITH, L".MP3"))
+		&& mp3_recognize (nread, header)) return Sound_readFromSoundFile (file);   // Erez Volk, May 2007
 	return NULL;
 }
 
@@ -2226,8 +2229,10 @@ static Any movieFileRecognizer (int nread, const char *header, MelderFile file)
 		header [1], header [2], header [3],
 		header [4], header [5], header [6],
 		header [7], header [8], header [9]);*/
-	if (nread < 512 || (! wcsstr (fileName, L".mov") && ! wcsstr (fileName, L".MOV") &&
-	    ! wcsstr (fileName, L".avi") && ! wcsstr (fileName, L".AVI"))) return NULL;
+	if (nread < 512 || (! Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".mov") &&
+	                    ! Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".MOV") &&
+	                    ! Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".avi") &&
+	                    ! Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".AVI"))) return NULL;
 	Melder_throw ("This Praat version cannot open movie files.");
 	return NULL;
 }
@@ -2235,7 +2240,8 @@ static Any movieFileRecognizer (int nread, const char *header, MelderFile file)
 static Any sesamFileRecognizer (int nread, const char *header, MelderFile file) {
 	const wchar_t *fileName = MelderFile_name (file);
 	(void) header;
-	if (nread < 512 || (! wcsstr (fileName, L".sdf") && ! wcsstr (fileName, L".SDF"))) return NULL;
+	if (nread < 512 || (! Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".sdf") &&
+	                    ! Melder_stringMatchesCriterion (fileName, kMelder_string_ENDS_WITH, L".SDF"))) return NULL;
 	return Sound_readFromSesamFile (file);
 }
 
diff --git a/fon/praat_TextGrid_init.cpp b/fon/praat_TextGrid_init.cpp
index 0722d87..e195d34 100644
--- a/fon/praat_TextGrid_init.cpp
+++ b/fon/praat_TextGrid_init.cpp
@@ -1,6 +1,6 @@
 /* praat_TextGrid_init.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -44,83 +44,83 @@ void praat_TimeFunction_modify_init (ClassInfo klas);   // Modify buttons for ti
 
 /***** ANYTIER (generic) *****/
 
-DIRECT (AnyTier_into_TextGrid)
+DIRECT2 (AnyTier_into_TextGrid) {
 	autoTextGrid grid = TextGrid_createWithoutTiers (1e30, -1e30);
 	LOOP {
 		iam (AnyTier);
 		TextGrid_addTier (grid.peek(), me);
 	}
 	praat_new (grid.transfer(), L"grid");
-END
+END2 }
 
 /***** INTERVALTIER *****/
 
-FORM (IntervalTier_downto_TableOfReal, L"IntervalTier: Down to TableOfReal", 0)
+FORM (IntervalTier_downto_TableOfReal, L"IntervalTier: Down to TableOfReal", 0) {
 	SENTENCE (L"Label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (IntervalTier);
 		autoTableOfReal thee = IntervalTier_downto_TableOfReal (me, GET_STRING (L"Label"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (IntervalTier_downto_TableOfReal_any)
+DIRECT2 (IntervalTier_downto_TableOfReal_any) {
 	LOOP {
 		iam (IntervalTier);
 		autoTableOfReal thee = IntervalTier_downto_TableOfReal_any (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (IntervalTier_getCentrePoints, L"IntervalTier: Get centre points", 0)
+FORM (IntervalTier_getCentrePoints, L"IntervalTier: Get centre points", 0) {
 	SENTENCE (L"Text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (IntervalTier);
 		autoPointProcess thee = IntervalTier_getCentrePoints (me, GET_STRING (L"Text"));
 		praat_new (thee.transfer(), GET_STRING (L"Text"));
 	}
-END
+END2 }
 
-FORM (IntervalTier_getEndPoints, L"IntervalTier: Get end points", 0)
+FORM (IntervalTier_getEndPoints, L"IntervalTier: Get end points", 0) {
 	SENTENCE (L"Text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (IntervalTier);
 		autoPointProcess thee = IntervalTier_getEndPoints (me, GET_STRING (L"Text"));
 		praat_new (thee.transfer(), GET_STRING (L"Text"));
 	}
-END
+END2 }
 
-FORM (IntervalTier_getStartingPoints, L"IntervalTier: Get starting points", 0)
+FORM (IntervalTier_getStartingPoints, L"IntervalTier: Get starting points", 0) {
 	SENTENCE (L"Text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (IntervalTier);
 		autoPointProcess thee = IntervalTier_getStartingPoints (me, GET_STRING (L"Text"));
 		praat_new (thee.transfer(), GET_STRING (L"Text"));
 	}
-END
+END2 }
 
-DIRECT (IntervalTier_help) Melder_help (L"IntervalTier"); END
+DIRECT2 (IntervalTier_help) { Melder_help (L"IntervalTier"); END2 }
 
-FORM_WRITE (IntervalTier_writeToXwaves, L"Xwaves label file", 0, 0)
+FORM_WRITE2 (IntervalTier_writeToXwaves, L"Xwaves label file", 0, 0) {
 	LOOP {
 		iam (IntervalTier);
 		IntervalTier_writeToXwaves (me, file);
 	}
-END
+END2 }
 
 /***** INTERVALTIER & POINTPROCESS *****/
 
-FORM (IntervalTier_PointProcess_endToCentre, L"From end to centre", L"IntervalTier & PointProcess: End to centre...")
+FORM (IntervalTier_PointProcess_endToCentre, L"From end to centre", L"IntervalTier & PointProcess: End to centre...") {
 	REAL (L"Phase (0-1)", L"0.5")
-	OK
+	OK2
 DO
 	IntervalTier tier = NULL;
 	PointProcess point = NULL;
@@ -131,11 +131,11 @@ DO
 	double phase = GET_REAL (L"Phase");
 	autoPointProcess thee = IntervalTier_PointProcess_endToCentre (tier, point, phase);
 	praat_new (thee.transfer(), tier -> name, L"_", point -> name, L"_", Melder_integer ((long) (100 * phase)));
-END
+END2 }
 
-FORM (IntervalTier_PointProcess_startToCentre, L"From start to centre", L"IntervalTier & PointProcess: Start to centre...")
+FORM (IntervalTier_PointProcess_startToCentre, L"From start to centre", L"IntervalTier & PointProcess: Start to centre...") {
 	REAL (L"Phase (0-1)", L"0.5")
-	OK
+	OK2
 DO
 	IntervalTier tier = NULL;
 	PointProcess point = NULL;
@@ -146,11 +146,11 @@ DO
 	double phase = GET_REAL (L"Phase");
 	autoPointProcess thee = IntervalTier_PointProcess_startToCentre (tier, point, phase);
 	praat_new (thee.transfer(), tier -> name, L"_", point -> name, L"_", Melder_integer ((long) (100 * phase)));
-END
+END2 }
 
 /***** LABEL (obsolete) *****/
 
-DIRECT (Label_Sound_to_TextGrid)
+DIRECT2 (Label_Sound_to_TextGrid) {
 	Label label = NULL;
 	Sound sound = NULL;
 	LOOP {
@@ -159,12 +159,12 @@ DIRECT (Label_Sound_to_TextGrid)
 	}
 	autoTextGrid thee = Label_Function_to_TextGrid (label, sound);
 	praat_new (thee.transfer(), sound -> name);
-END
+END2 }
 
-DIRECT (info_Label_Sound_to_TextGrid)
+DIRECT2 (info_Label_Sound_to_TextGrid) {
 	Melder_information (L"This is an old-style Label object. To turn it into a TextGrid, L"
 		"select it together with a Sound of the appropriate duration, and click \"To TextGrid\".");
-END
+END2 }
 
 /***** PITCH & TEXTGRID *****/
 
@@ -185,7 +185,7 @@ static void pr_TextGrid_Pitch_draw (Any dia, int speckle, int unit) {
 		GET_INTEGER (L"Use text styles"), GET_INTEGER (L"Text alignment") - 1, GET_INTEGER (L"Garnish"), speckle, unit);
 }
 
-FORM (TextGrid_Pitch_draw, L"TextGrid & Pitch: Draw", 0)
+FORM (TextGrid_Pitch_draw, L"TextGrid & Pitch: Draw", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	REAL (STRING_FROM_FREQUENCY_HZ, L"0.0")
@@ -194,12 +194,12 @@ FORM (TextGrid_Pitch_draw, L"TextGrid & Pitch: Draw", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_NO, kPitch_unit_HERTZ);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawErb, L"TextGrid & Pitch: Draw erb", 0)
+FORM (TextGrid_Pitch_drawErb, L"TextGrid & Pitch: Draw erb", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (ERB)", L"0")
@@ -208,12 +208,12 @@ FORM (TextGrid_Pitch_drawErb, L"TextGrid & Pitch: Draw erb", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_NO, kPitch_unit_ERB);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawLogarithmic, L"TextGrid & Pitch: Draw logarithmic", 0)
+FORM (TextGrid_Pitch_drawLogarithmic, L"TextGrid & Pitch: Draw logarithmic", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	POSITIVE (STRING_FROM_FREQUENCY_HZ, L"50.0")
@@ -222,12 +222,12 @@ FORM (TextGrid_Pitch_drawLogarithmic, L"TextGrid & Pitch: Draw logarithmic", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_NO, kPitch_unit_HERTZ_LOGARITHMIC);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawMel, L"TextGrid & Pitch: Draw mel", 0)
+FORM (TextGrid_Pitch_drawMel, L"TextGrid & Pitch: Draw mel", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (mel)", L"0")
@@ -236,12 +236,12 @@ FORM (TextGrid_Pitch_drawMel, L"TextGrid & Pitch: Draw mel", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_NO, kPitch_unit_MEL);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawSemitones, L"TextGrid & Pitch: Draw semitones", 0)
+FORM (TextGrid_Pitch_drawSemitones, L"TextGrid & Pitch: Draw semitones", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	LABEL (L"", L"Range in semitones re 100 hertz:")
@@ -251,10 +251,10 @@ FORM (TextGrid_Pitch_drawSemitones, L"TextGrid & Pitch: Draw semitones", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_NO, kPitch_unit_SEMITONES_100);
-END
+END2 }
 
 static void pr_TextGrid_Pitch_drawSeparately (Any dia, int speckle, int unit) {
 	TextGrid grid = NULL;
@@ -273,55 +273,55 @@ static void pr_TextGrid_Pitch_drawSeparately (Any dia, int speckle, int unit) {
 		GET_INTEGER (L"Use text styles"), GET_INTEGER (L"Garnish"), speckle, unit);
 }
 
-FORM (TextGrid_Pitch_drawSeparately, L"TextGrid & Pitch: Draw separately", 0)
+FORM (TextGrid_Pitch_drawSeparately, L"TextGrid & Pitch: Draw separately", 0) {
 	praat_dia_timeRange (dia);
 	REAL (STRING_FROM_FREQUENCY_HZ, L"0.0")
 	REAL (STRING_TO_FREQUENCY_HZ, L"500.0")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_NO, kPitch_unit_HERTZ);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawSeparatelyErb, L"TextGrid & Pitch: Draw separately erb", 0)
+FORM (TextGrid_Pitch_drawSeparatelyErb, L"TextGrid & Pitch: Draw separately erb", 0) {
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (ERB)", L"0")
 	REAL (L"right Frequency range (ERB)", L"10.0")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_NO, kPitch_unit_ERB);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawSeparatelyLogarithmic, L"TextGrid & Pitch: Draw separately logarithmic", 0)
+FORM (TextGrid_Pitch_drawSeparatelyLogarithmic, L"TextGrid & Pitch: Draw separately logarithmic", 0) {
 	praat_dia_timeRange (dia);
 	POSITIVE (STRING_FROM_FREQUENCY_HZ, L"50.0")
 	POSITIVE (STRING_TO_FREQUENCY_HZ, L"500.0")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_NO, kPitch_unit_HERTZ_LOGARITHMIC);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawSeparatelyMel, L"TextGrid & Pitch: Draw separately mel", 0)
+FORM (TextGrid_Pitch_drawSeparatelyMel, L"TextGrid & Pitch: Draw separately mel", 0) {
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (mel)", L"0")
 	REAL (L"right Frequency range (mel)", L"500")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_NO, kPitch_unit_MEL);
-END
+END2 }
 
-FORM (TextGrid_Pitch_drawSeparatelySemitones, L"TextGrid & Pitch: Draw separately semitones", 0)
+FORM (TextGrid_Pitch_drawSeparatelySemitones, L"TextGrid & Pitch: Draw separately semitones", 0) {
 	praat_dia_timeRange (dia);
 	LABEL (L"", L"Range in semitones re 100 hertz:")
 	REAL (L"left Frequency range (st)", L"-12.0")
@@ -329,12 +329,12 @@ FORM (TextGrid_Pitch_drawSeparatelySemitones, L"TextGrid & Pitch: Draw separatel
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_NO, kPitch_unit_SEMITONES_100);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckle, L"TextGrid & Pitch: Speckle", 0)
+FORM (TextGrid_Pitch_speckle, L"TextGrid & Pitch: Speckle", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	REAL (STRING_FROM_FREQUENCY_HZ, L"0.0")
@@ -343,12 +343,12 @@ FORM (TextGrid_Pitch_speckle, L"TextGrid & Pitch: Speckle", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_YES, kPitch_unit_HERTZ);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleErb, L"TextGrid & Pitch: Speckle erb", 0)
+FORM (TextGrid_Pitch_speckleErb, L"TextGrid & Pitch: Speckle erb", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (ERB)", L"0")
@@ -357,12 +357,12 @@ FORM (TextGrid_Pitch_speckleErb, L"TextGrid & Pitch: Speckle erb", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_YES, kPitch_unit_ERB);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleLogarithmic, L"TextGrid & Pitch: Speckle logarithmic", 0)
+FORM (TextGrid_Pitch_speckleLogarithmic, L"TextGrid & Pitch: Speckle logarithmic", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	POSITIVE (STRING_FROM_FREQUENCY_HZ, L"50.0")
@@ -371,12 +371,12 @@ FORM (TextGrid_Pitch_speckleLogarithmic, L"TextGrid & Pitch: Speckle logarithmic
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_YES, kPitch_unit_HERTZ_LOGARITHMIC);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleMel, L"TextGrid & Pitch: Speckle mel", 0)
+FORM (TextGrid_Pitch_speckleMel, L"TextGrid & Pitch: Speckle mel", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (mel)", L"0")
@@ -385,12 +385,12 @@ FORM (TextGrid_Pitch_speckleMel, L"TextGrid & Pitch: Speckle mel", 0)
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_YES, kPitch_unit_MEL);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleSemitones, L"TextGrid & Pitch: Speckle semitones", 0)
+FORM (TextGrid_Pitch_speckleSemitones, L"TextGrid & Pitch: Speckle semitones", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	praat_dia_timeRange (dia);
 	LABEL (L"", L"Range in semitones re 100 hertz:")
@@ -400,60 +400,60 @@ FORM (TextGrid_Pitch_speckleSemitones, L"TextGrid & Pitch: Speckle semitones", 0
 	BOOLEAN (L"Use text styles", 1)
 	OPTIONMENU (L"Text alignment", 2) OPTION (L"Left") OPTION (L"Centre") OPTION (L"Right")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_draw (dia, Pitch_speckle_YES, kPitch_unit_SEMITONES_100);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleSeparately, L"TextGrid & Pitch: Speckle separately", 0)
+FORM (TextGrid_Pitch_speckleSeparately, L"TextGrid & Pitch: Speckle separately", 0) {
 	praat_dia_timeRange (dia);
 	REAL (STRING_FROM_FREQUENCY_HZ, L"0.0")
 	REAL (STRING_TO_FREQUENCY_HZ, L"500.0")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_YES, kPitch_unit_HERTZ);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleSeparatelyErb, L"TextGrid & Pitch: Speckle separately erb", 0)
+FORM (TextGrid_Pitch_speckleSeparatelyErb, L"TextGrid & Pitch: Speckle separately erb", 0) {
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (ERB)", L"0")
 	REAL (L"right Frequency range (ERB)", L"10.0")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_YES, kPitch_unit_ERB);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleSeparatelyLogarithmic, L"TextGrid & Pitch: Speckle separately logarithmic", 0)
+FORM (TextGrid_Pitch_speckleSeparatelyLogarithmic, L"TextGrid & Pitch: Speckle separately logarithmic", 0) {
 	praat_dia_timeRange (dia);
 	POSITIVE (STRING_FROM_FREQUENCY_HZ, L"50.0")
 	POSITIVE (STRING_TO_FREQUENCY_HZ, L"500.0")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_YES, kPitch_unit_HERTZ_LOGARITHMIC);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleSeparatelyMel, L"TextGrid & Pitch: Speckle separately mel", 0)
+FORM (TextGrid_Pitch_speckleSeparatelyMel, L"TextGrid & Pitch: Speckle separately mel", 0) {
 	praat_dia_timeRange (dia);
 	REAL (L"left Frequency range (mel)", L"0")
 	REAL (L"right Frequency range (mel)", L"500")
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_YES, kPitch_unit_MEL);
-END
+END2 }
 
-FORM (TextGrid_Pitch_speckleSeparatelySemitones, L"TextGrid & Pitch: Speckle separately semitones", 0)
+FORM (TextGrid_Pitch_speckleSeparatelySemitones, L"TextGrid & Pitch: Speckle separately semitones", 0) {
 	praat_dia_timeRange (dia);
 	LABEL (L"", L"Range in semitones re 100 hertz:")
 	REAL (L"left Frequency range (st)", L"-12.0")
@@ -461,19 +461,19 @@ FORM (TextGrid_Pitch_speckleSeparatelySemitones, L"TextGrid & Pitch: Speckle sep
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	pr_TextGrid_Pitch_drawSeparately (dia, Pitch_speckle_YES, kPitch_unit_SEMITONES_100);
-END
+END2 }
 
 /***** PITCH & TEXTTIER *****/
 
-FORM (Pitch_TextTier_to_PitchTier, L"Pitch & TextTier to PitchTier", L"Pitch & TextTier: To PitchTier...")
+FORM (Pitch_TextTier_to_PitchTier, L"Pitch & TextTier to PitchTier", L"Pitch & TextTier: To PitchTier...") {
 	RADIO (L"Unvoiced strategy", 3)
 		RADIOBUTTON (L"Zero")
 		RADIOBUTTON (L"Error")
 		RADIOBUTTON (L"Interpolate")
-	OK
+	OK2
 DO
 	Pitch pitch = NULL;
 	TextTier tier = NULL;
@@ -484,138 +484,138 @@ DO
 	}
 	autoPitchTier thee = Pitch_AnyTier_to_PitchTier (pitch, (AnyTier) tier, GET_INTEGER (L"Unvoiced strategy") - 1);
 	praat_new (thee.transfer(), pitch -> name);
-END
+END2 }
 
 /***** SOUND & TEXTGRID *****/
 
-FORM (TextGrid_Sound_draw, L"TextGrid & Sound: Draw...", 0)
+FORM (TextGrid_Sound_draw, L"TextGrid & Sound: Draw...", 0) {
 	praat_dia_timeRange (dia);
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
-	TextGrid grid = NULL;
+	TextGrid textgrid = NULL;
 	Sound sound = NULL;
 	LOOP {
-		if (CLASS == classTextGrid) grid = (TextGrid) OBJECT;
+		if (CLASS == classTextGrid) textgrid = (TextGrid) OBJECT;
 		if (CLASS == classSound) sound = (Sound) OBJECT;
 	}
 	autoPraatPicture picture;
-	TextGrid_Sound_draw (grid, sound, GRAPHICS,
+	TextGrid_Sound_draw (textgrid, sound, GRAPHICS,
 		GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Show boundaries"),
 		GET_INTEGER (L"Use text styles"), GET_INTEGER (L"Garnish"));
-END
+END2 }
 
-FORM (TextGrid_Sound_extractAllIntervals, L"TextGrid & Sound: Extract all intervals", 0)
+FORM (TextGrid_Sound_extractAllIntervals, L"TextGrid & Sound: Extract all intervals", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	BOOLEAN (L"Preserve times", 0)
-	OK
+	OK2
 DO
-	TextGrid grid = NULL;
+	TextGrid textgrid = NULL;
 	Sound sound = NULL;
 	LOOP {
-		if (CLASS == classTextGrid) grid = (TextGrid) OBJECT;
+		if (CLASS == classTextGrid) textgrid = (TextGrid) OBJECT;
 		if (CLASS == classSound) sound = (Sound) OBJECT;
 	}
-	autoCollection thee = TextGrid_Sound_extractAllIntervals (grid, sound,
+	autoCollection thee = TextGrid_Sound_extractAllIntervals (textgrid, sound,
 		GET_INTEGER (STRING_TIER_NUMBER), GET_INTEGER (L"Preserve times"));
 	praat_new (thee.transfer(), L"dummy");
-END
+END2 }
 
-FORM (TextGrid_Sound_extractNonemptyIntervals, L"TextGrid & Sound: Extract non-empty intervals", 0)
+FORM (TextGrid_Sound_extractNonemptyIntervals, L"TextGrid & Sound: Extract non-empty intervals", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	BOOLEAN (L"Preserve times", 0)
-	OK
+	OK2
 DO
-	TextGrid grid = NULL;
+	TextGrid textgrid = NULL;
 	Sound sound = NULL;
 	LOOP {
-		if (CLASS == classTextGrid) grid = (TextGrid) OBJECT;
+		if (CLASS == classTextGrid) textgrid = (TextGrid) OBJECT;
 		if (CLASS == classSound) sound = (Sound) OBJECT;
 	}
-	autoCollection thee = TextGrid_Sound_extractNonemptyIntervals (grid, sound,
+	autoCollection thee = TextGrid_Sound_extractNonemptyIntervals (textgrid, sound,
 		GET_INTEGER (STRING_TIER_NUMBER), GET_INTEGER (L"Preserve times"));
 	praat_new (thee.transfer(), L"dummy");
-END
+END2 }
 
-FORM (TextGrid_Sound_extractIntervals, L"TextGrid & Sound: Extract intervals", 0)
+FORM (TextGrid_Sound_extractIntervals, L"TextGrid & Sound: Extract intervals", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	BOOLEAN (L"Preserve times", 0)
 	SENTENCE (L"Label text", L"")
-	OK
+	OK2
 DO
-	TextGrid grid = NULL;
+	TextGrid textgrid = NULL;
 	Sound sound = NULL;
 	LOOP {
-		if (CLASS == classTextGrid) grid = (TextGrid) OBJECT;
+		if (CLASS == classTextGrid) textgrid = (TextGrid) OBJECT;
 		if (CLASS == classSound) sound = (Sound) OBJECT;
 	}
-	autoCollection thee = TextGrid_Sound_extractIntervalsWhere (grid, sound,
+	autoCollection thee = TextGrid_Sound_extractIntervalsWhere (textgrid, sound,
 		GET_INTEGER (STRING_TIER_NUMBER), kMelder_string_EQUAL_TO, GET_STRING (L"Label text"),
 		GET_INTEGER (L"Preserve times"));
 	praat_new (thee.transfer(), GET_STRING (L"Label text"));
-END
+END2 }
 
-FORM (TextGrid_Sound_extractIntervalsWhere, L"TextGrid & Sound: Extract intervals", 0)
+FORM (TextGrid_Sound_extractIntervalsWhere, L"TextGrid & Sound: Extract intervals", 0) {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	BOOLEAN (L"Preserve times", 0)
-	OPTIONMENU_ENUM (L"Extract all intervals whose label...", kMelder_string, DEFAULT)
+	OPTIONMENU_ENUM (L"Extract every interval whose label...", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"")
-	OK
+	OK2
 DO
-	TextGrid grid = NULL;
+	TextGrid textgrid = NULL;
 	Sound sound = NULL;
 	LOOP {
-		if (CLASS == classTextGrid) grid = (TextGrid) OBJECT;
+		if (CLASS == classTextGrid) textgrid = (TextGrid) OBJECT;
 		if (CLASS == classSound) sound = (Sound) OBJECT;
 	}
-	autoCollection thee = TextGrid_Sound_extractIntervalsWhere (grid, sound,
+	autoCollection thee = TextGrid_Sound_extractIntervalsWhere (textgrid, sound,
 		GET_INTEGER (STRING_TIER_NUMBER),
-		GET_ENUM (kMelder_string, L"Extract all intervals whose label..."),
+		GET_ENUM (kMelder_string, L"Extract every interval whose label..."),
 		GET_STRING (L"...the text"),
 		GET_INTEGER (L"Preserve times"));
 	praat_new (thee.transfer(), GET_STRING (L"...the text"));
-END
+END2 }
 
-DIRECT (TextGrid_Sound_scaleTimes)
-	TextGrid grid = NULL;
+DIRECT2 (TextGrid_Sound_scaleTimes) {
+	TextGrid textgrid = NULL;
 	Sound sound = NULL;
 	LOOP {
-		if (CLASS == classTextGrid) grid = (TextGrid) OBJECT;
+		if (CLASS == classTextGrid) textgrid = (TextGrid) OBJECT;
 		if (CLASS == classSound) sound = (Sound) OBJECT;
 	}
-	Function_scaleXTo (grid, sound -> xmin, sound -> xmax);
-	praat_dataChanged (grid);
-END
+	Function_scaleXTo (textgrid, sound -> xmin, sound -> xmax);
+	praat_dataChanged (textgrid);
+END2 }
 
-DIRECT (TextGrid_Sound_cloneTimeDomain)
-	TextGrid grid = NULL;
+DIRECT2 (TextGrid_Sound_cloneTimeDomain) {
+	TextGrid textgrid = NULL;
 	Sound sound = NULL;
 	LOOP {
-		if (CLASS == classTextGrid) grid = (TextGrid) OBJECT;
+		if (CLASS == classTextGrid) textgrid = (TextGrid) OBJECT;
 		if (CLASS == classSound) sound = (Sound) OBJECT;
 	}
-	sound -> x1 += grid -> xmin - sound -> xmin;
-	sound -> xmin = grid -> xmin;
-	sound -> xmax = grid -> xmax;
+	sound -> x1 += textgrid -> xmin - sound -> xmin;
+	sound -> xmin = textgrid -> xmin;
+	sound -> xmax = textgrid -> xmax;
 	praat_dataChanged (sound);
-END
+END2 }
 
 /***** SPELLINGCHECKER *****/
 
-FORM (SpellingChecker_addNewWord, L"Add word to user dictionary", L"SpellingChecker")
+FORM (SpellingChecker_addNewWord, L"Add word to user dictionary", L"SpellingChecker") {
 	SENTENCE (L"New word", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (SpellingChecker);
 		SpellingChecker_addNewWord (me, GET_STRING (L"New word"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (SpellingChecker_edit, L"Edit spelling checker", L"SpellingChecker")
+FORM (SpellingChecker_edit, L"Edit spelling checker", L"SpellingChecker") {
 	LABEL (L"", L"-- Syntax --")
 	SENTENCE (L"Forbidden strings", L"")
 	BOOLEAN (L"Check matching parentheses", 0)
@@ -632,7 +632,7 @@ FORM (SpellingChecker_edit, L"Edit spelling checker", L"SpellingChecker")
 	SENTENCE (L"Allow all words containing", L"")
 	SENTENCE (L"Allow all words starting with", L"")
 	SENTENCE (L"Allow all words ending in", L"")
-	OK
+	OK2
 int IOBJECT;
 LOOP {
 	iam (SpellingChecker);
@@ -666,40 +666,40 @@ DO
 		Melder_free (my allowAllWordsEndingIn); my allowAllWordsEndingIn = Melder_wcsdup_f (GET_STRING (L"Allow all words ending in"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (SpellingChecker_extractWordList)
+DIRECT2 (SpellingChecker_extractWordList) {
 	LOOP {
 		iam (SpellingChecker);
 		autoWordList thee = SpellingChecker_extractWordList (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (SpellingChecker_extractUserDictionary)
+DIRECT2 (SpellingChecker_extractUserDictionary) {
 	LOOP {
 		iam (SpellingChecker);
 		autoSortedSetOfString thee = SpellingChecker_extractUserDictionary (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (SpellingChecker_isWordAllowed, L"Is word allowed?", L"SpellingChecker")
+FORM (SpellingChecker_isWordAllowed, L"Is word allowed?", L"SpellingChecker") {
 	SENTENCE (L"Word", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (SpellingChecker);
 		bool isWordAllowed = SpellingChecker_isWordAllowed (me, GET_STRING (L"Word"));
 		Melder_information (isWordAllowed ? L"1 (allowed)" : L"0 (not allowed)");
 	}
-END
+END2 }
 
-FORM (SpellingChecker_nextNotAllowedWord, L"Next not allowed word?", L"SpellingChecker")
+FORM (SpellingChecker_nextNotAllowedWord, L"Next not allowed word?", L"SpellingChecker") {
 	LABEL (L"", L"Sentence:")
 	TEXTFIELD (L"sentence", L"")
 	INTEGER (L"Starting character", L"0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (SpellingChecker);
@@ -710,9 +710,9 @@ DO
 		wchar_t *nextNotAllowedWord = SpellingChecker_nextNotAllowedWord (me, sentence, & startingCharacter);
 		Melder_information (nextNotAllowedWord);
 	}
-END
+END2 }
 
-DIRECT (SpellingChecker_replaceWordList)
+DIRECT2 (SpellingChecker_replaceWordList) {
 	SpellingChecker spellingChecker = NULL;
 	WordList wordList = NULL;
 	LOOP {
@@ -721,15 +721,15 @@ DIRECT (SpellingChecker_replaceWordList)
 		SpellingChecker_replaceWordList (spellingChecker, wordList);
 		praat_dataChanged (spellingChecker);
 	}
-END
+END2 }
 
-DIRECT (SpellingChecker_replaceWordList_help)
+DIRECT2 (SpellingChecker_replaceWordList_help) {
 	Melder_information (L"To replace the checker's word list\nby the contents of a Strings object:\n"
 		"1. select the Strings;\n2. convert to a WordList object;\n3. select the SpellingChecker and the WordList;\n"
 		"4. choose Replace.");
-END
+END2 }
 
-DIRECT (SpellingChecker_replaceUserDictionary)
+DIRECT2 (SpellingChecker_replaceUserDictionary) {
 	SpellingChecker spellingChecker = NULL;
 	SortedSetOfString dictionary = NULL;
 	LOOP {
@@ -737,28 +737,28 @@ DIRECT (SpellingChecker_replaceUserDictionary)
 		if (CLASS == classSortedSetOfString) dictionary = (SortedSetOfString) OBJECT;
 		SpellingChecker_replaceUserDictionary (spellingChecker, dictionary);
 	}
-END
+END2 }
 
 /***** TEXTGRID *****/
 
-FORM (TextGrid_countLabels, L"Count labels", L"TextGrid: Count labels...")
+FORM (TextGrid_countLabels, L"Count labels", L"TextGrid: Count labels...") {
 	INTEGER (STRING_TIER_NUMBER, L"1")
 	SENTENCE (L"Label text", L"a")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		long numberOfLabels = TextGrid_countLabels (me, GET_INTEGER (STRING_TIER_NUMBER), GET_STRING (L"Label text"));
 		Melder_information (Melder_integer (numberOfLabels), L" labels");
 	}
-END
+END2 }
 
-FORM (TextGrid_downto_Table, L"TextGrid: Down to Table", 0)
+FORM (TextGrid_downto_Table, L"TextGrid: Down to Table", 0) {
 	BOOLEAN (L"Include line number", false)
 	NATURAL (L"Time decimals", L"6")
 	BOOLEAN (L"Include tier names", true)
 	BOOLEAN (L"Include empty intervals", false)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
@@ -766,14 +766,14 @@ DO
 			GET_INTEGER (L"Include tier names"), GET_INTEGER (L"Include empty intervals"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (TextGrid_draw, L"TextGrid: Draw", 0)
+FORM (TextGrid_draw, L"TextGrid: Draw", 0) {
 	praat_dia_timeRange (dia);
 	BOOLEAN (L"Show boundaries", 1)
 	BOOLEAN (L"Use text styles", 1)
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -782,13 +782,13 @@ DO
 			GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Show boundaries"),
 			GET_INTEGER (L"Use text styles"), GET_INTEGER (L"Garnish"));
 	}
-END
+END2 }
 
-FORM (TextGrid_duplicateTier, L"TextGrid: Duplicate tier", 0)
+FORM (TextGrid_duplicateTier, L"TextGrid: Duplicate tier", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (L"Position", L"1 (= at top)")
 	WORD (L"Name", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
@@ -801,7 +801,7 @@ DO
 		Ordered_addItemPos (my tiers, newTier.transfer(), position);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
 static void cb_TextGridEditor_publication (Editor editor, void *closure, Data publication) {
 	(void) editor;
@@ -824,7 +824,7 @@ static void cb_TextGridEditor_publication (Editor editor, void *closure, Data pu
 		Melder_flushError (NULL);
 	}
 }
-DIRECT (TextGrid_edit)
+DIRECT2 (TextGrid_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot view or edit a TextGrid from batch.");
 	Sound sound = NULL;
 	LOOP {
@@ -832,13 +832,13 @@ DIRECT (TextGrid_edit)
 	}
 	LOOP if (CLASS == classTextGrid) {
 		iam (TextGrid);
-		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, sound, true, NULL);
+		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, sound, true, NULL, NULL);
 		editor -> setPublicationCallback (cb_TextGridEditor_publication, NULL);
 		praat_installEditor (editor.transfer(), IOBJECT);
 	}
-END
+END2 }
 
-DIRECT (TextGrid_LongSound_edit)
+DIRECT2 (TextGrid_LongSound_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot view or edit a TextGrid from batch.");
 	LongSound longSound = NULL;
 	int ilongSound = 0;
@@ -848,13 +848,13 @@ DIRECT (TextGrid_LongSound_edit)
 	Melder_assert (ilongSound != 0);
 	LOOP if (CLASS == classTextGrid) {
 		iam (TextGrid);
-		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, longSound, false, NULL);
+		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, longSound, false, NULL, NULL);
 		editor -> setPublicationCallback (cb_TextGridEditor_publication, NULL);
 		praat_installEditor2 (editor.transfer(), IOBJECT, ilongSound);
 	}
-END
+END2 }
 
-DIRECT (TextGrid_SpellingChecker_edit)
+DIRECT2 (TextGrid_SpellingChecker_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot view or edit a TextGrid from batch.");
 	SpellingChecker spellingChecker = NULL;
 	int ispellingChecker = 0;
@@ -866,12 +866,12 @@ DIRECT (TextGrid_SpellingChecker_edit)
 	Melder_assert (ispellingChecker != 0);
 	LOOP if (CLASS == classTextGrid) {
 		iam (TextGrid);
-		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, sound, true, spellingChecker);
+		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, sound, true, spellingChecker, NULL);
 		praat_installEditor2 (editor.transfer(), IOBJECT, ispellingChecker);
 	}
-END
+END2 }
 
-DIRECT (TextGrid_LongSound_SpellingChecker_edit)
+DIRECT2 (TextGrid_LongSound_SpellingChecker_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot view or edit a TextGrid from batch.");
 	LongSound longSound = NULL;
 	SpellingChecker spellingChecker = NULL;
@@ -883,23 +883,23 @@ DIRECT (TextGrid_LongSound_SpellingChecker_edit)
 	Melder_assert (ilongSound != 0 && ispellingChecker != 0);
 	LOOP if (CLASS == classTextGrid) {
 		iam (TextGrid);
-		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, longSound, false, spellingChecker);
+		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, longSound, false, spellingChecker, NULL);
 		praat_installEditor3 (editor.transfer(), IOBJECT, ilongSound, ispellingChecker);
 	}
-END
+END2 }
 
-FORM (TextGrid_extractPart, L"TextGrid: Extract part", 0)
+FORM (TextGrid_extractPart, L"TextGrid: Extract part", 0) {
 	REAL (L"left Time range (s)", L"0.0")
 	REAL (L"right Time range (s)", L"1.0")
 	BOOLEAN (L"Preserve times", 0)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		autoTextGrid thee = TextGrid_extractPart (me, GET_REAL (L"left Time range"), GET_REAL (L"right Time range"), GET_INTEGER (L"Preserve times"));
 		praat_new (thee.transfer(), my name, L"_part");
 	}
-END
+END2 }
 
 static Function pr_TextGrid_peekTier (Any dia) {
 	int IOBJECT;
@@ -940,182 +940,222 @@ static TextPoint pr_TextGrid_peekPoint (Any dia) {
 	return (TextPoint) textTier -> points -> item [pointNumber];
 }
 
-FORM (TextGrid_extractOneTier, L"TextGrid: Extract one tier", 0)
+FORM (TextGrid_extractOneTier, L"TextGrid: Extract one tier", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OK
+	OK2
 DO
 	Function tier = pr_TextGrid_peekTier (dia);   // a reference
 	autoTextGrid grid = TextGrid_createWithoutTiers (1e30, -1e30);
 	TextGrid_addTier (grid.peek(), tier);   // no transfer of tier ownership, because a copy is made
 	praat_new (grid.transfer(), tier -> name);
-END
+END2 }
 
-FORM (TextGrid_extractTier, L"TextGrid: Extract tier", 0)
+FORM (TextGrid_extractTier, L"TextGrid: Extract tier", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OK
+	OK2
 DO
 	Function tier = pr_TextGrid_peekTier (dia);
 	autoFunction thee = Data_copy (tier);
 	praat_new (thee.transfer(), tier -> name);
-END
+END2 }
 
-DIRECT (TextGrid_genericize)
+DIRECT2 (TextGrid_genericize) {
 	LOOP {
 		iam (TextGrid);
 		TextGrid_genericize (me);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (TextGrid_nativize)
+DIRECT2 (TextGrid_nativize) {
 	LOOP {
 		iam (TextGrid);
 		TextGrid_nativize (me);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_getHighIndexFromTime, L"Get high index", L"AnyTier: Get high index from time...")
+FORM (TextGrid_getHighIndexFromTime, L"Get high index", L"AnyTier: Get high index from time...") {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO
 	TextTier textTier = pr_TextGrid_peekTextTier (dia);
 	long highIndex = AnyTier_timeToHighIndex (textTier, GET_REAL (L"Time"));
 	Melder_information (Melder_integer (highIndex));
-END
+END2 }
 
-FORM (TextGrid_getLowIndexFromTime, L"Get low index", L"AnyTier: Get low index from time...")
+FORM (TextGrid_getHighIntervalAtTime, L"TextGrid: Get high interval at time", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
+DO
+	IntervalTier intervalTier = pr_TextGrid_peekIntervalTier (dia);
+	long index = IntervalTier_timeToLowIndex (intervalTier, GET_REAL (L"Time"));
+	Melder_information (Melder_integer (index));
+END2 }
+
+FORM (TextGrid_getIntervalBoundaryFromTime, L"TextGrid: Get interval boundary from time", 0) {
+	NATURAL (STRING_TIER_NUMBER, L"1")
+	REAL (L"Time (s)", L"0.5")
+	OK2
+DO
+	IntervalTier intervalTier = pr_TextGrid_peekIntervalTier (dia);
+	long index = IntervalTier_hasBoundary (intervalTier, GET_REAL (L"Time"));
+	Melder_information (Melder_integer (index));
+END2 }
+
+FORM (TextGrid_getIntervalEdgeFromTime, L"TextGrid: Get interval edge from time", 0) {
+	NATURAL (STRING_TIER_NUMBER, L"1")
+	REAL (L"Time (s)", L"0.5")
+	OK2
+DO
+	IntervalTier intervalTier = pr_TextGrid_peekIntervalTier (dia);
+	long index = IntervalTier_hasTime (intervalTier, GET_REAL (L"Time"));
+	Melder_information (Melder_integer (index));
+END2 }
+
+FORM (TextGrid_getLowIndexFromTime, L"Get low index", L"AnyTier: Get low index from time...") {
+	NATURAL (STRING_TIER_NUMBER, L"1")
+	REAL (L"Time (s)", L"0.5")
+	OK2
 DO
 	TextTier textTier = pr_TextGrid_peekTextTier (dia);
 	long lowIndex = AnyTier_timeToLowIndex (textTier, GET_REAL (L"Time"));
 	Melder_information (Melder_integer (lowIndex));
-END
+END2 }
 
-FORM (TextGrid_getNearestIndexFromTime, L"Get nearest index", L"AnyTier: Get nearest index from time...")
+FORM (TextGrid_getLowIntervalAtTime, L"TextGrid: Get low interval at time", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
+DO
+	IntervalTier intervalTier = pr_TextGrid_peekIntervalTier (dia);
+	long index = IntervalTier_timeToHighIndex (intervalTier, GET_REAL (L"Time"));
+	Melder_information (Melder_integer (index));
+END2 }
+
+FORM (TextGrid_getNearestIndexFromTime, L"Get nearest index", L"AnyTier: Get nearest index from time...") {
+	NATURAL (STRING_TIER_NUMBER, L"1")
+	REAL (L"Time (s)", L"0.5")
+	OK2
 DO
 	TextTier textTier = pr_TextGrid_peekTextTier (dia);
 	long nearestIndex = AnyTier_timeToNearestIndex (textTier, GET_REAL (L"Time"));
 	Melder_information (Melder_integer (nearestIndex));
-END
+END2 }
 
-FORM (TextGrid_getIntervalAtTime, L"TextGrid: Get interval at time", 0)
+FORM (TextGrid_getIntervalAtTime, L"TextGrid: Get interval at time", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO
 	IntervalTier intervalTier = pr_TextGrid_peekIntervalTier (dia);
 	long index = IntervalTier_timeToIndex (intervalTier, GET_REAL (L"Time"));
 	Melder_information (Melder_integer (index));
-END
+END2 }
 
-FORM (TextGrid_getNumberOfIntervals, L"TextGrid: Get number of intervals", 0)
+FORM (TextGrid_getNumberOfIntervals, L"TextGrid: Get number of intervals", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OK
+	OK2
 DO
 	IntervalTier intervalTier = pr_TextGrid_peekIntervalTier (dia);
 	long numberOfIntervals = intervalTier -> intervals -> size;
 	Melder_information (Melder_integer (numberOfIntervals));
-END
+END2 }
 
-DIRECT (TextGrid_getNumberOfTiers)
+DIRECT2 (TextGrid_getNumberOfTiers) {
 	LOOP {
 		iam (TextGrid);
 		long numberOfTiers = my tiers -> size;
 		Melder_information (Melder_integer (numberOfTiers));
 	}
-END
+END2 }
 
-FORM (TextGrid_getStartingPoint, L"TextGrid: Get start point", 0)
+FORM (TextGrid_getStartingPoint, L"TextGrid: Get start point", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_INTERVAL_NUMBER, L"1")
-	OK
+	OK2
 DO
 	TextInterval interval = pr_TextGrid_peekInterval (dia);
 	double startingPoint = interval -> xmin;
 	Melder_informationReal (startingPoint, L"seconds");
-END
+END2 }
 
-FORM (TextGrid_getEndPoint, L"TextGrid: Get end point", 0)
+FORM (TextGrid_getEndPoint, L"TextGrid: Get end point", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_INTERVAL_NUMBER, L"1")
-	OK
+	OK2
 DO
 	TextInterval interval = pr_TextGrid_peekInterval (dia);
 	double endPoint = interval -> xmax;
 	Melder_informationReal (endPoint, L"seconds");
-END
-	
-FORM (TextGrid_getLabelOfInterval, L"TextGrid: Get label of interval", 0)
+END2 }
+
+FORM (TextGrid_getLabelOfInterval, L"TextGrid: Get label of interval", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_INTERVAL_NUMBER, L"1")
-	OK
+	OK2
 DO
 	TextInterval interval = pr_TextGrid_peekInterval (dia);
 	MelderInfo_open ();
 	MelderInfo_write (interval -> text);
 	MelderInfo_close ();
-END
-	
-FORM (TextGrid_getNumberOfPoints, L"TextGrid: Get number of points", 0)
+END2 }
+
+FORM (TextGrid_getNumberOfPoints, L"TextGrid: Get number of points", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OK
+	OK2
 DO
 	TextTier textTier = pr_TextGrid_peekTextTier (dia);
 	long numberOfPoints = textTier -> points -> size;
 	Melder_information (Melder_integer (numberOfPoints));
-END
-	
-FORM (TextGrid_getTierName, L"TextGrid: Get tier name", 0)
+END2 }
+
+FORM (TextGrid_getTierName, L"TextGrid: Get tier name", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OK
+	OK2
 DO
 	Data tier = pr_TextGrid_peekTier (dia);
 	Melder_information (tier -> name);
-END
+END2 }
 
-FORM (TextGrid_getTimeOfPoint, L"TextGrid: Get time of point", 0)
+FORM (TextGrid_getTimeOfPoint, L"TextGrid: Get time of point", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_POINT_NUMBER, L"1")
-	OK
+	OK2
 DO
 	TextPoint point = pr_TextGrid_peekPoint (dia);
 	Melder_informationReal (point -> number, L"seconds");
-END
-	
-FORM (TextGrid_getLabelOfPoint, L"TextGrid: Get label of point", 0)
+END2 }
+
+FORM (TextGrid_getLabelOfPoint, L"TextGrid: Get label of point", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_POINT_NUMBER, L"1")
-	OK
+	OK2
 DO
 	TextPoint point = pr_TextGrid_peekPoint (dia);
 	Melder_information (point -> mark);
-END
-	
-DIRECT (TextGrid_help) Melder_help (L"TextGrid"); END
+END2 }
+
+DIRECT2 (TextGrid_help) { Melder_help (L"TextGrid"); END2 }
 
-FORM (TextGrid_insertBoundary, L"TextGrid: Insert boundary", 0)
+FORM (TextGrid_insertBoundary, L"TextGrid: Insert boundary", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		TextGrid_insertBoundary (me, GET_INTEGER (STRING_TIER_NUMBER), GET_REAL (L"Time"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_insertIntervalTier, L"TextGrid: Insert interval tier", 0)
+FORM (TextGrid_insertIntervalTier, L"TextGrid: Insert interval tier", 0) {
 	NATURAL (L"Position", L"1 (= at top)")
 	WORD (L"Name", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
@@ -1127,26 +1167,26 @@ DO
 		Ordered_addItemPos (my tiers, tier.transfer(), position);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_insertPoint, L"TextGrid: Insert point", 0)
+FORM (TextGrid_insertPoint, L"TextGrid: Insert point", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	REAL (L"Time (s)", L"0.5")
 	LABEL (L"", L"Text:")
 	TEXTFIELD (L"text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		TextGrid_insertPoint (me, GET_INTEGER (STRING_TIER_NUMBER), GET_REAL (L"Time"), GET_STRING (L"text"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_insertPointTier, L"TextGrid: Insert point tier", 0)
+FORM (TextGrid_insertPointTier, L"TextGrid: Insert point tier", 0) {
 	NATURAL (L"Position", L"1 (= at top)")
 	WORD (L"Name", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
@@ -1158,11 +1198,11 @@ DO
 		Ordered_addItemPos (my tiers, tier.transfer(), position);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_isIntervalTier, L"TextGrid: Is interval tier?", 0)
+FORM (TextGrid_isIntervalTier, L"TextGrid: Is interval tier?", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OK
+	OK2
 DO
 	Data tier = pr_TextGrid_peekTier (dia);
 	if (tier -> classInfo == classIntervalTier) {
@@ -1170,114 +1210,152 @@ DO
 	} else {
 		Melder_information (L"0 (no, tier ", Melder_integer (GET_INTEGER (STRING_TIER_NUMBER)), L" is a point tier)");
 	}
-END
+END2 }
 
-FORM (TextGrid_list, L"TextGrid: List", 0)
+FORM (TextGrid_list, L"TextGrid: List", 0) {
 	BOOLEAN (L"Include line number", false)
 	NATURAL (L"Time decimals", L"6")
 	BOOLEAN (L"Include tier names", true)
 	BOOLEAN (L"Include empty intervals", false)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		TextGrid_list (me, GET_INTEGER (L"Include line number"), GET_INTEGER (L"Time decimals"),
 			GET_INTEGER (L"Include tier names"), GET_INTEGER (L"Include empty intervals"));
 	}
-END
+END2 }
 
-DIRECT (TextGrids_concatenate)
+DIRECT2 (TextGrids_concatenate) {
 	autoCollection textGrids = praat_getSelectedObjects ();
 	autoTextGrid thee = TextGrids_concatenate (textGrids.peek());
 	praat_new (thee.transfer(), L"chain");
-END
+END2 }
 
-DIRECT (TextGrids_merge)
+DIRECT2 (TextGrids_merge) {
 	autoCollection textGrids = praat_getSelectedObjects ();
 	autoTextGrid thee = TextGrid_merge (textGrids.peek());
 	praat_new (thee.transfer(), L"merged");
-END
+END2 }
 
-DIRECT (info_TextGrid_Pitch_draw)
+DIRECT2 (info_TextGrid_Pitch_draw) {
 	Melder_information (L"You can draw a TextGrid together with a Pitch after selecting them both.");
-END
+END2 }
 
-FORM (TextGrid_removeBoundaryAtTime, L"TextGrid: Remove boundary at time", 0)
+FORM (TextGrid_removeBoundaryAtTime, L"TextGrid: Remove boundary at time", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	REAL (L"Time (s)", L"0.5")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		TextGrid_removeBoundaryAtTime (me, GET_INTEGER (STRING_TIER_NUMBER), GET_REAL (L"Time"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_getCentrePoints, L"TextGrid: Get centre points", 0)
+FORM (TextGrid_getCentrePoints, L"TextGrid: Get centre points", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OPTIONMENU_ENUM (L"Get centre points where label", kMelder_string, DEFAULT)
+	OPTIONMENU_ENUM (L"Get centre points whose label", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"hi")
-	OK
+	OK2
 DO
 	wchar_t *text = GET_STRING (L"...the text");
 	LOOP {
 		iam (TextGrid);
 		autoPointProcess thee = TextGrid_getCentrePoints (me, GET_INTEGER (STRING_TIER_NUMBER),
-			GET_ENUM (kMelder_string, L"Get centre points where label"), text);
+			GET_ENUM (kMelder_string, L"Get centre points whose label"), text);
 		praat_new (thee.transfer(), my name, L"_", text);
 	}
-END
+END2 }
 
-FORM (TextGrid_getEndPoints, L"TextGrid: Get end points", 0)
+FORM (TextGrid_getEndPoints, L"TextGrid: Get end points", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OPTIONMENU_ENUM (L"Get end points where label", kMelder_string, DEFAULT)
+	OPTIONMENU_ENUM (L"Get end points whose label", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"hi")
-	OK
+	OK2
 DO
 	wchar_t *text = GET_STRING (L"...the text");
 	LOOP {
 		iam (TextGrid);
 		autoPointProcess thee = TextGrid_getEndPoints (me, GET_INTEGER (STRING_TIER_NUMBER),
-			GET_ENUM (kMelder_string, L"Get end points where label"), text);
+			GET_ENUM (kMelder_string, L"Get end points whose label"), text);
 		praat_new (thee.transfer(), my name, L"_", text);
 	}
-END
+END2 }
 
-FORM (TextGrid_getStartingPoints, L"TextGrid: Get starting points", 0)
+FORM (TextGrid_getStartingPoints, L"TextGrid: Get starting points", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OPTIONMENU_ENUM (L"Get starting points where label", kMelder_string, DEFAULT)
+	OPTIONMENU_ENUM (L"Get starting points whose label", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"hi")
-	OK
+	OK2
 DO
 	wchar_t *text = GET_STRING (L"...the text");
 	LOOP {
 		iam (TextGrid);
 		autoPointProcess thee = TextGrid_getStartingPoints (me, GET_INTEGER (STRING_TIER_NUMBER),
-			GET_ENUM (kMelder_string, L"Get starting points where label"), text);
+			GET_ENUM (kMelder_string, L"Get starting points whose label"), text);
 		praat_new (thee.transfer(), my name, L"_", text);
 	}
-END
+END2 }
 
-FORM (TextGrid_getPoints, L"Get points", 0)
+FORM (TextGrid_getPoints, L"Get points", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OPTIONMENU_ENUM (L"Get points where label", kMelder_string, DEFAULT)
+	OPTIONMENU_ENUM (L"Get points whose label", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"hi")
-	OK
+	OK2
 DO
 	wchar_t *text = GET_STRING (L"...the text");
 	LOOP {
 		iam (TextGrid);
 		autoPointProcess thee = TextGrid_getPoints (me, GET_INTEGER (STRING_TIER_NUMBER),
-			GET_ENUM (kMelder_string, L"Get points where label"), text);
+			GET_ENUM (kMelder_string, L"Get points whose label"), text);
+		praat_new (thee.transfer(), my name, L"_", text);
+	}
+END2 }
+
+FORM (TextGrid_getPoints_followed, L"Get points (followed)", 0) {
+	NATURAL (STRING_TIER_NUMBER, L"1")
+	OPTIONMENU_ENUM (L"Get points whose label", kMelder_string, DEFAULT)
+	SENTENCE (L"...the text", L"hi")
+	OPTIONMENU_ENUM (L"followed by a label that", kMelder_string, DEFAULT)
+	SENTENCE (L" ...the text", L"there")
+	OK2
+DO
+	wchar_t *text = GET_STRING (L"...the text");
+	wchar_t *following = GET_STRING (L" ...the text");
+	LOOP {
+		iam (TextGrid);
+		autoPointProcess thee = TextGrid_getPoints_followed (me, GET_INTEGER (STRING_TIER_NUMBER),
+			GET_ENUM (kMelder_string, L"Get points whose label"), text,
+			GET_ENUM (kMelder_string, L"followed by a label that"), following);
+		praat_new (thee.transfer(), my name, L"_", text);
+	}
+END2 }
+
+FORM (TextGrid_getPoints_preceded, L"Get points (preceded)", 0) {
+	NATURAL (STRING_TIER_NUMBER, L"1")
+	OPTIONMENU_ENUM (L"Get points whose label", kMelder_string, DEFAULT)
+	SENTENCE (L"...the text", L"there")
+	OPTIONMENU_ENUM (L"preceded by a label that", kMelder_string, DEFAULT)
+	SENTENCE (L" ...the text", L"hi")
+	OK2
+DO
+	wchar_t *text = GET_STRING (L"...the text");
+	wchar_t *preceding = GET_STRING (L" ...the text");
+	LOOP {
+		iam (TextGrid);
+		autoPointProcess thee = TextGrid_getPoints_preceded (me, GET_INTEGER (STRING_TIER_NUMBER),
+			GET_ENUM (kMelder_string, L"Get points whose label"), text,
+			GET_ENUM (kMelder_string, L"preceded by a label that"), preceding);
 		praat_new (thee.transfer(), my name, L"_", text);
 	}
-END
+END2 }
 
-FORM (TextGrid_removeLeftBoundary, L"TextGrid: Remove left boundary", 0)
+FORM (TextGrid_removeLeftBoundary, L"TextGrid: Remove left boundary", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_INTERVAL_NUMBER, L"2")
-	OK
+	OK2
 DO
 	long itier = GET_INTEGER (STRING_TIER_NUMBER);
 	long iinterval = GET_INTEGER (STRING_INTERVAL_NUMBER);
@@ -1300,12 +1378,12 @@ DO
 		IntervalTier_removeLeftBoundary (intervalTier, iinterval);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_removePoint, L"TextGrid: Remove point", 0)
+FORM (TextGrid_removePoint, L"TextGrid: Remove point", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_POINT_NUMBER, L"2")
-	OK
+	OK2
 DO
 	long itier = GET_INTEGER (STRING_TIER_NUMBER);
 	long ipoint = GET_INTEGER (STRING_POINT_NUMBER);
@@ -1325,12 +1403,25 @@ DO
 		TextTier_removePoint (pointTier, ipoint);
 		praat_dataChanged (me);
 	}
-END
+END2 }
+
+FORM (TextGrid_removePoints, L"Remove points", 0) {
+	NATURAL (STRING_TIER_NUMBER, L"1")
+	OPTIONMENU_ENUM (L"Remove every point whose label...", kMelder_string, DEFAULT)
+	SENTENCE (L"...the text", L"hi")
+	OK2
+DO
+	LOOP {
+		iam (TextGrid);
+		my removePoints (GET_INTEGER (STRING_TIER_NUMBER), GET_ENUM (kMelder_string, L"Remove every point whose label..."), GET_STRING (L"...the text"));
+		praat_dataChanged (me);
+	}
+END2 }
 
-FORM (TextGrid_removeRightBoundary, L"TextGrid: Remove right boundary", 0)
+FORM (TextGrid_removeRightBoundary, L"TextGrid: Remove right boundary", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_INTERVAL_NUMBER, L"1")
-	OK
+	OK2
 DO
 	long itier = GET_INTEGER (STRING_TIER_NUMBER);
 	long iinterval = GET_INTEGER (STRING_INTERVAL_NUMBER);
@@ -1353,11 +1444,11 @@ DO
 		IntervalTier_removeLeftBoundary (intervalTier, iinterval + 1);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_removeTier, L"TextGrid: Remove tier", 0)
+FORM (TextGrid_removeTier, L"TextGrid: Remove tier", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
@@ -1368,55 +1459,55 @@ DO
 		Collection_removeItem (my tiers, itier);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (info_TextGrid_Sound_edit)
+DIRECT2 (info_TextGrid_Sound_edit) {
 	Melder_information (L"To include a copy of a Sound in your TextGrid editor:\n"
 		"   select a TextGrid and a Sound, and click \"View & Edit\".");
-END
+END2 }
 
-DIRECT (info_TextGrid_Sound_draw)
+DIRECT2 (info_TextGrid_Sound_draw) {
 	Melder_information (L"You can draw a TextGrid together with a Sound after selecting them both.");
-END
+END2 }
 
-FORM (TextGrid_setIntervalText, L"TextGrid: Set interval text", 0)
+FORM (TextGrid_setIntervalText, L"TextGrid: Set interval text", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_INTERVAL_NUMBER, L"1")
 	LABEL (L"", L"Text:")
 	TEXTFIELD (L"text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		TextGrid_setIntervalText (me, GET_INTEGER (STRING_TIER_NUMBER), GET_INTEGER (STRING_INTERVAL_NUMBER), GET_STRING (L"text"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TextGrid_setPointText, L"TextGrid: Set point text", 0)
+FORM (TextGrid_setPointText, L"TextGrid: Set point text", 0) {
 	NATURAL (STRING_TIER_NUMBER, L"1")
 	NATURAL (STRING_POINT_NUMBER, L"1")
 	LABEL (L"", L"Text:")
 	TEXTFIELD (L"text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextGrid);
 		TextGrid_setPointText (me, GET_INTEGER (STRING_TIER_NUMBER), GET_INTEGER (STRING_POINT_NUMBER), GET_STRING (L"text"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM_WRITE (TextGrid_writeToChronologicalTextFile, L"Text file", 0, 0)
+FORM_WRITE2 (TextGrid_writeToChronologicalTextFile, L"Text file", 0, 0) {
 	LOOP {
 		iam (TextGrid);
 		TextGrid_writeToChronologicalTextFile (me, file);
 	}
-END
+END2 }
 
 /***** TEXTGRID & ANYTIER *****/
 
-DIRECT (TextGrid_AnyTier_append)
+DIRECT2 (TextGrid_AnyTier_append) {
 	TextGrid oldGrid = NULL;
 	LOOP {
 		if (CLASS == classTextGrid) oldGrid = (TextGrid) OBJECT;
@@ -1427,11 +1518,11 @@ DIRECT (TextGrid_AnyTier_append)
 		TextGrid_addTier (newGrid.peek(), me);
 	}
 	praat_new (newGrid.transfer(), oldGrid -> name);
-END
+END2 }
 
 /***** TEXTGRID & LONGSOUND *****/
 
-DIRECT (TextGrid_LongSound_scaleTimes)
+DIRECT2 (TextGrid_LongSound_scaleTimes) {
 	TextGrid grid = NULL;
 	LongSound longSound = NULL;
 	LOOP {
@@ -1440,52 +1531,52 @@ DIRECT (TextGrid_LongSound_scaleTimes)
 	}
 	Function_scaleXTo (grid, longSound -> xmin, longSound -> xmax);
 	praat_dataChanged (grid);
-END
+END2 }
 
 /***** TEXTTIER *****/
 
-FORM (TextTier_addPoint, L"TextTier: Add point", L"TextTier: Add point...")
+FORM (TextTier_addPoint, L"TextTier: Add point", L"TextTier: Add point...") {
 	REAL (L"Time (s)", L"0.5")
 	SENTENCE (L"Text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextTier);
 		TextTier_addPoint (me, GET_REAL (L"Time"), GET_STRING (L"Text"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (TextTier_downto_PointProcess)
+DIRECT2 (TextTier_downto_PointProcess) {
 	LOOP {
 		iam (TextTier);
 		autoPointProcess thee = AnyTier_downto_PointProcess (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (TextTier_downto_TableOfReal, L"TextTier: Down to TableOfReal", 0)
+FORM (TextTier_downto_TableOfReal, L"TextTier: Down to TableOfReal", 0) {
 	SENTENCE (L"Label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextTier);
 		autoTableOfReal thee = TextTier_downto_TableOfReal (me, GET_STRING (L"Label"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (TextTier_downto_TableOfReal_any)
+DIRECT2 (TextTier_downto_TableOfReal_any) {
 	LOOP {
 		iam (TextTier);
 		autoTableOfReal thee = TextTier_downto_TableOfReal_any (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (TextTier_getLabelOfPoint, L"Get label of point", 0)
+FORM (TextTier_getLabelOfPoint, L"Get label of point", 0) {
 	NATURAL (L"Point number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextTier);
@@ -1494,49 +1585,49 @@ DO
 		TextPoint point = (TextPoint) my points -> item [ipoint];
 		Melder_information (point -> mark);
 	}
-END
+END2 }
 
-FORM (TextTier_getPoints, L"Get points", 0)
+FORM (TextTier_getPoints, L"Get points", 0) {
 	SENTENCE (L"Text", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TextTier);
 		autoPointProcess thee = TextTier_getPoints (me, GET_STRING (L"Text"));
 		praat_new (thee.transfer(), GET_STRING (L"Text"));
 	}
-END
+END2 }
 
-DIRECT (TextTier_help) Melder_help (L"TextTier"); END
+DIRECT2 (TextTier_help) { Melder_help (L"TextTier"); END2 }
 
 /***** WORDLIST *****/
 
-FORM (WordList_hasWord, L"Does word occur in list?", L"WordList")
+FORM (WordList_hasWord, L"Does word occur in list?", L"WordList") {
 	SENTENCE (L"Word", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (WordList);
 		bool hasWord = WordList_hasWord (me, GET_STRING (L"Word"));
 		Melder_information (hasWord ? L"1" : L"0");
 	}
-END
+END2 }
 
-DIRECT (WordList_to_Strings)
+DIRECT2 (WordList_to_Strings) {
 	LOOP {
 		iam (WordList);
 		autoStrings thee = WordList_to_Strings (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-DIRECT (WordList_upto_SpellingChecker)
+DIRECT2 (WordList_upto_SpellingChecker) {
 	LOOP {
 		iam (WordList);
 		autoSpellingChecker thee = WordList_upto_SpellingChecker (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
 /***** buttons *****/
 
@@ -1602,11 +1693,17 @@ void praat_uvafon_TextGrid_init (void) {
 			praat_addAction1 (classTextGrid, 1, L"Get starting point...", 0, praat_HIDDEN + praat_DEPTH_2, DO_TextGrid_getStartingPoint);   // hidden 2008
 			praat_addAction1 (classTextGrid, 1, L"Get end point...", 0, 2, DO_TextGrid_getEndPoint);
 			praat_addAction1 (classTextGrid, 1, L"Get label of interval...", 0, 2, DO_TextGrid_getLabelOfInterval);
+			praat_addAction1 (classTextGrid, 1, L"-- query interval from time --", 0, 2, 0);
 			praat_addAction1 (classTextGrid, 1, L"Get interval at time...", 0, 2, DO_TextGrid_getIntervalAtTime);
+			praat_addAction1 (classTextGrid, 1, L"Get low interval at time...", 0, 2, DO_TextGrid_getLowIntervalAtTime);
+			praat_addAction1 (classTextGrid, 1, L"Get high interval at time...", 0, 2, DO_TextGrid_getHighIntervalAtTime);
+			praat_addAction1 (classTextGrid, 1, L"Get interval edge from time...", 0, 2, DO_TextGrid_getIntervalEdgeFromTime);
+			praat_addAction1 (classTextGrid, 1, L"Get interval boundary from time...", 0, 2, DO_TextGrid_getIntervalBoundaryFromTime);
 		praat_addAction1 (classTextGrid, 1, L"Query point tier", 0, 1, 0);
 			praat_addAction1 (classTextGrid, 1, L"Get number of points...", 0, 2, DO_TextGrid_getNumberOfPoints);
 			praat_addAction1 (classTextGrid, 1, L"Get time of point...", 0, 2, DO_TextGrid_getTimeOfPoint);
 			praat_addAction1 (classTextGrid, 1, L"Get label of point...", 0, 2, DO_TextGrid_getLabelOfPoint);
+			praat_addAction1 (classTextGrid, 1, L"-- query point from time --", 0, 2, 0);
 			praat_addAction1 (classTextGrid, 1, L"Get low index from time...", 0, 2, DO_TextGrid_getLowIndexFromTime);
 			praat_addAction1 (classTextGrid, 1, L"Get high index from time...", 0, 2, DO_TextGrid_getHighIndexFromTime);
 			praat_addAction1 (classTextGrid, 1, L"Get nearest index from time...", 0, 2, DO_TextGrid_getNearestIndexFromTime);
@@ -1633,6 +1730,7 @@ void praat_uvafon_TextGrid_init (void) {
 		praat_addAction1 (classTextGrid, 0, L"Modify point tier", 0, 1, 0);
 			praat_addAction1 (classTextGrid, 0, L"Insert point...", 0, 2, DO_TextGrid_insertPoint);
 			praat_addAction1 (classTextGrid, 0, L"Remove point...", 0, 2, DO_TextGrid_removePoint);
+			praat_addAction1 (classTextGrid, 0, L"Remove points...", 0, 2, DO_TextGrid_removePoints);
 			praat_addAction1 (classTextGrid, 0, L"Set point text...", 0, 2, DO_TextGrid_setPointText);
 praat_addAction1 (classTextGrid, 0, L"Analyse", 0, 0, 0);
 	praat_addAction1 (classTextGrid, 1, L"Extract one tier...", 0, 0, DO_TextGrid_extractOneTier);
@@ -1644,6 +1742,8 @@ praat_addAction1 (classTextGrid, 0, L"Analyse", 0, 0, 0);
 		praat_addAction1 (classTextGrid, 1, L"Get centre points...", 0, 1, DO_TextGrid_getCentrePoints);
 	praat_addAction1 (classTextGrid, 1, L"Analyse point tier -", 0, 0, 0);
 		praat_addAction1 (classTextGrid, 1, L"Get points...", 0, 1, DO_TextGrid_getPoints);
+		praat_addAction1 (classTextGrid, 1, L"Get points (preceded)...", 0, 1, DO_TextGrid_getPoints_preceded);
+		praat_addAction1 (classTextGrid, 1, L"Get points (followed)...", 0, 1, DO_TextGrid_getPoints_followed);
 praat_addAction1 (classTextGrid, 0, L"Synthesize", 0, 0, 0);
 	praat_addAction1 (classTextGrid, 0, L"Merge", 0, 0, DO_TextGrids_merge);
 	praat_addAction1 (classTextGrid, 0, L"Concatenate", 0, 0, DO_TextGrids_concatenate);
diff --git a/gram/Makefile b/gram/Makefile
index 89ec503..c977453 100644
--- a/gram/Makefile
+++ b/gram/Makefile
@@ -1,5 +1,5 @@
 # Makefile of the library "gram"
-# Paul Boersma, 24 August 2013
+# Paul Boersma, 26 July 2014
 
 include ../makefile.defs
 
@@ -7,7 +7,8 @@ CPPFLAGS = -I ../num -I ../kar -I ../sys -I ../dwsys -I ../stat -I ../dwtools -I
 
 OBJECTS = Network.o \
    OTGrammar.o OTGrammarEditor.o manual_gram.o praat_gram.o OTMulti.o OTMultiEditor.o \
-   OTGrammar_ex_metrics.o OTGrammar_ex_NoCoda.o OTGrammar_ex_NPA.o OTGrammar_ex_tongueRoot.o
+   OTGrammar_ex_metrics.o OTGrammar_ex_NoCoda.o OTGrammar_ex_NPA.o OTGrammar_ex_tongueRoot.o \
+   OTMulti_ex_metrics.o
 
 .PHONY: all clean
 
diff --git a/gram/OTGrammar.cpp b/gram/OTGrammar.cpp
index c4e9430..0e19b4a 100644
--- a/gram/OTGrammar.cpp
+++ b/gram/OTGrammar.cpp
@@ -1,6 +1,6 @@
 /* OTGrammar.cpp
  *
- * Copyright (C) 1997-2012 Paul Boersma
+ * Copyright (C) 1997-2012,2014 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
@@ -69,6 +69,8 @@
  * pb 2011/03/22 C++
  * pb 2011/04/27 Melder_debug 41 and 42
  * pb 2011/07/14 C++
+ * pb 2014/02/27 skippable symmetric all
+ * pb 2014/07/25 RRIP
  */
 
 #include "OTGrammar.h"
@@ -651,6 +653,16 @@ bool OTGrammar_isPartialOutputGrammatical (OTGrammar me, const wchar_t *partialO
 	return false;
 }
 
+bool OTGrammar_areAllPartialOutputsGrammatical (OTGrammar me, Strings thee) {
+	for (long ioutput = 1; ioutput <= thy numberOfStrings; ioutput ++) {
+		const wchar_t *partialOutput = thy strings [ioutput];
+		if (! OTGrammar_isPartialOutputGrammatical (me, partialOutput)) {
+			return false;
+		}
+	}
+	return true;
+}
+
 bool OTGrammar_isPartialOutputSinglyGrammatical (OTGrammar me, const wchar_t *partialOutput) {
 	bool found = false;
 	for (long itab = 1; itab <= my numberOfTableaus; itab ++) {
@@ -676,6 +688,16 @@ bool OTGrammar_isPartialOutputSinglyGrammatical (OTGrammar me, const wchar_t *pa
 	return found;
 }
 
+bool OTGrammar_areAllPartialOutputsSinglyGrammatical (OTGrammar me, Strings thee) {
+	for (long ioutput = 1; ioutput <= thy numberOfStrings; ioutput ++) {
+		const wchar_t *partialOutput = thy strings [ioutput];
+		if (! OTGrammar_isPartialOutputSinglyGrammatical (me, partialOutput)) {
+			return false;
+		}
+	}
+	return true;
+}
+
 static int OTGrammar_crucialCell (OTGrammar me, long itab, long icand, long iwinner, long numberOfOptimalCandidates) {
 	int icons;
 	OTGrammarTableau tableau = & my tableaus [itab];
@@ -1231,13 +1253,13 @@ static void OTGrammar_honourLocalRankings (OTGrammar me, double plasticity, doub
 	} while (improved);
 }
 
-static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, long iloser,
+static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, long iadult,
 	int updateRule, int honourLocalRankings,
 	double plasticity, double relativePlasticityNoise, int warnIfStalled, int *grammarHasChanged)
 {
 	try {
 		OTGrammarTableau tableau = & my tableaus [itab];
-		OTGrammarCandidate winner = & tableau -> candidates [iwinner], loser = & tableau -> candidates [iloser];
+		OTGrammarCandidate winner = & tableau -> candidates [iwinner], adult = & tableau -> candidates [iadult];
 		double step = learningStep (plasticity, relativePlasticityNoise);
 		bool multiplyStepByNumberOfViolations =
 			my decisionStrategy == kOTGrammar_decisionStrategy_HARMONIC_GRAMMAR ||
@@ -1258,14 +1280,14 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			OTGrammarConstraint constraint = & my constraints [icons];
 			double constraintStep = step * constraint -> plasticity;
 			int winnerMarks = winner -> marks [icons];
-			int loserMarks = loser -> marks [icons];
-			if (loserMarks > winnerMarks) {
-				if (multiplyStepByNumberOfViolations) constraintStep *= loserMarks - winnerMarks;
+			int adultMarks = adult -> marks [icons];
+			if (adultMarks > winnerMarks) {
+				if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
 				constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
 				if (grammarHasChanged != NULL) *grammarHasChanged = true;
 			}
-			if (winnerMarks > loserMarks) {
-				if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+			if (winnerMarks > adultMarks) {
+				if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 				constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
 				if (grammarHasChanged != NULL) *grammarHasChanged = true;
 			}
@@ -1275,14 +1297,51 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 				OTGrammarConstraint constraint = & my constraints [icons];
 				double constraintStep = step * constraint -> plasticity;
 				int winnerMarks = winner -> marks [icons];
-				int loserMarks = loser -> marks [icons];
-				if (loserMarks > winnerMarks) {
-					if (multiplyStepByNumberOfViolations) constraintStep *= loserMarks - winnerMarks;
+				int adultMarks = adult -> marks [icons];
+				if (adultMarks > winnerMarks) {
+					if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
 					constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
 					changed = true;
 				}
-				if (winnerMarks > loserMarks) {
-					if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+				if (winnerMarks > adultMarks) {
+					if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
+					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
+					changed = true;
+				}
+			}
+			if (changed && my decisionStrategy == kOTGrammar_decisionStrategy_EXPONENTIAL_HG)
+			{
+				double sumOfWeights = 0.0;
+				for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+					sumOfWeights += my constraints [icons]. ranking;
+				}
+				double averageWeight = sumOfWeights / my numberOfConstraints;
+				for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+					my constraints [icons]. ranking -= averageWeight;
+				}
+			}
+			if (grammarHasChanged != NULL) *grammarHasChanged = changed;
+		} else if (updateRule == kOTGrammar_rerankingStrategy_SYMMETRIC_ALL_SKIPPABLE) {
+			bool changed = false;
+			int winningConstraints = 0, adultConstraints = 0;
+			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+				int winnerMarks = winner -> marks [icons];
+				int adultMarks = adult -> marks [icons];
+				if (adultMarks > winnerMarks) adultConstraints ++;
+				if (winnerMarks > adultMarks) winningConstraints ++;
+			}
+			if (winningConstraints != 0) for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+				OTGrammarConstraint constraint = & my constraints [icons];
+				double constraintStep = step * constraint -> plasticity;
+				int winnerMarks = winner -> marks [icons];
+				int adultMarks = adult -> marks [icons];
+				if (adultMarks > winnerMarks) {
+					if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
+					constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
+					changed = true;
+				}
+				if (winnerMarks > adultMarks) {
+					if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
 					changed = true;
 				}
@@ -1300,51 +1359,51 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			}
 			if (grammarHasChanged != NULL) *grammarHasChanged = changed;
 		} else if (updateRule == kOTGrammar_rerankingStrategy_WEIGHTED_UNCANCELLED) {
-			int winningConstraints = 0, losingConstraints = 0;
+			int winningConstraints = 0, adultConstraints = 0;
 			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				int winnerMarks = winner -> marks [icons];
-				int loserMarks = loser -> marks [icons];
-				if (loserMarks > winnerMarks) losingConstraints ++;
-				if (winnerMarks > loserMarks) winningConstraints ++;
+				int adultMarks = adult -> marks [icons];
+				if (adultMarks > winnerMarks) adultConstraints ++;
+				if (winnerMarks > adultMarks) winningConstraints ++;
 			}
 			if (winningConstraints != 0) for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				OTGrammarConstraint constraint = & my constraints [icons];
 				double constraintStep = step * constraint -> plasticity;
 				int winnerMarks = winner -> marks [icons];
-				int loserMarks = loser -> marks [icons];
-				if (loserMarks > winnerMarks) {
-					if (multiplyStepByNumberOfViolations) constraintStep *= loserMarks - winnerMarks;
-					constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / losingConstraints;
+				int adultMarks = adult -> marks [icons];
+				if (adultMarks > winnerMarks) {
+					if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
+					constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / adultConstraints;
 					//constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) * winningConstraints;
 					if (grammarHasChanged != NULL) *grammarHasChanged = true;
 				}
-				if (winnerMarks > loserMarks) {
-					if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+				if (winnerMarks > adultMarks) {
+					if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
-					//constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * losingConstraints;
+					//constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * adultConstraints;
 					if (grammarHasChanged != NULL) *grammarHasChanged = true;
 				}
 			}
 		} else if (updateRule == kOTGrammar_rerankingStrategy_WEIGHTED_ALL) {
-			int winningConstraints = 0, losingConstraints = 0;
+			int winningConstraints = 0, adultConstraints = 0;
 			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				int winnerMarks = winner -> marks [icons];
-				int loserMarks = loser -> marks [icons];
-				if (loserMarks > 0) losingConstraints ++;
+				int adultMarks = adult -> marks [icons];
+				if (adultMarks > 0) adultConstraints ++;
 				if (winnerMarks > 0) winningConstraints ++;
 			}
 			if (winningConstraints != 0) for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				OTGrammarConstraint constraint = & my constraints [icons];
 				double constraintStep = step * constraint -> plasticity;
 				int winnerMarks = winner -> marks [icons];
-				int loserMarks = loser -> marks [icons];
-				if (loserMarks > 0) {
-					if (multiplyStepByNumberOfViolations) constraintStep *= loserMarks /*- winnerMarks*/;
-					constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / losingConstraints;
+				int adultMarks = adult -> marks [icons];
+				if (adultMarks > 0) {
+					if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks /*- winnerMarks*/;
+					constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / adultConstraints;
 					if (grammarHasChanged != NULL) *grammarHasChanged = true;
 				}
 				if (winnerMarks > 0) {
-					if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks /*- loserMarks*/;
+					if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks /*- adultMarks*/;
 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
 					if (grammarHasChanged != NULL) *grammarHasChanged = true;
 				}
@@ -1358,15 +1417,15 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			long icons = 1;
 			for (; icons <= my numberOfConstraints; icons ++) {
 				int winnerMarks = winner -> marks [my index [icons]];   // the order is important, therefore indirect
-				int loserMarks = loser -> marks [my index [icons]];
-				if (loserMarks < winnerMarks) break;
-				if (loserMarks > winnerMarks) equivalent = false;
+				int adultMarks = adult -> marks [my index [icons]];
+				if (adultMarks < winnerMarks) break;
+				if (adultMarks > winnerMarks) equivalent = false;
 			}
 			if (icons > my numberOfConstraints) {   // completed the loop?
 				if (warnIfStalled && ! equivalent)
 					Melder_warning ("Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
-						"Input: ", tableau -> input, "\nCorrect output: ", loser -> output, "\nLearner's output: ", winner -> output);
-				return;
+						"Input: ", tableau -> input, "\nCorrect output: ", adult -> output, "\nLearner's output: ", winner -> output);
+				return;   // Tesar & Smolensky (2000: 67): "stopped dead in its tracks"
 			}
 			/*
 			 * Determine the stratum into which some constraints will be demoted.
@@ -1376,8 +1435,8 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 				long numberOfConstraintsToDemote = 0;
 				for (icons = 1; icons <= my numberOfConstraints; icons ++) {
 					int winnerMarks = winner -> marks [icons];
-					int loserMarks = loser -> marks [icons];
-					if (loserMarks > winnerMarks) {
+					int adultMarks = adult -> marks [icons];
+					if (adultMarks > winnerMarks) {
 						OTGrammarConstraint constraint = & my constraints [icons];
 						if (constraint -> ranking >= pivotRanking) {
 							numberOfConstraintsToDemote += 1;
@@ -1395,14 +1454,14 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 				}
 			}
 			/*
-			 * Demote all the uniquely violated constraints in the loser
+			 * Demote all the uniquely violated constraints in the adult form
 			 * that have rankings not lower than the pivot.
 			 */
 			for (icons = 1; icons <= my numberOfConstraints; icons ++) {
 				long numberOfConstraintsDemoted = 0;
 				int winnerMarks = winner -> marks [my index [icons]];   // for the vacation version, the order is important, therefore indirect
-				int loserMarks = loser -> marks [my index [icons]];
-				if (loserMarks > winnerMarks) {
+				int adultMarks = adult -> marks [my index [icons]];
+				if (adultMarks > winnerMarks) {
 					OTGrammarConstraint constraint = & my constraints [my index [icons]];
 					double constraintStep = step * constraint -> plasticity;
 					if (constraint -> ranking >= pivotRanking) {
@@ -1414,27 +1473,27 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			}
 		} else if (updateRule == kOTGrammar_rerankingStrategy_DEMOTION_ONLY) {
 			/*
-			 * Determine the crucial loser mark.
+			 * Determine the crucial adult mark.
 			 */
-			long crucialLoserMark;
+			long crucialAdultMark;
 			OTGrammarConstraint offendingConstraint;
 			long icons = 1;
 			for (; icons <= my numberOfConstraints; icons ++) {
-				int winnerMarks = winner -> marks [my index [icons]];   /* Order is important, so indirect. */
-				int loserMarks = loser -> marks [my index [icons]];
+				int winnerMarks = winner -> marks [my index [icons]];   // the order is important, so we indirect
+				int adultMarks = adult -> marks [my index [icons]];
 				if (my constraints [my index [icons]]. tiedToTheRight)
 					Melder_throw ("Demotion-only learning cannot handle tied constraints.");
-				if (loserMarks < winnerMarks)
-					Melder_throw ("Demotion-only learning step: Loser wins! Should never happen.");
-				if (loserMarks > winnerMarks) break;
+				if (adultMarks < winnerMarks)
+					Melder_throw ("Demotion-only learning step: Adult form wins! Should never happen.");
+				if (adultMarks > winnerMarks) break;
 			}
 			if (icons > my numberOfConstraints)   // completed the loop?
-				Melder_throw ("Loser equals correct candidate.");
-			crucialLoserMark = icons;
+				Melder_throw ("Adult form equals correct candidate.");
+			crucialAdultMark = icons;
 			/*
-			 * Demote the highest uniquely violated constraint in the loser.
+			 * Demote the highest uniquely violated constraint in the adult form.
 			 */
-			offendingConstraint = & my constraints [my index [crucialLoserMark]];
+			offendingConstraint = & my constraints [my index [crucialAdultMark]];
 			double constraintStep = step * offendingConstraint -> plasticity;
 			offendingConstraint -> ranking -= constraintStep;
 			if (grammarHasChanged != NULL) *grammarHasChanged = true;
@@ -1442,8 +1501,8 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			long numberOfUp = 0;
 			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				int winnerMarks = winner -> marks [icons];
-				int loserMarks = loser -> marks [icons];
-				if (winnerMarks > loserMarks) {
+				int adultMarks = adult -> marks [icons];
+				if (winnerMarks > adultMarks) {
 					numberOfUp ++;
 				}
 			}
@@ -1452,34 +1511,34 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 					OTGrammarConstraint constraint = & my constraints [icons];
 					double constraintStep = step * constraint -> plasticity;
 					int winnerMarks = winner -> marks [icons];
-					int loserMarks = loser -> marks [icons];
-					if (winnerMarks > loserMarks) {
-						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+					int adultMarks = adult -> marks [icons];
+					if (winnerMarks > adultMarks) {
+						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 						constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / numberOfUp;
 						if (grammarHasChanged != NULL) *grammarHasChanged = true;
 					}
 				}
-				long crucialLoserMark, winnerMarks = 0, loserMarks = 0;
+				long crucialAdultMark, winnerMarks = 0, adultMarks = 0;
 				OTGrammarConstraint offendingConstraint;
 				long icons = 1;
 				for (; icons <= my numberOfConstraints; icons ++) {
 					winnerMarks = winner -> marks [my index [icons]];   // the order is important, therefore indirect
-					loserMarks = loser -> marks [my index [icons]];
+					adultMarks = adult -> marks [my index [icons]];
 					if (my constraints [my index [icons]]. tiedToTheRight)
 						Melder_throw ("Demotion-only learning cannot handle tied constraints.");
-					if (loserMarks < winnerMarks)
-						Melder_throw ("Demotion-only learning step: Loser wins! Should never happen.");
-					if (loserMarks > winnerMarks) break;
+					if (adultMarks < winnerMarks)
+						Melder_throw ("Demotion-only learning step: Adult form wins! Should never happen.");
+					if (adultMarks > winnerMarks) break;
 				}
 				if (icons > my numberOfConstraints)   // completed the loop?
-					Melder_throw ("Loser equals correct candidate.");
-				crucialLoserMark = icons;
+					Melder_throw ("Adult form equals correct candidate.");
+				crucialAdultMark = icons;
 				/*
-				 * Demote the highest uniquely violated constraint in the loser.
+				 * Demote the highest uniquely violated constraint in the adult form.
 				 */
-				offendingConstraint = & my constraints [my index [crucialLoserMark]];
+				offendingConstraint = & my constraints [my index [crucialAdultMark]];
 				double constraintStep = step * offendingConstraint -> plasticity;
-				if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+				if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 				offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
 				if (grammarHasChanged != NULL) *grammarHasChanged = true;
 			}
@@ -1487,8 +1546,8 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			long numberOfUp = 0;
 			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				int winnerMarks = winner -> marks [icons];
-				int loserMarks = loser -> marks [icons];
-				if (winnerMarks > loserMarks) {
+				int adultMarks = adult -> marks [icons];
+				if (winnerMarks > adultMarks) {
 					numberOfUp ++;
 				}
 			}
@@ -1497,34 +1556,34 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 					OTGrammarConstraint constraint = & my constraints [icons];
 					double constraintStep = step * constraint -> plasticity;
 					int winnerMarks = winner -> marks [icons];
-					int loserMarks = loser -> marks [icons];
-					if (winnerMarks > loserMarks) {
-						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+					int adultMarks = adult -> marks [icons];
+					if (winnerMarks > adultMarks) {
+						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 						constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / (numberOfUp + 1);
 						if (grammarHasChanged != NULL) *grammarHasChanged = true;
 					}
 				}
-				long crucialLoserMark, winnerMarks = 0, loserMarks = 0;
+				long crucialAdultMark, winnerMarks = 0, adultMarks = 0;
 				OTGrammarConstraint offendingConstraint;
 				long icons = 1;
 				for (; icons <= my numberOfConstraints; icons ++) {
 					winnerMarks = winner -> marks [my index [icons]];   // the order is important, therefore indirect
-					loserMarks = loser -> marks [my index [icons]];
+					adultMarks = adult -> marks [my index [icons]];
 					if (my constraints [my index [icons]]. tiedToTheRight)
 						Melder_throw ("Demotion-only learning cannot handle tied constraints.");
-					if (loserMarks < winnerMarks)
-						Melder_throw ("Demotion-only learning step: Loser wins! Should never happen.");
-					if (loserMarks > winnerMarks) break;
+					if (adultMarks < winnerMarks)
+						Melder_throw ("Demotion-only learning step: Adult form wins! Should never happen.");
+					if (adultMarks > winnerMarks) break;
 				}
 				if (icons > my numberOfConstraints)   // completed the loop?
-					Melder_throw ("Loser equals correct candidate.");
-				crucialLoserMark = icons;
+					Melder_throw ("Adult form equals correct candidate.");
+				crucialAdultMark = icons;
 				/*
-				 * Demote the highest uniquely violated constraint in the loser.
+				 * Demote the highest uniquely violated constraint in the adult form.
 				 */
-				offendingConstraint = & my constraints [my index [crucialLoserMark]];
+				offendingConstraint = & my constraints [my index [crucialAdultMark]];
 				double constraintStep = step * offendingConstraint -> plasticity;
-				if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+				if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 				offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
 				if (grammarHasChanged != NULL) *grammarHasChanged = true;
 			}
@@ -1532,10 +1591,10 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			long numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
 			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				int winnerMarks = winner -> marks [my index [icons]];   // the order is important, therefore indirect
-				int loserMarks = loser -> marks [my index [icons]];
-				if (loserMarks < winnerMarks) {
+				int adultMarks = adult -> marks [my index [icons]];
+				if (adultMarks < winnerMarks) {
 					numberOfUp ++;
-				} else if (loserMarks > winnerMarks) {
+				} else if (adultMarks > winnerMarks) {
 					if (numberOfUp == 0) {
 						numberOfDown ++;
 						lowestDemotableConstraint = icons;
@@ -1544,7 +1603,7 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			}
 			if (warnIfStalled && numberOfDown == 0) {
 				Melder_warning ("Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
-					"Input: ", tableau -> input, "\nCorrect output: ", loser -> output, "\nLearner's output: ", winner -> output);
+					"Input: ", tableau -> input, "\nCorrect output: ", adult -> output, "\nLearner's output: ", winner -> output);
 				return;
 			}
 			if (numberOfUp > 0) {
@@ -1553,15 +1612,15 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 					OTGrammarConstraint constraint = & my constraints [constraintIndex];
 					double constraintStep = step * constraint -> plasticity;
 					int winnerMarks = winner -> marks [constraintIndex];   // the order is important, therefore indirect
-					int loserMarks = loser -> marks [constraintIndex];
+					int adultMarks = adult -> marks [constraintIndex];
 					if (my constraints [constraintIndex]. tiedToTheRight)
 						Melder_throw ("Demotion-only learning cannot handle tied constraints.");
-					if (loserMarks < winnerMarks) {
-						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+					if (adultMarks < winnerMarks) {
+						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 						constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 0.0);
-					} else if (loserMarks > winnerMarks) {
+					} else if (adultMarks > winnerMarks) {
 						if (icons <= lowestDemotableConstraint) {
-							if (multiplyStepByNumberOfViolations) constraintStep *= loserMarks - winnerMarks;
+							if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
 							constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
 						}
 					}
@@ -1572,10 +1631,10 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			long numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
 			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 				int winnerMarks = winner -> marks [my index [icons]];   // the order is important, therefore indirect
-				int loserMarks = loser -> marks [my index [icons]];
-				if (loserMarks < winnerMarks) {
+				int adultMarks = adult -> marks [my index [icons]];
+				if (adultMarks < winnerMarks) {
 					numberOfUp ++;
-				} else if (loserMarks > winnerMarks) {
+				} else if (adultMarks > winnerMarks) {
 					if (numberOfUp == 0) {
 						numberOfDown ++;
 						lowestDemotableConstraint = icons;
@@ -1584,7 +1643,7 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 			}
 			if (warnIfStalled && numberOfDown == 0) {
 				Melder_warning ("Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
-					"Input: ", tableau -> input, "\nCorrect output: ", loser -> output, "\nLearner's output: ", winner -> output);
+					"Input: ", tableau -> input, "\nCorrect output: ", adult -> output, "\nLearner's output: ", winner -> output);
 				return;
 			}
 			if (numberOfUp > 0) {
@@ -1593,15 +1652,15 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 					OTGrammarConstraint constraint = & my constraints [constraintIndex];
 					double constraintStep = step * constraint -> plasticity;
 					int winnerMarks = winner -> marks [constraintIndex];   // the order is important, therefore indirect
-					int loserMarks = loser -> marks [constraintIndex];
+					int adultMarks = adult -> marks [constraintIndex];
 					if (my constraints [constraintIndex]. tiedToTheRight)
 						Melder_throw ("Demotion-only learning cannot handle tied constraints.");
-					if (loserMarks < winnerMarks) {
-						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+					if (adultMarks < winnerMarks) {
+						if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
 						constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 1.0);
-					} else if (loserMarks > winnerMarks) {
+					} else if (adultMarks > winnerMarks) {
 						if (icons <= lowestDemotableConstraint) {
-							if (multiplyStepByNumberOfViolations) constraintStep *= loserMarks - winnerMarks;
+							if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
 							constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
 						}
 					}
@@ -1617,7 +1676,7 @@ static void OTGrammar_modifyRankings (OTGrammar me, long itab, long iwinner, lon
 	}
 }
 
-void OTGrammar_learnOne (OTGrammar me, const wchar_t *underlyingForm, const wchar_t *adultOutput,
+void OTGrammar_learnOne (OTGrammar me, const wchar_t *input, const wchar_t *adultOutput,
 	double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, int honourLocalRankings,
 	double plasticity, double relativePlasticityNoise, int newDisharmonies, int warnIfStalled, int *grammarHasChanged)
 {
@@ -1628,7 +1687,7 @@ void OTGrammar_learnOne (OTGrammar me, const wchar_t *underlyingForm, const wcha
 		/*
 		 * Evaluate the input in the learner's hypothesis.
 		 */
-		long itab = OTGrammar_getTableau (me, underlyingForm);
+		long itab = OTGrammar_getTableau (me, input);
 		OTGrammarTableau tableau = & my tableaus [itab];
 
 		/*
@@ -1644,25 +1703,25 @@ void OTGrammar_learnOne (OTGrammar me, const wchar_t *underlyingForm, const wcha
 		if (wcsequ (winner -> output, adultOutput)) return;   // as far as we know, the grammar is already correct: don't update rankings
 
 		/*
-		 * Find (perhaps the learner's interpretation of) the adult winner (the "loser") in the learner's own tableau
+		 * Find (perhaps the learner's interpretation of) the adult output in the learner's own tableau
 		 * (Tesar & Smolensky call this the "winner").
 		 */
-		long iloser = 1;
-		for (; iloser <= tableau -> numberOfCandidates; iloser ++) {
-			OTGrammarCandidate loser = & tableau -> candidates [iloser];
-			if (wcsequ (loser -> output, adultOutput)) break;
+		long iadult = 1;
+		for (; iadult <= tableau -> numberOfCandidates; iadult ++) {
+			OTGrammarCandidate cand = & tableau -> candidates [iadult];
+			if (wcsequ (cand -> output, adultOutput)) break;
 		}
-		if (iloser > tableau -> numberOfCandidates)
+		if (iadult > tableau -> numberOfCandidates)
 			Melder_throw ("Cannot generate adult output \"", adultOutput, L"\".");
 
 		/*
 		 * Now we know that the current hypothesis prefers the (wrong) learner's winner over the (correct) adult output.
 		 * The grammar will have to change.
 		 */
-		OTGrammar_modifyRankings (me, itab, iwinner, iloser, updateRule, honourLocalRankings,
+		OTGrammar_modifyRankings (me, itab, iwinner, iadult, updateRule, honourLocalRankings,
 			plasticity, relativePlasticityNoise, warnIfStalled, grammarHasChanged);
 	} catch (MelderError) {
-		Melder_throw (me, ": not learned from input \"", underlyingForm, "\" and adult output \"", adultOutput, "\".");
+		Melder_throw (me, ": not learned from input \"", input, "\" and adult output \"", adultOutput, "\".");
 	}
 }
 
@@ -1821,12 +1880,26 @@ void OTGrammar_reset (OTGrammar me, double ranking) {
 	OTGrammar_sort (me);
 }
 
+void OTGrammar_resetToRandomRanking (OTGrammar me, double mean, double standardDeviation) {
+	for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+		OTGrammarConstraint constraint = & my constraints [my index [icons]];
+		constraint -> disharmony = constraint -> ranking = NUMrandomGauss (mean, standardDeviation);
+	}
+	OTGrammar_sort (me);
+}
+
 void OTGrammar_resetToRandomTotalRanking (OTGrammar me, double maximumRanking, double rankingDistance) {
+	/*
+	 * First put the constraints in a random order and build a random index.
+	 */
 	for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 		OTGrammarConstraint constraint = & my constraints [icons];
 		constraint -> ranking = 0.0;
 	}
 	OTGrammar_newDisharmonies (me, 1.0);
+	/*
+	 * Then use the random index to yield a cascade of rankings.
+	 */
 	for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
 		OTGrammarConstraint constraint = & my constraints [my index [icons]];
 		constraint -> disharmony = constraint -> ranking = maximumRanking - (icons - 1) * rankingDistance;
@@ -1909,7 +1982,7 @@ void OTGrammar_learnOneFromPartialOutput (OTGrammar me, const wchar_t *partialAd
 				my tableaus [assumedAdultInputTableau]. input,
 				my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output,
 				evaluationNoise, updateRule, honourLocalRankings,
-				plasticity, relativePlasticityNoise, FALSE, warnIfStalled, & grammarHasChanged);
+				plasticity, relativePlasticityNoise, Melder_debug == 47, warnIfStalled, & grammarHasChanged);
 			if (! grammarHasChanged) return;
 		}
 		if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy_EDCD && ichew > numberOfChews) {
@@ -1933,9 +2006,10 @@ void OTGrammar_learnOneFromPartialOutput (OTGrammar me, const wchar_t *partialAd
 	}
 }
 
-static void OTGrammar_learnOneFromPartialOutput_opt (OTGrammar me, long ipartialAdultOutput,
+static void OTGrammar_learnOneFromPartialOutput_opt (OTGrammar me, const wchar_t *partialAdultOutput, long ipartialAdultOutput,
 	double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, int honourLocalRankings,
-	double plasticity, double relativePlasticityNoise, long numberOfChews, int warnIfStalled)
+	double plasticity, double relativePlasticityNoise, long numberOfChews, int warnIfStalled,
+	bool resampleForVirtualProduction, bool compareOnlyPartialOutput)
 {
 	try {
 		OTGrammar_newDisharmonies (me, evaluationNoise);
@@ -1946,12 +2020,45 @@ static void OTGrammar_learnOneFromPartialOutput_opt (OTGrammar me, long ipartial
 		for (; ichew <= numberOfChews; ichew ++) {
 			long assumedAdultInputTableau, assumedAdultCandidate;
 			OTGrammar_getInterpretiveParse_opt (me, ipartialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
-			int grammarHasChanged;
-			OTGrammar_learnOne (me,
-				my tableaus [assumedAdultInputTableau]. input,
-				my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output,
-				evaluationNoise, updateRule, honourLocalRankings,
-				plasticity, relativePlasticityNoise, FALSE, warnIfStalled, & grammarHasChanged);
+			OTGrammarTableau tableau = & my tableaus [assumedAdultInputTableau];
+			OTGrammarCandidate assumedCorrect = & tableau -> candidates [assumedAdultCandidate];
+
+			/*
+			 * Determine the "winner", i.e. the candidate that wins in the learner's grammar
+			 * (Tesar & Smolensky call this the "loser").
+			 */
+			if (resampleForVirtualProduction) OTGrammar_newDisharmonies (me, evaluationNoise);
+			long iwinner = OTGrammar_getWinner (me, assumedAdultInputTableau);
+			OTGrammarCandidate winner = & tableau -> candidates [iwinner];
+
+			/*
+			 * Error-driven: compare the adult winner (the correct candidate) and the learner's winner.
+			 */
+			if (compareOnlyPartialOutput) {
+				if (wcsstr (winner -> output, partialAdultOutput)) return;   // as far as we know, the grammar is already correct: don't update rankings
+			} else {
+				if (wcsequ (winner -> output, assumedCorrect -> output)) return;   // as far as we know, the grammar is already correct: don't update rankings
+			}
+
+			/*
+			 * Find (perhaps the learner's interpretation of) the adult output in the learner's own tableau
+			 * (Tesar & Smolensky call this the "winner").
+			 */
+			long iadult = 1;
+			for (; iadult <= tableau -> numberOfCandidates; iadult ++) {
+				OTGrammarCandidate cand = & tableau -> candidates [iadult];
+				if (wcsequ (cand -> output, assumedCorrect -> output)) break;
+			}
+			if (iadult > tableau -> numberOfCandidates)
+				Melder_throw ("Cannot generate adult output \"", assumedCorrect -> output, L"\".");
+
+			/*
+			 * Now we know that the current hypothesis prefers the (wrong) learner's winner over the (correct) adult output.
+			 * The grammar will have to change.
+			 */
+			int grammarHasChanged = FALSE;
+			OTGrammar_modifyRankings (me, assumedAdultInputTableau, iwinner, iadult, updateRule, honourLocalRankings,
+				plasticity, relativePlasticityNoise, warnIfStalled, & grammarHasChanged);
 			if (! grammarHasChanged) return;
 		}
 		if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy_EDCD && ichew > numberOfChews) {
@@ -1971,7 +2078,7 @@ static void OTGrammar_learnOneFromPartialOutput_opt (OTGrammar me, long ipartial
 			}
 		}
 	} catch (MelderError) {
-		Melder_throw (me, ": not learned from partial adult output ", ipartialAdultOutput, ".");
+		Melder_throw (me, ": not learned from partial adult output ", partialAdultOutput, ".");
 	}
 }
 
@@ -2117,7 +2224,8 @@ void OTGrammar_Distributions_learnFromPartialOutputs (OTGrammar me, Distribution
 	double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, int honourLocalRankings,
 	double initialPlasticity, long replicationsPerPlasticity, double plasticityDecrement,
 	long numberOfPlasticities, double relativePlasticityNoise, long numberOfChews,
-	long storeHistoryEvery, OTHistory *history_out)
+	long storeHistoryEvery, OTHistory *history_out,
+	bool resampleForVirtualProduction, bool compareOnlyPartialOutput)
 {
 	long idatum = 0;
 	const long numberOfData = numberOfPlasticities * replicationsPerPlasticity;
@@ -2135,8 +2243,9 @@ void OTGrammar_Distributions_learnFromPartialOutputs (OTGrammar me, Distribution
 			double plasticity = initialPlasticity;
 			for (long iplasticity = 1; iplasticity <= numberOfPlasticities; iplasticity ++) {
 				for (long ireplication = 1; ireplication <= replicationsPerPlasticity; ireplication ++) {
+					wchar_t *partialOutput;
 					long ipartialOutput;
-					Distributions_peek_opt (thee, columnNumber, & ipartialOutput);
+					Distributions_peek (thee, columnNumber, & partialOutput, & ipartialOutput);
 					++ idatum;
 					if (monitor.graphics() && idatum % (numberOfData / 400 + 1) == 0) {
 						Graphics_setWindow (monitor.graphics(), 0, numberOfData, 50, 150);
@@ -2151,9 +2260,10 @@ void OTGrammar_Distributions_learnFromPartialOutputs (OTGrammar me, Distribution
 						L"Processing partial output ", Melder_integer (idatum), L" out of ", Melder_integer (numberOfData), L": ",
 						thy rowLabels [ipartialOutput]);
 					try {
-						OTGrammar_learnOneFromPartialOutput_opt (me, ipartialOutput,
+						OTGrammar_learnOneFromPartialOutput_opt (me, partialOutput, ipartialOutput,
 							evaluationNoise, updateRule, honourLocalRankings,
-							plasticity, relativePlasticityNoise, numberOfChews, FALSE);   // no warning if stalled: RIP form is allowed to be harmonically bounded
+							plasticity, relativePlasticityNoise, numberOfChews, FALSE,
+							resampleForVirtualProduction, compareOnlyPartialOutput);   // no warning if stalled: RIP form is allowed to be harmonically bounded
 					} catch (MelderError) {
 						if (history.peek()) {
 							OTGrammar_updateHistory (me, history.peek(), storeHistoryEvery, idatum, thy rowLabels [ipartialOutput]);
@@ -2240,7 +2350,7 @@ double OTGrammar_Distributions_getFractionCorrect (OTGrammar me, Distributions t
 		OTGrammar_Distributions_opt_createOutputMatching (me, thee, columnNumber);
 		for (long ireplication = 1; ireplication <= numberOfInputs; ireplication ++) {
 			long ipartialOutput;
-			Distributions_peek_opt (thee, columnNumber, & ipartialOutput);
+			Distributions_peek (thee, columnNumber, NULL, & ipartialOutput);
 			OTGrammar_newDisharmonies (me, evaluationNoise);
 			long assumedAdultInputTableau, assumedAdultCandidate;
 			OTGrammar_getInterpretiveParse_opt (me, ipartialOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
@@ -2694,7 +2804,7 @@ void OTGrammar_Distributions_listObligatoryRankings (OTGrammar me, Distributions
 				Melder_progressOff ();
 				OTGrammar_Distributions_learnFromPartialOutputs (me, thee, columnNumber,
 					1e-9, kOTGrammar_rerankingStrategy_EDCD, TRUE /* honour fixed rankings; very important */,
-					1.0, 1000, 0.0, 1, 0.0, 1, 0, NULL);
+					1.0, 1000, 0.0, 1, 0.0, 1, 0, NULL, false, false);
 				Melder_progressOn ();
 				for (kcons = 1; kcons <= my numberOfConstraints; kcons ++) {
 					if (my constraints [kcons]. ranking < 0.0) {
diff --git a/gram/OTGrammar.h b/gram/OTGrammar.h
index e52172c..a6f8266 100644
--- a/gram/OTGrammar.h
+++ b/gram/OTGrammar.h
@@ -2,7 +2,7 @@
 #define _OTGrammar_h_
 /* OTGrammar.h
  *
- * Copyright (C) 1997-2011 Paul Boersma
+ * Copyright (C) 1997-2011,2014 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
@@ -77,6 +77,8 @@ bool OTGrammar_isPartialOutputGrammatical (OTGrammar me, const wchar_t *partialO
 	/* Is there an input for which this partial output is contained in any of the optimal outputs? */
 bool OTGrammar_isPartialOutputSinglyGrammatical (OTGrammar me, const wchar_t *partialOutput);
 	/* Is every optimal output that contains this partial output the only optimal output in its tableau? */
+bool OTGrammar_areAllPartialOutputsGrammatical (OTGrammar me, Strings thee);
+bool OTGrammar_areAllPartialOutputsSinglyGrammatical (OTGrammar me, Strings thee);
 
 void OTGrammar_drawTableau (OTGrammar me, Graphics g, bool vertical, const wchar_t *input);
 
@@ -111,7 +113,8 @@ void OTGrammar_Distributions_learnFromPartialOutputs (OTGrammar me, Distribution
 	double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, int honourLocalRankings,
 	double initialPlasticity, long replicationsPerPlasticity, double plasticityDecrement,
 	long numberOfPlasticities, double relativePlasticityNoise, long numberOfChews,
-	long storeHistoryEvery, OTHistory *history_out);
+	long storeHistoryEvery, OTHistory *history_out,
+	bool resampleForVirtualProduction, bool compareOnlyPartialOutput);
 double OTGrammar_PairDistribution_getFractionCorrect (OTGrammar me, PairDistribution thee,
 	double evaluationNoise, long numberOfInputs);
 long OTGrammar_PairDistribution_getMinimumNumberCorrect (OTGrammar me, PairDistribution thee,
@@ -131,6 +134,7 @@ OTGrammar OTGrammar_create_metrics (int equal_footForm_wsp,
 	/* T&S: 1, FALSE, FALSE, FALSE, 1, TRUE, FALSE, FALSE */
 
 void OTGrammar_reset (OTGrammar me, double ranking);
+void OTGrammar_resetToRandomRanking (OTGrammar me, double mean, double standardDeviation);
 void OTGrammar_resetToRandomTotalRanking (OTGrammar me, double maximumRanking, double rankingDistance);
 void OTGrammar_setRanking (OTGrammar me, long constraint, double ranking, double disharmony);
 void OTGrammar_setConstraintPlasticity (OTGrammar me, long constraint, double plasticity);
diff --git a/gram/OTGrammar_enums.h b/gram/OTGrammar_enums.h
index b5398f0..b1bb4b7 100644
--- a/gram/OTGrammar_enums.h
+++ b/gram/OTGrammar_enums.h
@@ -1,6 +1,6 @@
 /* OTGrammar_enums.h
  *
- * Copyright (C) 2006-2011,2013 Paul Boersma
+ * Copyright (C) 2006-2011,2013,2014 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
@@ -31,14 +31,15 @@ enums_begin (kOTGrammar_rerankingStrategy, 0)
 	enums_add (kOTGrammar_rerankingStrategy, 0, DEMOTION_ONLY, L"Demotion only")
 	enums_add (kOTGrammar_rerankingStrategy, 1, SYMMETRIC_ONE, L"Symmetric one")
 	enums_add (kOTGrammar_rerankingStrategy, 2, SYMMETRIC_ALL, L"Symmetric all")
-	enums_add (kOTGrammar_rerankingStrategy, 3, WEIGHTED_UNCANCELLED, L"Weighted uncancelled")
-	enums_add (kOTGrammar_rerankingStrategy, 4, WEIGHTED_ALL, L"Weighted all")
-	enums_add (kOTGrammar_rerankingStrategy, 5, EDCD, L"EDCD")
-	enums_add (kOTGrammar_rerankingStrategy, 6, EDCD_WITH_VACATION, L"EDCD with vacation")
-	enums_add (kOTGrammar_rerankingStrategy, 7, WEIGHTED_ALL_UP_HIGHEST_DOWN, L"Weighted all up, highest down")
-	enums_add (kOTGrammar_rerankingStrategy, 8, WEIGHTED_ALL_UP_HIGHEST_DOWN_2012, L"Weighted all up, highest down (2012)")
-	enums_add (kOTGrammar_rerankingStrategy, 9, WEIGHTED_ALL_UP_HIGH_DOWN, L"Weighted all up, high down")
-	enums_add (kOTGrammar_rerankingStrategy, 10, WEIGHTED_ALL_UP_HIGH_DOWN_2012, L"Weighted all up, high down (2012)")
-enums_end (kOTGrammar_rerankingStrategy, 10, SYMMETRIC_ALL)
+	enums_add (kOTGrammar_rerankingStrategy, 3, SYMMETRIC_ALL_SKIPPABLE, L"Symmetric all (skippable)")
+	enums_add (kOTGrammar_rerankingStrategy, 4, WEIGHTED_UNCANCELLED, L"Weighted uncancelled")
+	enums_add (kOTGrammar_rerankingStrategy, 5, WEIGHTED_ALL, L"Weighted all")
+	enums_add (kOTGrammar_rerankingStrategy, 6, EDCD, L"EDCD")
+	enums_add (kOTGrammar_rerankingStrategy, 7, EDCD_WITH_VACATION, L"EDCD with vacation")
+	enums_add (kOTGrammar_rerankingStrategy, 8, WEIGHTED_ALL_UP_HIGHEST_DOWN, L"Weighted all up, highest down")
+	enums_add (kOTGrammar_rerankingStrategy, 9, WEIGHTED_ALL_UP_HIGHEST_DOWN_2012, L"Weighted all up, highest down (2012)")
+	enums_add (kOTGrammar_rerankingStrategy, 10, WEIGHTED_ALL_UP_HIGH_DOWN, L"Weighted all up, high down")
+	enums_add (kOTGrammar_rerankingStrategy, 11, WEIGHTED_ALL_UP_HIGH_DOWN_2012, L"Weighted all up, high down (2012)")
+enums_end (kOTGrammar_rerankingStrategy, 11, SYMMETRIC_ALL)
 
 /* End of file OTGrammar_enums.h */
diff --git a/gram/OTGrammar_ex_metrics.cpp b/gram/OTGrammar_ex_metrics.cpp
index 687c079..848f580 100644
--- a/gram/OTGrammar_ex_metrics.cpp
+++ b/gram/OTGrammar_ex_metrics.cpp
@@ -1,6 +1,6 @@
 /* OTGrammar_ex_metrics.cpp
  *
- * Copyright (C) 2001-2011 Paul Boersma
+ * Copyright (C) 2001-2011,2014 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
@@ -441,7 +441,6 @@ OTGrammar OTGrammar_create_metrics (int equal_footForm_wsp, int trochaicityConst
 	int includeClashAndLapse, int includeCodas)
 {
 	try {
-		int numberOfSyllables;
 		int underlyingWeightPattern [1+7], maximumUnderlyingWeight = includeCodas ? 3 : 2;
 		long numberOfTableaus = includeCodas ? 9 + 27 + 81 + 243 + 2 : 62;
 		autoOTGrammar me = Thing_new (OTGrammar);
@@ -463,7 +462,7 @@ OTGrammar OTGrammar_create_metrics (int equal_footForm_wsp, int trochaicityConst
 			my constraints [WSP]. ranking = 102.0;
 		}
 		my tableaus = NUMvector <structOTGrammarTableau> (1, numberOfTableaus);
-		for (numberOfSyllables = 2; numberOfSyllables <= 7; numberOfSyllables ++) {
+		for (int numberOfSyllables = 2; numberOfSyllables <= 7; numberOfSyllables ++) {
 			long numberOfUnderlyingWeightPatterns = numberOfSyllables > 5 ? 1 : (long) floor (pow (maximumUnderlyingWeight, numberOfSyllables) + 0.5);
 			for (long isyll = 1; isyll <= numberOfSyllables; isyll ++) {
 				underlyingWeightPattern [isyll] = 1;   /* L or cv */
diff --git a/gram/OTMulti.cpp b/gram/OTMulti.cpp
index cbcd606..245ec20 100644
--- a/gram/OTMulti.cpp
+++ b/gram/OTMulti.cpp
@@ -451,6 +451,42 @@ static void OTMulti_modifyRankings (OTMulti me, long iwinner, long iloser,
 			}
 		}
 		if (grammarHasChanged != NULL) *grammarHasChanged = changed;
+	} else if (updateRule == kOTGrammar_rerankingStrategy_SYMMETRIC_ALL_SKIPPABLE) {
+		bool changed = false;
+		int winningConstraints = 0, losingConstraints = 0;
+		for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+			int winnerMarks = winner -> marks [icons];
+			int loserMarks = loser -> marks [icons];
+			if (loserMarks > winnerMarks) losingConstraints ++;
+			if (winnerMarks > loserMarks) winningConstraints ++;
+		}
+		if (winningConstraints != 0) for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+			OTConstraint constraint = & my constraints [icons];
+			double constraintStep = step * constraint -> plasticity;
+			int winnerMarks = winner -> marks [icons];
+			int loserMarks = loser -> marks [icons];
+			if (loserMarks > winnerMarks) {
+				if (multiplyStepByNumberOfViolations) constraintStep *= loserMarks - winnerMarks;
+				constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
+				changed = true;
+			}
+			if (winnerMarks > loserMarks) {
+				if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - loserMarks;
+				constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
+				changed = true;
+			}
+		}
+		if (changed && my decisionStrategy == kOTGrammar_decisionStrategy_EXPONENTIAL_HG) {
+			double sumOfWeights = 0.0;
+			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+				sumOfWeights += my constraints [icons]. ranking;
+			}
+			double averageWeight = sumOfWeights / my numberOfConstraints;
+			for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
+				my constraints [icons]. ranking -= averageWeight;
+			}
+		}
+		if (grammarHasChanged != NULL) *grammarHasChanged = changed;
 	} else if (updateRule == kOTGrammar_rerankingStrategy_WEIGHTED_UNCANCELLED) {
 		int winningConstraints = 0, losingConstraints = 0;
 		for (long icons = 1; icons <= my numberOfConstraints; icons ++) {
@@ -793,12 +829,16 @@ int OTMulti_learnOne (OTMulti me, const wchar_t *form1, const wchar_t *form2,
 {
 	long iloser = OTMulti_getWinner (me, form1, form2);
 	if (direction & OTMulti_LEARN_FORWARD) {
+		if (Melder_debug == 47) OTMulti_newDisharmonies (me, 2.0);
 		long iwinner = OTMulti_getWinner (me, form1, L"");
-		OTMulti_modifyRankings (me, iwinner, iloser, updateRule, plasticity, relativePlasticityNoise);
+		if (Melder_debug != 47 || ! OTMulti_candidateMatches (me, iwinner, form2, L""))
+			OTMulti_modifyRankings (me, iwinner, iloser, updateRule, plasticity, relativePlasticityNoise);
 	}
 	if (direction & OTMulti_LEARN_BACKWARD) {
+		if (Melder_debug == 47) OTMulti_newDisharmonies (me, 2.0);
 		long iwinner = OTMulti_getWinner (me, form2, L"");
-		OTMulti_modifyRankings (me, iwinner, iloser, updateRule, plasticity, relativePlasticityNoise);
+		if (Melder_debug != 47 || ! OTMulti_candidateMatches (me, iwinner, form1, L""))
+			OTMulti_modifyRankings (me, iwinner, iloser, updateRule, plasticity, relativePlasticityNoise);
 	}
 	return 1;
 }
diff --git a/gram/OTMulti.h b/gram/OTMulti.h
index 52a37d1..4f01065 100644
--- a/gram/OTMulti.h
+++ b/gram/OTMulti.h
@@ -2,7 +2,7 @@
 #define _OTMulti_h_
 /* OTMulti.h
  *
- * Copyright (C) 2005-2011 Paul Boersma
+ * Copyright (C) 2005-2011,2014 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
@@ -56,6 +56,10 @@ void OTMulti_PairDistribution_learn (OTMulti me, PairDistribution thee,
 
 void OTMulti_drawTableau (OTMulti me, Graphics g, const wchar_t *form1, const wchar_t *form2, bool vertical, bool showDisharmonies);
 
+OTMulti OTMulti_create_metrics (int equal_footForm_wsp, int trochaicityConstraint, int includeFootBimoraic, int includeFootBisyllabic,
+	int includePeripheral, int nonfinalityConstraint, int overtFormsHaveSecondaryStress,
+	int includeClashAndLapse, int includeCodas);
+
 void OTMulti_reset (OTMulti me, double ranking);
 void OTMulti_setRanking (OTMulti me, long constraint, double ranking, double disharmony);
 void OTMulti_setConstraintPlasticity (OTMulti me, long constraint, double plasticity);
diff --git a/gram/OTGrammar_ex_metrics.cpp b/gram/OTMulti_ex_metrics.cpp
similarity index 69%
copy from gram/OTGrammar_ex_metrics.cpp
copy to gram/OTMulti_ex_metrics.cpp
index 687c079..04b40e1 100644
--- a/gram/OTGrammar_ex_metrics.cpp
+++ b/gram/OTMulti_ex_metrics.cpp
@@ -1,6 +1,6 @@
-/* OTGrammar_ex_metrics.cpp
+/* OTMulti_ex_metrics.cpp
  *
- * Copyright (C) 2001-2011 Paul Boersma
+ * Copyright (C) 2014 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,23 +17,7 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-/*
- * pb 2001/11/25
- * pb 2002/07/16 GPL
- * pb 2003/03/31 removeConstraint
- * pb 2003/05/09 added Peripheral and MainNonfinal
- * pb 2004/01/14 added HeadNonfinal
- * pb 2004/01/25 added overtFormsHaveSecondaryStress
- * pb 2004/07/27 added Clash and Lapse
- * pb 2004/08/11 complete rewrite in order to include WeightByPosition and *MoraicConsonant
- * pb 2004/12/03 corrected *Lapse
- * pb 2007/07/23 constraint plasticity
- * pb 2007/08/12 wchar_t
- * pb 2011/03/29 C++
- * pb 2011/06/29 C++
- */
-
-#include "OTGrammar.h"
+#include "OTMulti.h"
 
 #define WSP  1
 #define FtNonfinal  2
@@ -66,31 +50,32 @@ static const wchar_t *constraintNames [1+NUMBER_OF_CONSTRAINTS] = { 0,
 	L"WSP", L"FtNonfinal", L"Iambic", L"Parse", L"FootBin", L"WFL", L"WFR", L"Main-L", L"Main-R", L"AFL", L"AFR", L"Nonfinal",
 	L"Trochaic", L"FtBimor", L"FtBisyl", L"Peripheral", L"MainNonfinal", L"HeadNonfinal", L"*Clash", L"*Lapse", L"WeightByPosition", L"*C\\mu" };
 
-static void addCandidate (OTGrammarTableau me, long numberOfSyllables, int stress [],
+static void addCandidate (OTMulti me, const wchar_t *underlyingForm, long numberOfSyllables, int stress [],
 	int footedToTheLeft [], int footedToTheRight [], int surfaceWeightPattern [],
 	int overtFormsHaveSecondaryStress)
 {
 	static const wchar_t *syllable [] = { L"L", L"L1", L"L2", L"H", L"H1", L"H2", L"K", L"K1", L"K2", L"J", L"J1", L"J2" };
 	static const wchar_t *syllableWithoutSecondaryStress [] = { L"L", L"L1", L"L", L"H", L"H1", L"H", L"K", L"K1", L"K", L"J", L"J1", L"J" };
-	wchar_t output [100];
-	wcscpy (output, L"[");
+	wchar_t string [150];
+	wcscpy (string, underlyingForm);
+	wcscat (string, L" /");
 	for (long isyll = 1; isyll <= numberOfSyllables; isyll ++) {
-		if (isyll > 1) wcscat (output, L" ");
-		wcscat (output, ( overtFormsHaveSecondaryStress ? syllable : syllableWithoutSecondaryStress )
-				[stress [isyll] + 3 * (surfaceWeightPattern [isyll] - 1)]);
+		if (isyll > 1) wcscat (string, L" ");
+		if (footedToTheRight [isyll] || (! footedToTheLeft [isyll] && stress [isyll] != 0)) wcscat (string, L"(");
+		wcscat (string, syllable [stress [isyll] + 3 * (surfaceWeightPattern [isyll] - 1)]);
+		if (footedToTheLeft [isyll] || (! footedToTheRight [isyll] && stress [isyll] != 0)) wcscat (string, L")");
 	}
-	wcscat (output, L"] \\-> /");
+	wcscat (string, L"/ [");
 	for (long isyll = 1; isyll <= numberOfSyllables; isyll ++) {
-		if (isyll > 1) wcscat (output, L" ");
-		if (footedToTheRight [isyll] || (! footedToTheLeft [isyll] && stress [isyll] != 0)) wcscat (output, L"(");
-		wcscat (output, syllable [stress [isyll] + 3 * (surfaceWeightPattern [isyll] - 1)]);
-		if (footedToTheLeft [isyll] || (! footedToTheRight [isyll] && stress [isyll] != 0)) wcscat (output, L")");
+		if (isyll > 1) wcscat (string, L" ");
+		wcscat (string, ( overtFormsHaveSecondaryStress ? syllable : syllableWithoutSecondaryStress )
+				[stress [isyll] + 3 * (surfaceWeightPattern [isyll] - 1)]);
 	}
-	wcscat (output, L"/");
-	my candidates [++ my numberOfCandidates]. output = Melder_wcsdup (output);
+	wcscat (string, L"]");
+	my candidates [++ my numberOfCandidates]. string = Melder_wcsdup (string);
 }
 
-static void fillSurfaceWeightPattern (OTGrammarTableau me, long numberOfSyllables, int stress [],
+static void fillSurfaceWeightPattern (OTMulti me, const wchar_t *underlyingForm, long numberOfSyllables, int stress [],
 	int footedToTheLeft [], int footedToTheRight [], int underlyingWeightPattern [],
 	int overtFormsHaveSecondaryStress)
 {
@@ -109,18 +94,18 @@ static void fillSurfaceWeightPattern (OTGrammarTableau me, long numberOfSyllable
 		for (weight2 = minSurfaceWeight [2]; weight2 <= maxSurfaceWeight [2]; weight2 ++) {
 			surfaceWeightPattern [2] = weight2;
 			if (numberOfSyllables == 2) {
-				addCandidate (me, 2, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
+				addCandidate (me, underlyingForm, 2, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
 			} else for (weight3 = minSurfaceWeight [3]; weight3 <= maxSurfaceWeight [3]; weight3 ++) {
 				surfaceWeightPattern [3] = weight3;
 				if (numberOfSyllables == 3) {
-					addCandidate (me, 3, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
+					addCandidate (me, underlyingForm, 3, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
 				} else for (weight4 = minSurfaceWeight [4]; weight4 <= maxSurfaceWeight [4]; weight4 ++) {
 					surfaceWeightPattern [4] = weight4;
 					if (numberOfSyllables == 4) {
-						addCandidate (me, 4, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
+						addCandidate (me, underlyingForm, 4, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
 					} else for (weight5 = minSurfaceWeight [5]; weight5 <= maxSurfaceWeight [5]; weight5 ++) {
 						surfaceWeightPattern [5] = weight5;
-						addCandidate (me, numberOfSyllables, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
+						addCandidate (me, underlyingForm, numberOfSyllables, stress, footedToTheLeft, footedToTheRight, surfaceWeightPattern, overtFormsHaveSecondaryStress);
 					}
 				}
 			}
@@ -128,7 +113,7 @@ static void fillSurfaceWeightPattern (OTGrammarTableau me, long numberOfSyllable
 	}
 }
 
-static void path (OTGrammarTableau me, long numberOfSyllables, int stress [],
+static void path (OTMulti me, const wchar_t *underlyingForm, long numberOfSyllables, int stress [],
 	int startingSyllable, int footedToTheLeft_in [], int footedToTheRight_in [], int underlyingWeightPattern [],
 	int overtFormsHaveSecondaryStress)
 {
@@ -142,14 +127,14 @@ static void path (OTGrammarTableau me, long numberOfSyllables, int stress [],
 	for (isyll = startingSyllable + 1; isyll <= numberOfSyllables; isyll ++)
 		footedToTheLeft [isyll] = footedToTheRight [isyll] = 0;
 	if (startingSyllable > numberOfSyllables) {
-		fillSurfaceWeightPattern (me, numberOfSyllables, stress, footedToTheLeft, footedToTheRight, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+		fillSurfaceWeightPattern (me, underlyingForm, numberOfSyllables, stress, footedToTheLeft, footedToTheRight, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 	} else {
-		path (me, numberOfSyllables, stress, startingSyllable + 1,
+		path (me, underlyingForm, numberOfSyllables, stress, startingSyllable + 1,
 			footedToTheLeft, footedToTheRight, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 		if (stress [startingSyllable] == 0 && startingSyllable < numberOfSyllables && stress [startingSyllable + 1] != 0) {
 			footedToTheLeft [startingSyllable + 1] = TRUE;
 			footedToTheRight [startingSyllable] = TRUE;
-			path (me, numberOfSyllables, stress, startingSyllable + 1,
+			path (me, underlyingForm, numberOfSyllables, stress, startingSyllable + 1,
 				footedToTheLeft, footedToTheRight, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 			footedToTheLeft [startingSyllable + 1] = FALSE;
 			footedToTheRight [startingSyllable] = FALSE;
@@ -159,61 +144,60 @@ static void path (OTGrammarTableau me, long numberOfSyllables, int stress [],
 		{
 			footedToTheRight [startingSyllable - 1] = TRUE;
 			footedToTheLeft [startingSyllable] = TRUE;
-			path (me, numberOfSyllables, stress, startingSyllable + 1,
+			path (me, underlyingForm, numberOfSyllables, stress, startingSyllable + 1,
 				footedToTheLeft, footedToTheRight, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 		}
 	}
 }
 
-static void fillOvertStressPattern (OTGrammarTableau me, long numberOfSyllables, int stress [], int underlyingWeightPattern [],
+static void fillOvertStressPattern (OTMulti me, const wchar_t *underlyingForm, long numberOfSyllables, int stress [], int underlyingWeightPattern [],
 	int overtFormsHaveSecondaryStress)
 {
 	int footedToTheLeft [10], footedToTheRight [10];
 	for (int isyll = 1; isyll <= numberOfSyllables; isyll ++)
 		footedToTheLeft [isyll] = footedToTheRight [isyll] = 0;
-	path (me, numberOfSyllables, stress, 1, footedToTheLeft, footedToTheRight, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+	path (me, underlyingForm, numberOfSyllables, stress, 1, footedToTheLeft, footedToTheRight, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 }
 
-static void fillTableau (OTGrammarTableau me, long numberOfSyllables, int underlyingWeightPattern [], int overtFormsHaveSecondaryStress, int includeCodas) {
-	wchar_t input [100];
-	static int numberOfCandidates_noCodas [1+7] = { 0, 1, 6, 24, 88, 300, 984, 3136 };
-	static int numberOfCandidates_codas [1+7] = { 0, 1, 24, 192, 1408, 9600, 984, 3136 };
-	wcscpy (input, L"|");
+static int numberOfCandidates_noCodas [1+7] = { 0, 1, 6, 24, 88, 300, 984, 3136 };
+static int numberOfCandidates_codas [1+7] = { 0, 1, 24, 192, 1408, 9600, 984, 3136 };
+
+static void fillTableau (OTMulti me, long numberOfSyllables, int underlyingWeightPattern [], int overtFormsHaveSecondaryStress, int includeCodas) {
+	wchar_t underlyingForm [100];
+	wcscpy (underlyingForm, L"|");
 	for (long isyll = 1; isyll <= numberOfSyllables; isyll ++) {
 		static const wchar_t *syllable_noCodas [] = { L"", L"L", L"H" };
 		static const wchar_t *syllable_codas [] = { L"", L"cv", L"cv:", L"cvc" };
-		if (isyll > 1) wcscat (input, includeCodas ? L"." : L" ");
-		wcscat (input, ( includeCodas ? syllable_codas : syllable_noCodas ) [underlyingWeightPattern [isyll]]);
+		if (isyll > 1) wcscat (underlyingForm, includeCodas ? L"." : L" ");
+		wcscat (underlyingForm, ( includeCodas ? syllable_codas : syllable_noCodas ) [underlyingWeightPattern [isyll]]);
 	}
-	wcscat (input, L"|");
-	my input = Melder_wcsdup (input);
-	my candidates = NUMvector <structOTGrammarCandidate> (1, ( includeCodas ? numberOfCandidates_codas : numberOfCandidates_noCodas ) [numberOfSyllables]);
+	wcscat (underlyingForm, L"|");
 	for (long mainStressed = 1; mainStressed <= numberOfSyllables; mainStressed ++) {
 		int stress [10];
 		stress [mainStressed] = 1;
 		for (int secondary1 = FALSE; secondary1 <= TRUE; secondary1 ++) {
 			stress [mainStressed <= 1 ? 2 : 1] = secondary1 ? 2 : 0;
 			if (numberOfSyllables == 2) {
-				fillOvertStressPattern (me, 2, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+				fillOvertStressPattern (me, underlyingForm, 2, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 			} else for (int secondary2 = FALSE; secondary2 <= TRUE; secondary2 ++) {
 				stress [mainStressed <= 2 ? 3 : 2] = secondary2 ? 2 : 0;
 				if (numberOfSyllables == 3) {
-					fillOvertStressPattern (me, 3, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+					fillOvertStressPattern (me, underlyingForm, 3, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 				} else for (int secondary3 = FALSE; secondary3 <= TRUE; secondary3 ++) {
 					stress [mainStressed <= 3 ? 4 : 3] = secondary3 ? 2 : 0;
 					if (numberOfSyllables == 4) {
-						fillOvertStressPattern (me, 4, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+						fillOvertStressPattern (me, underlyingForm, 4, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 					} else for (int secondary4 = FALSE; secondary4 <= TRUE; secondary4 ++) {
 						stress [mainStressed <= 4 ? 5 : 4] = secondary4 ? 2 : 0;
 						if (numberOfSyllables == 5) {
-							fillOvertStressPattern (me, 5, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+							fillOvertStressPattern (me, underlyingForm, 5, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 						} else for (int secondary5 = FALSE; secondary5 <= TRUE; secondary5 ++) {
 							stress [mainStressed <= 5 ? 6 : 5] = secondary5 ? 2 : 0;
 							if (numberOfSyllables == 6) {
-								fillOvertStressPattern (me, 6, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+								fillOvertStressPattern (me, underlyingForm, 6, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 							} else for (int secondary6 = FALSE; secondary6 <= TRUE; secondary6 ++) {
 								stress [mainStressed <= 6 ? 7 : 6] = secondary6 ? 2 : 0;
-								fillOvertStressPattern (me, 7, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
+								fillOvertStressPattern (me, underlyingForm, 7, stress, underlyingWeightPattern, overtFormsHaveSecondaryStress);
 							}
 						}
 					}
@@ -223,14 +207,14 @@ static void fillTableau (OTGrammarTableau me, long numberOfSyllables, int underl
 	}
 }
 
-static void computeViolationMarks (OTGrammarCandidate me) {
+static void computeViolationMarks (OTCandidate me) {
 	#define isHeavy(s)  ((s) == 'H' || (s) == 'J')
 	#define isLight(s)  ((s) == 'L' || (s) == 'K')
 	#define isSyllable(s)  (isHeavy (s) || isLight (s))
 	#define isStress(s)  ((s) == '1' || (s) == '2')
 	int depth;
-	wchar_t *firstSlash = wcschr (my output, '/');
-	wchar_t *lastSlash = & my output [wcslen (my output) - 1];
+	wchar_t *firstSlash = wcschr (my string, '/');
+	wchar_t *lastSlash = wcschr (firstSlash + 1, '/');
 	my marks = NUMvector <int> (1, my numberOfConstraints = NUMBER_OF_CONSTRAINTS);
 	/* Violations of WSP: count all H not followed by 1 or 2. */
 	for (wchar_t *p = firstSlash + 1; p != lastSlash; p ++) {
@@ -391,11 +375,11 @@ static void computeViolationMarks (OTGrammarCandidate me) {
 	}
 }
 
-static void replaceOutput (OTGrammarCandidate me) {
+static void replaceOutput (OTCandidate me) {
 	int abstract = FALSE;
-	Melder_assert (my output != NULL);
-	wchar_t newOutput [100], *q = & newOutput [0];
-	for (const wchar_t *p = & my output [0]; *p != '\0'; p ++) {
+	Melder_assert (my string != NULL);
+	wchar_t newString [150], *q = & newString [0];
+	for (const wchar_t *p = & my string [0]; *p != '\0'; p ++) {
 		if (p [0] == ' ') {
 			*q ++ = p [-1] == ']' || p [1] == '/' ? ' ' : '.';
 		} else if (isSyllable (p [0])) {
@@ -432,22 +416,20 @@ static void replaceOutput (OTGrammarCandidate me) {
 		}
 	}
 	*q = '\0';
-	Melder_free (my output);
-	my output = Melder_wcsdup_f (newOutput);
+	Melder_free (my string);
+	my string = Melder_wcsdup_f (newString);
 }
 
-OTGrammar OTGrammar_create_metrics (int equal_footForm_wsp, int trochaicityConstraint, int includeFootBimoraic, int includeFootBisyllabic,
+OTMulti OTMulti_create_metrics (int equal_footForm_wsp, int trochaicityConstraint, int includeFootBimoraic, int includeFootBisyllabic,
 	int includePeripheral, int nonfinalityConstraint, int overtFormsHaveSecondaryStress,
 	int includeClashAndLapse, int includeCodas)
 {
 	try {
-		int numberOfSyllables;
 		int underlyingWeightPattern [1+7], maximumUnderlyingWeight = includeCodas ? 3 : 2;
-		long numberOfTableaus = includeCodas ? 9 + 27 + 81 + 243 + 2 : 62;
-		autoOTGrammar me = Thing_new (OTGrammar);
-		my constraints = NUMvector <structOTGrammarConstraint> (1, my numberOfConstraints = NUMBER_OF_CONSTRAINTS);
+		autoOTMulti me = Thing_new (OTMulti);
+		my constraints = NUMvector <structOTConstraint> (1, my numberOfConstraints = NUMBER_OF_CONSTRAINTS);
 		for (long icons = 1; icons <= NUMBER_OF_CONSTRAINTS; icons ++) {
-			OTGrammarConstraint constraint = & my constraints [icons];
+			OTConstraint constraint = & my constraints [icons];
 			constraint -> name = Melder_wcsdup (constraintNames [icons]);
 			constraint -> ranking = 100.0;
 			constraint -> plasticity = 1.0;
@@ -462,14 +444,20 @@ OTGrammar OTGrammar_create_metrics (int equal_footForm_wsp, int trochaicityConst
 			/* Quantity sensitivity high, foot form constraints in the second stratum. */
 			my constraints [WSP]. ranking = 102.0;
 		}
-		my tableaus = NUMvector <structOTGrammarTableau> (1, numberOfTableaus);
-		for (numberOfSyllables = 2; numberOfSyllables <= 7; numberOfSyllables ++) {
+		long numberOfCandidates = 0;
+		for (int numberOfSyllables = 2; numberOfSyllables <= 7; numberOfSyllables ++) {
+			long numberOfUnderlyingWeightPatterns = numberOfSyllables > 5 ? 1 : (long) floor (pow (maximumUnderlyingWeight, numberOfSyllables) + 0.5);
+			numberOfCandidates += ( includeCodas ? numberOfCandidates_codas : numberOfCandidates_noCodas ) [numberOfSyllables] * numberOfUnderlyingWeightPatterns;
+		}
+		my candidates = NUMvector <structOTCandidate> (1, numberOfCandidates);
+		my numberOfCandidates = 0;
+		for (int numberOfSyllables = 2; numberOfSyllables <= 7; numberOfSyllables ++) {
 			long numberOfUnderlyingWeightPatterns = numberOfSyllables > 5 ? 1 : (long) floor (pow (maximumUnderlyingWeight, numberOfSyllables) + 0.5);
 			for (long isyll = 1; isyll <= numberOfSyllables; isyll ++) {
 				underlyingWeightPattern [isyll] = 1;   /* L or cv */
 			}
 			for (long iweightPattern = 1; iweightPattern <= numberOfUnderlyingWeightPatterns; iweightPattern ++) {
-				fillTableau (& my tableaus [++ my numberOfTableaus], numberOfSyllables, underlyingWeightPattern, overtFormsHaveSecondaryStress, includeCodas);
+				fillTableau (me.peek(), numberOfSyllables, underlyingWeightPattern, overtFormsHaveSecondaryStress, includeCodas);
 				/*
 				 * Cycle to next underlying weight pattern.
 				 */
@@ -482,47 +470,42 @@ OTGrammar OTGrammar_create_metrics (int equal_footForm_wsp, int trochaicityConst
 				}
 			}
 		}
+		Melder_assert (my numberOfCandidates == numberOfCandidates);
 		/* Compute violation marks. */
-		for (long itab = 1; itab <= my numberOfTableaus; itab ++) {
-			OTGrammarTableau tableau = & my tableaus [itab];
-			for (long icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
-				computeViolationMarks (& tableau -> candidates [icand]);
-			}
+		for (long icand = 1; icand <= my numberOfCandidates; icand ++) {
+			computeViolationMarks (& my candidates [icand]);
 		}
-		OTGrammar_checkIndex (me.peek());
-		OTGrammar_newDisharmonies (me.peek(), 0.0);
+		OTMulti_checkIndex (me.peek());
+		OTMulti_newDisharmonies (me.peek(), 0.0);
 		if (trochaicityConstraint == 1) {
-			OTGrammar_removeConstraint (me.peek(), L"Trochaic");
+			OTMulti_removeConstraint (me.peek(), L"Trochaic");
 		} else {
-			OTGrammar_removeConstraint (me.peek(), L"FtNonfinal");
+			OTMulti_removeConstraint (me.peek(), L"FtNonfinal");
 		}
-		if (! includeFootBimoraic) OTGrammar_removeConstraint (me.peek(), L"FtBimor");
-		if (! includeFootBisyllabic) OTGrammar_removeConstraint (me.peek(), L"FtBisyl");
-		if (! includePeripheral) OTGrammar_removeConstraint (me.peek(), L"Peripheral");
+		if (! includeFootBimoraic) OTMulti_removeConstraint (me.peek(), L"FtBimor");
+		if (! includeFootBisyllabic) OTMulti_removeConstraint (me.peek(), L"FtBisyl");
+		if (! includePeripheral) OTMulti_removeConstraint (me.peek(), L"Peripheral");
 		if (nonfinalityConstraint == 1) {
-			OTGrammar_removeConstraint (me.peek(), L"MainNonfinal");
-			OTGrammar_removeConstraint (me.peek(), L"HeadNonfinal");
+			OTMulti_removeConstraint (me.peek(), L"MainNonfinal");
+			OTMulti_removeConstraint (me.peek(), L"HeadNonfinal");
 		} else if (nonfinalityConstraint == 2) {
-			OTGrammar_removeConstraint (me.peek(), L"HeadNonfinal");
-			OTGrammar_removeConstraint (me.peek(), L"Nonfinal");
+			OTMulti_removeConstraint (me.peek(), L"HeadNonfinal");
+			OTMulti_removeConstraint (me.peek(), L"Nonfinal");
 		} else {
-			OTGrammar_removeConstraint (me.peek(), L"MainNonfinal");
-			OTGrammar_removeConstraint (me.peek(), L"Nonfinal");
+			OTMulti_removeConstraint (me.peek(), L"MainNonfinal");
+			OTMulti_removeConstraint (me.peek(), L"Nonfinal");
 		}
 		if (! includeClashAndLapse) {
-			OTGrammar_removeConstraint (me.peek(), L"*Clash");
-			OTGrammar_removeConstraint (me.peek(), L"*Lapse");
+			OTMulti_removeConstraint (me.peek(), L"*Clash");
+			OTMulti_removeConstraint (me.peek(), L"*Lapse");
 		}
 		if (! includeCodas) {
-			OTGrammar_removeConstraint (me.peek(), L"WeightByPosition");
-			OTGrammar_removeConstraint (me.peek(), L"*C\\mu");
+			OTMulti_removeConstraint (me.peek(), L"WeightByPosition");
+			OTMulti_removeConstraint (me.peek(), L"*C\\mu");
 		}
 		if (includeCodas) {
-			for (long itab = 1; itab <= my numberOfTableaus; itab ++) {
-				OTGrammarTableau tableau = & my tableaus [itab];
-				for (long icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
-					replaceOutput (& tableau -> candidates [icand]);
-				}
+			for (long icand = 1; icand <= my numberOfCandidates; icand ++) {
+				replaceOutput (& my candidates [icand]);
 			}
 		}
 		return me.transfer();
@@ -531,4 +514,4 @@ OTGrammar OTGrammar_create_metrics (int equal_footForm_wsp, int trochaicityConst
 	}
 }
 
-/* End of file OTGrammar_ex_metrics.cpp */
+/* End of file OTMulti_ex_metrics.cpp */
diff --git a/gram/manual_gram.cpp b/gram/manual_gram.cpp
index 84a8a15..03bd61e 100644
--- a/gram/manual_gram.cpp
+++ b/gram/manual_gram.cpp
@@ -240,15 +240,15 @@ NORMAL (L"Ordinal OT grammars can be seen as a special case of the more general
 	"In Praat, therefore, every constraint is taken to have a ranking value, "
 	"so that you can do stochastic as well as ordinal OT.")
 ENTRY (L"3. Categorical Harmonic Grammars")
-NORMAL (L"@@J\\a\"ger (2003)@ and @@Soderstrom, Mathis & Smolensky (2006)@ devised an on-line learning algorithm "
+NORMAL (L"@@Jäger (2003)@ and @@Soderstrom, Mathis & Smolensky (2006)@ devised an on-line learning algorithm "
 	"for Harmonic Grammars (stochastic gradient ascent). As proven by @@Fischer (2005)@, "
 	"this algorithm is guaranteed to converge upon a correct grammar, if there exists one that handles the data.")
 ENTRY (L"4. Stochastic Harmonic Grammars")
 NORMAL (L"There are two kinds of stochastic models of HG, namely MaxEnt (= Maximum Entropy) grammars "
-	"(@@Smolensky (1986)@, @@J\\a\"ger (2003)@), in which the probablity of a candidate winning depends on its harmony, "
+	"(@@Smolensky (1986)@, @@Jäger (2003)@), in which the probablity of a candidate winning depends on its harmony, "
 	"and Noisy HG (@@Boersma & Escudero (2008)@, @@Boersma & Pater (2008)@), in which noise is added to constraint weights "
 	"at evaluation time, as in Stochastic OT.")
-NORMAL (L"The algorithm by @@J\\a\"ger (2003)@ and @@Soderstrom, Mathis & Smolensky (2006)@ "
+NORMAL (L"The algorithm by @@Jäger (2003)@ and @@Soderstrom, Mathis & Smolensky (2006)@ "
 	"can learn languages with optionality and variation (@@Boersma & Pater (2008)@).")
 ENTRY (L"The OTGrammar object")
 NORMAL (L"An OT grammar is implemented as an @OTGrammar object. "
@@ -818,7 +818,7 @@ NORMAL (L"If we have three constraints obligatorily ranked as A >> B >> C in the
 	"to be reversed at evaluation time. This relativity of error rates is an empirical prediction of our stochastic OT grammar model.")
 NORMAL (L"Our Harmonic Grammars with constraint noise (Noisy HG) are slightly different in that respect, "
 	"but are capable of learning a constraint ranking for any language that can be generated from an ordinal ranking. "
-	"As proved by @@Boersma & Pater (2008)@, the same learning rule as was devised for MaxEnt grammars by @@J\\a\"ger (2003)@ "
+	"As proved by @@Boersma & Pater (2008)@, the same learning rule as was devised for MaxEnt grammars by @@Jäger (2003)@ "
 	"is able to learn all languages generated by %nonnoisy HG grammars as well; "
 	"the GLA, by contrast, failed to converge on 0.4 percent of randomly generated OT languages "
 	"(failures of the GLA on ordinal grammars were discovered first by @@Pater (2008)@). "
diff --git a/gram/praat_gram.cpp b/gram/praat_gram.cpp
index a624271..447ed83 100644
--- a/gram/praat_gram.cpp
+++ b/gram/praat_gram.cpp
@@ -1,6 +1,6 @@
 /* praat_gram.cpp
  *
- * Copyright (C) 1997-2012,2013 Paul Boersma
+ * Copyright (C) 1997-2012,2013,2014 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
@@ -28,13 +28,10 @@
 #undef iam
 #define iam iam_LOOP
 
-/***** HELP *****/
+#pragma mark -
+#pragma mark NETWORK
 
-DIRECT (OT_learning_tutorial) Melder_help (L"OT learning"); END
-
-DIRECT (OTGrammar_help) Melder_help (L"OTGrammar"); END
-
-/***** NETWORK *****/
+#pragma mark New
 
 static void UiForm_addNetworkFields (UiForm dia) {
 	Any radio;
@@ -51,7 +48,7 @@ static void UiForm_addNetworkFields (UiForm dia) {
 	REAL (L"Weight leak", L"0.0")
 }
 
-FORM (Create_empty_Network, L"Create empty Network", 0)
+FORM (Create_empty_Network, L"Create empty Network", 0) {
 	WORD (L"Name", L"network")
 	UiForm_addNetworkFields (dia);
 	LABEL (L"", L"World coordinates:")
@@ -59,7 +56,7 @@ FORM (Create_empty_Network, L"Create empty Network", 0)
 	REAL (L"right x range", L"10.0")
 	REAL (L"left y range", L"0.0")
 	REAL (L"right y range", L"10.0")
-	OK
+	OK2
 DO
 	autoNetwork me = Network_create (GET_REAL (L"Spreading rate"), GET_ENUM (kNetwork_activityClippingRule, L"Activity clipping rule"),
 		GET_REAL (L"left Activity range"), GET_REAL (L"right Activity range"), GET_REAL (L"Activity leak"),
@@ -67,9 +64,9 @@ DO
 		GET_REAL (L"left x range"), GET_REAL (L"right x range"), GET_REAL (L"left y range"), GET_REAL (L"right y range"),
 		0, 0);
 	praat_new (me.transfer(), GET_STRING (L"Name"));
-END
+END2 }
 
-FORM (Create_rectangular_Network, L"Create rectangular Network", 0)
+FORM (Create_rectangular_Network, L"Create rectangular Network", 0) {
 	UiForm_addNetworkFields (dia);
 	LABEL (L"", L"Structure settings:")
 	NATURAL (L"Number of rows", L"10")
@@ -78,7 +75,7 @@ FORM (Create_rectangular_Network, L"Create rectangular Network", 0)
 	LABEL (L"", L"Initial state settings:")
 	REAL (L"left Initial weight range", L"-0.1")
 	REAL (L"right Initial weight range", L"0.1")
-	OK
+	OK2
 DO
 	autoNetwork me = Network_create_rectangle (GET_REAL (L"Spreading rate"), GET_ENUM (kNetwork_activityClippingRule, L"Activity clipping rule"),
 		GET_REAL (L"left Activity range"), GET_REAL (L"right Activity range"), GET_REAL (L"Activity leak"),
@@ -89,9 +86,9 @@ DO
 	praat_new (me.transfer(),
 			L"rectangle_", Melder_integer (GET_INTEGER (L"Number of rows")),
 			L"_", Melder_integer (GET_INTEGER (L"Number of columns")));
-END
+END2 }
 
-FORM (Create_rectangular_Network_vertical, L"Create rectangular Network (vertical)", 0)
+FORM (Create_rectangular_Network_vertical, L"Create rectangular Network (vertical)", 0) {
 	UiForm_addNetworkFields (dia);
 	LABEL (L"", L"Structure settings:")
 	NATURAL (L"Number of rows", L"10")
@@ -100,7 +97,7 @@ FORM (Create_rectangular_Network_vertical, L"Create rectangular Network (vertica
 	LABEL (L"", L"Initial state settings:")
 	REAL (L"left Initial weight range", L"-0.1")
 	REAL (L"right Initial weight range", L"0.1")
-	OK
+	OK2
 DO
 	autoNetwork me = Network_create_rectangle_vertical (GET_REAL (L"Spreading rate"), GET_ENUM (kNetwork_activityClippingRule, L"Activity clipping rule"),
 		GET_REAL (L"left Activity range"), GET_REAL (L"right Activity range"), GET_REAL (L"Activity leak"),
@@ -111,37 +108,47 @@ DO
 	praat_new (me.transfer(),
 			L"rectangle_", Melder_integer (GET_INTEGER (L"Number of rows")),
 			L"_", Melder_integer (GET_INTEGER (L"Number of columns")));
-END
+END2 }
 
-FORM (Network_addConnection, L"Network: Add connection", 0)
-	NATURAL (L"From node", L"1")
-	NATURAL (L"To node", L"2")
-	REAL (L"Weight", L"0.0")
-	REAL (L"Plasticity", L"1.0")
-	OK
+#pragma mark Draw
+
+FORM (Network_draw, L"Draw Network", 0) {
+	BOOLEAN (L"Colour", 1)
+	OK2
 DO
+	autoPraatPicture picture;
 	LOOP {
 		iam_LOOP (Network);
-		me -> f_addConnection (GET_INTEGER (L"From node"), GET_INTEGER (L"To node"), GET_REAL (L"Weight"), GET_REAL (L"Plasticity"));
-		praat_dataChanged (me);
+		me -> f_draw (GRAPHICS, GET_INTEGER (L"Colour"));
 	}
-END
+END2 }
 
-FORM (Network_addNode, L"Network: Add node", 0)
-	REAL (L"x", L"5.0")
-	REAL (L"y", L"5.0")
-	REAL (L"Activity", L"0.0")
-	BOOLEAN (L"Clamping", 0)
-	OK
+#pragma mark Tabulate
+
+FORM (Network_listNodes, L"Network: List nodes", 0) {
+	INTEGER (L"From node number", L"1")
+	INTEGER (L"To node number", L"1000")
+	BOOLEAN (L"Include node numbers", true)
+	BOOLEAN (L"Include x", false)
+	BOOLEAN (L"Include y", false)
+	INTEGER (L"Position decimals", L"6")
+	BOOLEAN (L"Include clamped", false)
+	BOOLEAN (L"Include activity", true)
+	BOOLEAN (L"Include excitation", false)
+	INTEGER (L"Activity decimals", L"6")
+	OK2
 DO
 	LOOP {
-		iam_LOOP (Network);
-		me -> f_addNode (GET_REAL (L"x"), GET_REAL (L"y"), GET_REAL (L"Activity"), GET_INTEGER (L"Clamping"));
-		praat_dataChanged (me);
+		iam (Network);
+		my f_listNodes (GET_INTEGER (L"From node number"), GET_INTEGER (L"To node number"),
+			GET_INTEGER (L"Include node numbers"),
+			GET_INTEGER (L"Include x"), GET_INTEGER (L"Include y"), GET_INTEGER (L"Position decimals"),
+			GET_INTEGER (L"Include clamped"),
+			GET_INTEGER (L"Include activity"), GET_INTEGER (L"Include excitation"), GET_INTEGER (L"Activity decimals"));
 	}
-END
+END2 }
 
-FORM (Network_nodes_downto_Table, L"Network: Nodes down to Table", 0)
+FORM (Network_nodes_downto_Table, L"Network: Nodes down to Table", 0) {
 	INTEGER (L"From node number", L"1")
 	INTEGER (L"To node number", L"1000")
 	BOOLEAN (L"Include node numbers", true)
@@ -152,7 +159,7 @@ FORM (Network_nodes_downto_Table, L"Network: Nodes down to Table", 0)
 	BOOLEAN (L"Include activity", true)
 	BOOLEAN (L"Include excitation", false)
 	INTEGER (L"Activity decimals", L"6")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Network);
@@ -163,79 +170,77 @@ DO
 			GET_INTEGER (L"Include activity"), GET_INTEGER (L"Include excitation"), GET_INTEGER (L"Activity decimals"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Network_draw, L"Draw Network", 0)
-	BOOLEAN (L"Colour", 1)
-	OK
-DO
-	autoPraatPicture picture;
-	LOOP {
-		iam_LOOP (Network);
-		me -> f_draw (GRAPHICS, GET_INTEGER (L"Colour"));
-	}
-END
+#pragma mark Query
 
-FORM (Network_getActivity, L"Network: Get activity", 0)
+FORM (Network_getActivity, L"Network: Get activity", 0) {
 	NATURAL (L"Node", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (Network);
 	double activity = me -> f_getActivity (GET_INTEGER (L"Node"));
 	Melder_information (Melder_double (activity));
-END
+END2 }
 
-FORM (Network_getWeight, L"Network: Get weight", 0)
+FORM (Network_getWeight, L"Network: Get weight", 0) {
 	NATURAL (L"Connection", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (Network);
 	double weight = me -> f_getWeight (GET_INTEGER (L"Connection"));
 	Melder_information (Melder_double (weight));
-END
+END2 }
 
-FORM (Network_listNodes, L"Network: List nodes", 0)
-	INTEGER (L"From node number", L"1")
-	INTEGER (L"To node number", L"1000")
-	BOOLEAN (L"Include node numbers", true)
-	BOOLEAN (L"Include x", false)
-	BOOLEAN (L"Include y", false)
-	INTEGER (L"Position decimals", L"6")
-	BOOLEAN (L"Include clamped", false)
-	BOOLEAN (L"Include activity", true)
-	BOOLEAN (L"Include excitation", false)
-	INTEGER (L"Activity decimals", L"6")
-	OK
+#pragma mark Modify
+
+FORM (Network_addConnection, L"Network: Add connection", 0) {
+	NATURAL (L"From node", L"1")
+	NATURAL (L"To node", L"2")
+	REAL (L"Weight", L"0.0")
+	REAL (L"Plasticity", L"1.0")
+	OK2
 DO
 	LOOP {
-		iam (Network);
-		my f_listNodes (GET_INTEGER (L"From node number"), GET_INTEGER (L"To node number"),
-			GET_INTEGER (L"Include node numbers"),
-			GET_INTEGER (L"Include x"), GET_INTEGER (L"Include y"), GET_INTEGER (L"Position decimals"),
-			GET_INTEGER (L"Include clamped"),
-			GET_INTEGER (L"Include activity"), GET_INTEGER (L"Include excitation"), GET_INTEGER (L"Activity decimals"));
+		iam_LOOP (Network);
+		me -> f_addConnection (GET_INTEGER (L"From node"), GET_INTEGER (L"To node"), GET_REAL (L"Weight"), GET_REAL (L"Plasticity"));
+		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_normalizeActivities, L"Network: Normalize activities", 0)
+FORM (Network_addNode, L"Network: Add node", 0) {
+	REAL (L"x", L"5.0")
+	REAL (L"y", L"5.0")
+	REAL (L"Activity", L"0.0")
+	BOOLEAN (L"Clamping", 0)
+	OK2
+DO
+	LOOP {
+		iam_LOOP (Network);
+		me -> f_addNode (GET_REAL (L"x"), GET_REAL (L"y"), GET_REAL (L"Activity"), GET_INTEGER (L"Clamping"));
+		praat_dataChanged (me);
+	}
+END2 }
+
+FORM (Network_normalizeActivities, L"Network: Normalize activities", 0) {
 	INTEGER (L"From node", L"1")
 	INTEGER (L"To node", L"0 (= all)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam_LOOP (Network);
 		me -> f_normalizeActivities (GET_INTEGER (L"From node"), GET_INTEGER (L"To node"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_normalizeWeights, L"Network: Normalize weights", 0)
+FORM (Network_normalizeWeights, L"Network: Normalize weights", 0) {
 	INTEGER (L"From node", L"1")
 	INTEGER (L"To node", L"0 (= all)")
 	INTEGER (L"From incoming node", L"1")
 	INTEGER (L"To incoming node", L"10")
 	REAL (L"New sum", L"1.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam_LOOP (Network);
@@ -243,145 +248,179 @@ DO
 			GET_INTEGER (L"From incoming node"), GET_INTEGER (L"To incoming node"), GET_REAL (L"New sum"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_setActivity, L"Network: Set activity", 0)
+FORM (Network_setActivity, L"Network: Set activity", 0) {
 	NATURAL (L"Node", L"1")
 	REAL (L"Activity", L"1.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam_LOOP (Network);
 		me -> f_setActivity (GET_INTEGER (L"Node"), GET_REAL (L"Activity"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_setActivityClippingRule, L"Network: Set activity clipping rule", 0)
+FORM (Network_setActivityClippingRule, L"Network: Set activity clipping rule", 0) {
 	RADIO_ENUM (L"Activity clipping rule", kNetwork_activityClippingRule, DEFAULT)
-	OK
+	OK2
 iam_ONLY (Network);
 SET_ENUM (L"Activity clipping rule", kNetwork_activityClippingRule, my d_activityClippingRule);
 DO
 	iam_ONLY (Network);
 	me -> f_setActivityClippingRule (GET_ENUM (kNetwork_activityClippingRule, L"Activity clipping rule"));
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (Network_setActivityLeak, L"Network: Set activity leak", 0)
+FORM (Network_setActivityLeak, L"Network: Set activity leak", 0) {
 	REAL (L"Activity leak", L"1.0")
-	OK
+	OK2
 iam_ONLY (Network);
 SET_REAL (L"Activity leak", my d_activityLeak);
 DO
 	iam_ONLY (Network);
 	me -> f_setActivityLeak (GET_REAL (L"Activity leak"));
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (Network_setClamping, L"Network: Set clamping", 0)
+FORM (Network_setClamping, L"Network: Set clamping", 0) {
 	NATURAL (L"Node", L"1")
 	BOOLEAN (L"Clamping", 1)
-	OK
+	OK2
 DO
 	LOOP {
 		iam_LOOP (Network);
 		me -> f_setClamping (GET_INTEGER (L"Node"), GET_INTEGER (L"Clamping"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_setInstar, L"Network: Set instar", 0)
+FORM (Network_setInstar, L"Network: Set instar", 0) {
 	REAL (L"Instar", L"0.0")
-	OK
+	OK2
 iam_ONLY (Network);
 SET_REAL (L"Instar", my d_instar);
 DO
 	iam_ONLY (Network);
 	me -> f_setInstar (GET_REAL (L"Instar"));
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (Network_setWeightLeak, L"Network: Set weight leak", 0)
+FORM (Network_setWeightLeak, L"Network: Set weight leak", 0) {
 	REAL (L"Weight leak", L"0.0")
-	OK
+	OK2
 iam_ONLY (Network);
 SET_REAL (L"Weight leak", my d_weightLeak);
 DO
 	iam_ONLY (Network);
 	me -> f_setWeightLeak (GET_REAL (L"Weight leak"));
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (Network_setOutstar, L"Network: Set outstar", 0)
+FORM (Network_setOutstar, L"Network: Set outstar", 0) {
 	REAL (L"Outstar", L"0.0")
-	OK
+	OK2
 iam_ONLY (Network);
 SET_REAL (L"Outstar", my d_outstar);
 DO
 	iam_ONLY (Network);
 	me -> f_setOutstar (GET_REAL (L"Outstar"));
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (Network_setShunting, L"Network: Set shunting", 0)
+FORM (Network_setShunting, L"Network: Set shunting", 0) {
 	REAL (L"Shunting", L"1.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam_LOOP (Network);
 		me -> f_setShunting (GET_REAL (L"Shunting"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_setWeight, L"Network: Set weight", 0)
+FORM (Network_setWeight, L"Network: Set weight", 0) {
 	NATURAL (L"Connection", L"1")
 	REAL (L"Weight", L"1.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam_LOOP (Network);
 		me -> f_setWeight (GET_INTEGER (L"Connection"), GET_REAL (L"Weight"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_spreadActivities, L"Network: Spread activities", 0)
+FORM (Network_spreadActivities, L"Network: Spread activities", 0) {
 	NATURAL (L"Number of steps", L"20")
-	OK
+	OK2
 DO
 	LOOP {
 		iam_LOOP (Network);
 		me -> f_spreadActivities (GET_INTEGER (L"Number of steps"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (Network_updateWeights)
+DIRECT2 (Network_updateWeights) {
 	LOOP {
 		iam_LOOP (Network);
 		me -> f_updateWeights ();
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Network_zeroActivities, L"Network: Zero activities", 0)
+FORM (Network_zeroActivities, L"Network: Zero activities", 0) {
 	INTEGER (L"From node", L"1")
 	INTEGER (L"To node", L"0 (= all)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Network);
 		me -> f_zeroActivities (GET_INTEGER (L"From node"), GET_INTEGER (L"To node"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
+#pragma mark -
+#pragma mark OTGRAMMAR
 
-/***** OTGRAMMAR *****/
+#pragma mark New
 
-FORM (Create_metrics_grammar, L"Create metrics grammar", 0)
+DIRECT2 (OT_learning_tutorial) { Melder_help (L"OT learning"); END2 }
+
+DIRECT2 (Create_NoCoda_grammar) {
+	autoOTGrammar me = OTGrammar_create_NoCoda_grammar ();
+	praat_new (me.transfer(), L"NoCoda");
+END2 }
+
+DIRECT2 (Create_NPA_grammar) {
+	autoOTGrammar me = OTGrammar_create_NPA_grammar ();
+	praat_new (me.transfer(), L"assimilation");
+END2 }
+
+DIRECT2 (Create_NPA_distribution) {
+	autoPairDistribution me = OTGrammar_create_NPA_distribution ();
+	praat_new (me.transfer(), L"assimilation");
+END2 }
+
+FORM (Create_tongue_root_grammar, L"Create tongue-root grammar", L"Create tongue-root grammar...") {
+	RADIO (L"Constraint set", 1)
+		RADIOBUTTON (L"Five")
+		RADIOBUTTON (L"Nine")
+	RADIO (L"Ranking", 3)
+		RADIOBUTTON (L"Equal")
+		RADIOBUTTON (L"Random")
+		RADIOBUTTON (L"Infant")
+		RADIOBUTTON (L"Wolof")
+	OK2
+DO
+	autoOTGrammar me = OTGrammar_create_tongueRoot_grammar (GET_INTEGER (L"Constraint set"), GET_INTEGER (L"Ranking"));
+	praat_new (me.transfer(), GET_STRING (L"Ranking"));
+END2 }
+
+FORM (Create_metrics_grammar, L"Create metrics grammar", 0) {
 	OPTIONMENU (L"Initial ranking", 1)
 		OPTION (L"Equal")
 		OPTION (L"Foot form high")
@@ -399,203 +438,148 @@ FORM (Create_metrics_grammar, L"Create metrics grammar", 0)
 	BOOLEAN (L"Overt forms have secondary stress", 1)
 	BOOLEAN (L"Include *Clash and *Lapse", 0)
 	BOOLEAN (L"Include codas", 0)
-	OK
+	OK2
 DO
 	praat_new (OTGrammar_create_metrics (GET_INTEGER (L"Initial ranking"), GET_INTEGER (L"Trochaicity constraint"),
 		GET_INTEGER (L"Include FootBimoraic"), GET_INTEGER (L"Include FootBisyllabic"),
 		GET_INTEGER (L"Include Peripheral"), GET_INTEGER (L"Nonfinality constraint"),
 		GET_INTEGER (L"Overt forms have secondary stress"), GET_INTEGER (L"Include *Clash and *Lapse"), GET_INTEGER (L"Include codas")),
 		GET_STRING (L"Initial ranking"));
-END
+END2 }
 
-DIRECT (Create_NoCoda_grammar)
-	autoOTGrammar me = OTGrammar_create_NoCoda_grammar ();
-	praat_new (me.transfer(), L"NoCoda");
-END
+#pragma mark Save
+FORM_WRITE2 (OTGrammar_writeToHeaderlessSpreadsheetFile, L"Write OTGrammar to spreadsheet", 0, L"txt") {
+	iam_ONLY (OTGrammar);
+	OTGrammar_writeToHeaderlessSpreadsheetFile (me, file);
+END2 }
 
-DIRECT (Create_NPA_grammar)
-	autoOTGrammar me = OTGrammar_create_NPA_grammar ();
-	praat_new (me.transfer(), L"assimilation");
-END
+#pragma mark Help
 
-DIRECT (Create_NPA_distribution)
-	autoPairDistribution me = OTGrammar_create_NPA_distribution ();
-	praat_new (me.transfer(), L"assimilation");
-END
+DIRECT2 (OTGrammar_help) { Melder_help (L"OTGrammar"); END2 }
 
-FORM (Create_tongue_root_grammar, L"Create tongue-root grammar", L"Create tongue-root grammar...")
-	RADIO (L"Constraint set", 1)
-		RADIOBUTTON (L"Five")
-		RADIOBUTTON (L"Nine")
-	RADIO (L"Ranking", 3)
-		RADIOBUTTON (L"Equal")
-		RADIOBUTTON (L"Random")
-		RADIOBUTTON (L"Infant")
-		RADIOBUTTON (L"Wolof")
-	OK
-DO
-	autoOTGrammar me = OTGrammar_create_tongueRoot_grammar (GET_INTEGER (L"Constraint set"), GET_INTEGER (L"Ranking"));
-	praat_new (me.transfer(), GET_STRING (L"Ranking"));
-END
+#pragma mark Edit
+
+DIRECT2 (OTGrammar_edit) {
+	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot edit from batch.");
+	LOOP {
+		iam (OTGrammar);
+		autoOTGrammarEditor editor = OTGrammarEditor_create (ID_AND_FULL_NAME, me);
+		praat_installEditor (editor.transfer(), IOBJECT);
+	}
+END2 }
+
+#pragma mark Draw
 
-FORM (OTGrammar_drawTableau, L"Draw tableau", L"OT learning")
+FORM (OTGrammar_drawTableau, L"Draw tableau", L"OT learning") {
 	SENTENCE (L"Input string", L"")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (OTGrammar);
 		OTGrammar_drawTableau (me, GRAPHICS, false, GET_STRING (L"Input string"));
 	}
-END
+END2 }
 
-FORM (OTGrammar_drawTableau_narrowly, L"Draw tableau (narrowly)", L"OT learning")
+FORM (OTGrammar_drawTableau_narrowly, L"Draw tableau (narrowly)", L"OT learning") {
 	SENTENCE (L"Input string", L"")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (OTGrammar);
 		OTGrammar_drawTableau (me, GRAPHICS, true, GET_STRING (L"Input string"));
 	}
-END
-
-DIRECT (OTGrammar_edit)
-	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot edit from batch.");
-	LOOP {
-		iam (OTGrammar);
-		autoOTGrammarEditor editor = OTGrammarEditor_create (ID_AND_FULL_NAME, me);
-		praat_installEditor (editor.transfer(), IOBJECT);
-	}
-END
+END2 }
 
-FORM (OTGrammar_evaluate, L"OTGrammar: Evaluate", 0)
-	REAL (L"Evaluation noise", L"2.0")
-	OK
-DO
-	LOOP {
-		iam (OTGrammar);
-		OTGrammar_newDisharmonies (me, GET_REAL (L"Evaluation noise"));
-		praat_dataChanged (me);
-	}
-END
+#pragma mark Query
 
-FORM (OTGrammar_generateInputs, L"Generate inputs", L"OTGrammar: Generate inputs...")
-	NATURAL (L"Number of trials", L"1000")
-	OK
-DO
-	LOOP {
-		iam (OTGrammar);
-		autoStrings thee = OTGrammar_generateInputs (me, GET_INTEGER (L"Number of trials"));
-		praat_new (thee.transfer(), my name, L"_in");
-	}
-END
+DIRECT2 (OTGrammar_getNumberOfConstraints) {
+	iam_ONLY (OTGrammar);
+	Melder_information (Melder_integer (my numberOfConstraints));
+END2 }
 
-FORM (OTGrammar_getCandidate, L"Get candidate", 0)
-	NATURAL (L"Tableau number", L"1")
-	NATURAL (L"Candidate number", L"1")
-	OK
+FORM (OTGrammar_getConstraint, L"Get constraint name", 0) {
+	NATURAL (L"Constraint number", L"1")
+	OK2
 DO
 	iam_ONLY (OTGrammar);
-	OTGrammarTableau tableau;
-	long itab = GET_INTEGER (L"Tableau number"), icand = GET_INTEGER (L"Candidate number");
-	if (itab > my numberOfTableaus)
-		Melder_throw ("The specified tableau number should not exceed the number of tableaus.");
-	tableau = & my tableaus [itab];
-	if (icand > tableau -> numberOfCandidates)
-		Melder_throw ("The specified candidate should not exceed the number of candidates.");
-	Melder_information (tableau -> candidates [icand]. output);
-END
+	long icons = GET_INTEGER (L"Constraint number");
+	if (icons > my numberOfConstraints)
+		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
+	Melder_information (my constraints [icons]. name);
+END2 }
 
-FORM (OTGrammar_getConstraint, L"Get constraint name", 0)
+FORM (OTGrammar_getRankingValue, L"Get ranking value", 0) {
 	NATURAL (L"Constraint number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long icons = GET_INTEGER (L"Constraint number");
 	if (icons > my numberOfConstraints)
 		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
-	Melder_information (my constraints [icons]. name);
-END
+	Melder_information (Melder_double (my constraints [icons]. ranking));
+END2 }
 
-FORM (OTGrammar_getDisharmony, L"Get disharmony", 0)
+FORM (OTGrammar_getDisharmony, L"Get disharmony", 0) {
 	NATURAL (L"Constraint number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long icons = GET_INTEGER (L"Constraint number");
 	if (icons > my numberOfConstraints)
 		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
 	Melder_information (Melder_double (my constraints [icons]. disharmony));
-END
+END2 }
+
+DIRECT2 (OTGrammar_getNumberOfTableaus) {
+	iam_ONLY (OTGrammar);
+	Melder_information (Melder_integer (my numberOfTableaus));
+END2 }
 
-FORM (OTGrammar_getInput, L"Get input", 0)
+FORM (OTGrammar_getInput, L"Get input", 0) {
 	NATURAL (L"Tableau number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long itab = GET_INTEGER (L"Tableau number");
 	if (itab > my numberOfTableaus)
 		Melder_throw ("The specified tableau number should not exceed the number of tableaus.");
 	Melder_information (my tableaus [itab]. input);
-END
-
-DIRECT (OTGrammar_getInputs)
-	LOOP {
-		iam (OTGrammar);
-		autoStrings thee = OTGrammar_getInputs (me);
-		praat_new (thee.transfer(), my name, L"_in");
-	}
-END
-
-FORM (OTGrammar_getInterpretiveParse, L"OTGrammar: Interpretive parse", 0)
-	SENTENCE (L"Partial output", L"")
-	OK
-DO
-	iam_ONLY (OTGrammar);
-	long bestInput, bestOutput;
-	OTGrammar_getInterpretiveParse (me, GET_STRING (L"Partial output"), & bestInput, & bestOutput);
-	Melder_information (L"Best input = ", Melder_integer (bestInput), L": ", my tableaus [bestInput]. input,
-		L"\nBest output = ", Melder_integer (bestOutput), L": ", my tableaus [bestInput]. candidates [bestOutput]. output);
-END
+END2 }
 
-FORM (OTGrammar_getNumberOfCandidates, L"Get number of candidates", 0)
+FORM (OTGrammar_getNumberOfCandidates, L"Get number of candidates", 0) {
 	NATURAL (L"Tableau number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long itab = GET_INTEGER (L"Tableau number");
 	if (itab > my numberOfTableaus)
 		Melder_throw ("The specified tableau number should not exceed the number of tableaus.");
 	Melder_information (Melder_integer (my tableaus [itab]. numberOfCandidates));
-END
-
-DIRECT (OTGrammar_getNumberOfConstraints)
-	iam_ONLY (OTGrammar);
-	Melder_information (Melder_integer (my numberOfConstraints));
-END
+END2 }
 
-FORM (OTGrammar_getNumberOfOptimalCandidates, L"Get number of optimal candidates", 0)
+FORM (OTGrammar_getCandidate, L"Get candidate", 0) {
 	NATURAL (L"Tableau number", L"1")
-	OK
+	NATURAL (L"Candidate number", L"1")
+	OK2
 DO
 	iam_ONLY (OTGrammar);
-	long itab = GET_INTEGER (L"Tableau number");
+	OTGrammarTableau tableau;
+	long itab = GET_INTEGER (L"Tableau number"), icand = GET_INTEGER (L"Candidate number");
 	if (itab > my numberOfTableaus)
 		Melder_throw ("The specified tableau number should not exceed the number of tableaus.");
-	Melder_information (Melder_integer (OTGrammar_getNumberOfOptimalCandidates (me, itab)));
-END
-
-DIRECT (OTGrammar_getNumberOfTableaus)
-	iam_ONLY (OTGrammar);
-	Melder_information (Melder_integer (my numberOfTableaus));
-END
+	tableau = & my tableaus [itab];
+	if (icand > tableau -> numberOfCandidates)
+		Melder_throw ("The specified candidate should not exceed the number of candidates.");
+	Melder_information (tableau -> candidates [icand]. output);
+END2 }
 
-FORM (OTGrammar_getNumberOfViolations, L"Get number of violations", 0)
+FORM (OTGrammar_getNumberOfViolations, L"Get number of violations", 0) {
 	NATURAL (L"Tableau number", L"1")
 	NATURAL (L"Candidate number", L"1")
 	NATURAL (L"Constraint number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long itab = GET_INTEGER (L"Tableau number"), icand = GET_INTEGER (L"Candidate number"), icons = GET_INTEGER (L"Constraint number");
@@ -606,14 +590,27 @@ DO
 	if (icons > my numberOfConstraints)
 		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
 	Melder_information (Melder_integer (my tableaus [itab]. candidates [icand]. marks [icons]));
-END
+END2 }
+
+#pragma mark Query (parse)
 
-FORM (OTGrammar_compareCandidates, L"Compare candidates", 0)
+FORM (OTGrammar_getWinner, L"Get winner", 0) {
+	NATURAL (L"Tableau", L"1")
+	OK2
+DO
+	iam_ONLY (OTGrammar);
+	long itab = GET_INTEGER (L"Tableau");
+	if (itab > my numberOfTableaus)
+		Melder_throw ("The specified tableau number should not exceed the number of tableaus.");
+	Melder_information (Melder_integer (OTGrammar_getWinner (me, itab)));
+END2 }
+
+FORM (OTGrammar_compareCandidates, L"Compare candidates", 0) {
 	NATURAL (L"Tableau number 1", L"1")
 	NATURAL (L"Candidate number 1", L"1")
 	NATURAL (L"Tableau number 2", L"1")
 	NATURAL (L"Candidate number 2", L"2")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long itab1 = GET_INTEGER (L"Tableau number 1"), icand1 = GET_INTEGER (L"Candidate number 1");
@@ -627,69 +624,23 @@ DO
 	if (icand2 > my tableaus [itab1]. numberOfCandidates)
 		Melder_throw ("The specified candidate (number 2) should not exceed the number of candidates for this tableau.");
 	Melder_information (Melder_integer (OTGrammar_compareCandidates (me, itab1, icand1, itab2, icand2)));
-END
-
-FORM (OTGrammar_getRankingValue, L"Get ranking value", 0)
-	NATURAL (L"Constraint number", L"1")
-	OK
-DO
-	iam_ONLY (OTGrammar);
-	long icons = GET_INTEGER (L"Constraint number");
-	if (icons > my numberOfConstraints)
-		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
-	Melder_information (Melder_double (my constraints [icons]. ranking));
-END
+END2 }
 
-FORM (OTGrammar_getWinner, L"Get winner", 0)
-	NATURAL (L"Tableau", L"1")
-	OK
+FORM (OTGrammar_getNumberOfOptimalCandidates, L"Get number of optimal candidates", 0) {
+	NATURAL (L"Tableau number", L"1")
+	OK2
 DO
 	iam_ONLY (OTGrammar);
-	long itab = GET_INTEGER (L"Tableau");
+	long itab = GET_INTEGER (L"Tableau number");
 	if (itab > my numberOfTableaus)
 		Melder_throw ("The specified tableau number should not exceed the number of tableaus.");
-	Melder_information (Melder_integer (OTGrammar_getWinner (me, itab)));
-END
-
-FORM (OTGrammar_inputToOutput, L"OTGrammar: Input to output", L"OTGrammar: Input to output...")
-	SENTENCE (L"Input form", L"")
-	REAL (L"Evaluation noise", L"2.0")
-	OK
-DO
-	iam_ONLY (OTGrammar);
-	wchar_t output [100];
-	OTGrammar_inputToOutput (me, GET_STRING (L"Input form"), output, GET_REAL (L"Evaluation noise"));
-	Melder_information (output);
-	praat_dataChanged (me);
-END
-
-FORM (OTGrammar_inputToOutputs, L"OTGrammar: Input to outputs", L"OTGrammar: Input to outputs...")
-	NATURAL (L"Trials", L"1000")
-	REAL (L"Evaluation noise", L"2.0")
-	SENTENCE (L"Input form", L"")
-	OK
-DO
-	iam_ONLY (OTGrammar);
-	autoStrings thee = OTGrammar_inputToOutputs (me, GET_STRING (L"Input form"), GET_INTEGER (L"Trials"), GET_REAL (L"Evaluation noise"));
-	praat_new (thee.transfer(), my name, L"_out");
-	praat_dataChanged (me);
-END
-
-FORM (OTGrammar_inputsToOutputs, L"OTGrammar: Inputs to outputs", L"OTGrammar: Inputs to outputs...")
-	REAL (L"Evaluation noise", L"2.0")
-	OK
-DO
-	iam_ONLY (OTGrammar);
-	thouart_ONLY (Strings);
-	autoStrings him = OTGrammar_inputsToOutputs (me, thee, GET_REAL (L"Evaluation noise"));
-	praat_new (him.transfer(), my name, L"_out");
-	praat_dataChanged (me);
-END
+	Melder_information (Melder_integer (OTGrammar_getNumberOfOptimalCandidates (me, itab)));
+END2 }
 
-FORM (OTGrammar_isCandidateGrammatical, L"Is candidate grammatical?", 0)
+FORM (OTGrammar_isCandidateGrammatical, L"Is candidate grammatical?", 0) {
 	NATURAL (L"Tableau", L"1")
 	NATURAL (L"Candidate", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long itab = GET_INTEGER (L"Tableau");
@@ -699,12 +650,12 @@ DO
 	if (icand > my tableaus [itab]. numberOfCandidates)
 		Melder_throw ("The specified candidate should not exceed the number of candidates.");
 	Melder_information (Melder_integer (OTGrammar_isCandidateGrammatical (me, itab, icand)));
-END
+END2 }
 
-FORM (OTGrammar_isCandidateSinglyGrammatical, L"Is candidate singly grammatical?", 0)
+FORM (OTGrammar_isCandidateSinglyGrammatical, L"Is candidate singly grammatical?", 0) {
 	NATURAL (L"Tableau", L"1")
 	NATURAL (L"Candidate", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	long itab = GET_INTEGER (L"Tableau");
@@ -714,79 +665,190 @@ DO
 	if (icand > my tableaus [itab]. numberOfCandidates)
 		Melder_throw ("The specified candidate should not exceed the number of candidates.");
 	Melder_information (Melder_integer (OTGrammar_isCandidateSinglyGrammatical (me, itab, icand)));
-END
+END2 }
+
+FORM (OTGrammar_getInterpretiveParse, L"OTGrammar: Interpretive parse", 0) {
+	SENTENCE (L"Partial output", L"")
+	OK2
+DO
+	iam_ONLY (OTGrammar);
+	long bestInput, bestOutput;
+	OTGrammar_getInterpretiveParse (me, GET_STRING (L"Partial output"), & bestInput, & bestOutput);
+	Melder_information (L"Best input = ", Melder_integer (bestInput), L": ", my tableaus [bestInput]. input,
+		L"\nBest output = ", Melder_integer (bestOutput), L": ", my tableaus [bestInput]. candidates [bestOutput]. output);
+END2 }
 
-FORM (OTGrammar_isPartialOutputGrammatical, L"Is partial output grammatical?", 0)
+FORM (OTGrammar_isPartialOutputGrammatical, L"Is partial output grammatical?", 0) {
 	SENTENCE (L"Partial output", L"")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	Melder_information (Melder_integer (OTGrammar_isPartialOutputGrammatical (me, GET_STRING (L"Partial output"))));
-END
+END2 }
 
-FORM (OTGrammar_isPartialOutputSinglyGrammatical, L"Is partial output singly grammatical?", 0)
+FORM (OTGrammar_isPartialOutputSinglyGrammatical, L"Is partial output singly grammatical?", 0) {
 	SENTENCE (L"Partial output", L"")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	Melder_information (Melder_integer (OTGrammar_isPartialOutputSinglyGrammatical (me, GET_STRING (L"Partial output"))));
-END
+END2 }
+
+#pragma mark -
+
+FORM (OTGrammar_generateInputs, L"Generate inputs", L"OTGrammar: Generate inputs...") {
+	NATURAL (L"Number of trials", L"1000")
+	OK2
+DO
+	LOOP {
+		iam (OTGrammar);
+		autoStrings thee = OTGrammar_generateInputs (me, GET_INTEGER (L"Number of trials"));
+		praat_new (thee.transfer(), my name, L"_in");
+	}
+END2 }
+
+DIRECT2 (OTGrammar_getInputs) {
+	LOOP {
+		iam (OTGrammar);
+		autoStrings thee = OTGrammar_getInputs (me);
+		praat_new (thee.transfer(), my name, L"_in");
+	}
+END2 }
+
+DIRECT2 (OTGrammar_measureTypology) {
+	LOOP try {
+		iam (OTGrammar);
+		autoDistributions thee = OTGrammar_measureTypology (me);
+		praat_new (thee.transfer(), my name, L"_out");
+		praat_dataChanged (me);
+	} catch (MelderError) {
+		praat_dataChanged (OBJECT);
+		throw;
+	}
+END2 }
+
+#pragma mark Evaluate
+
+FORM (OTGrammar_evaluate, L"OTGrammar: Evaluate", 0) {
+	REAL (L"Evaluation noise", L"2.0")
+	OK2
+DO
+	LOOP {
+		iam (OTGrammar);
+		OTGrammar_newDisharmonies (me, GET_REAL (L"Evaluation noise"));
+		praat_dataChanged (me);
+	}
+END2 }
+
+FORM (OTGrammar_inputToOutput, L"OTGrammar: Input to output", L"OTGrammar: Input to output...") {
+	SENTENCE (L"Input form", L"")
+	REAL (L"Evaluation noise", L"2.0")
+	OK2
+DO
+	iam_ONLY (OTGrammar);
+	wchar_t output [100];
+	OTGrammar_inputToOutput (me, GET_STRING (L"Input form"), output, GET_REAL (L"Evaluation noise"));
+	Melder_information (output);
+	praat_dataChanged (me);
+END2 }
+
+FORM (OTGrammar_inputToOutputs, L"OTGrammar: Input to outputs", L"OTGrammar: Input to outputs...") {
+	NATURAL (L"Trials", L"1000")
+	REAL (L"Evaluation noise", L"2.0")
+	SENTENCE (L"Input form", L"")
+	OK2
+DO
+	iam_ONLY (OTGrammar);
+	autoStrings thee = OTGrammar_inputToOutputs (me, GET_STRING (L"Input form"), GET_INTEGER (L"Trials"), GET_REAL (L"Evaluation noise"));
+	praat_new (thee.transfer(), my name, L"_out");
+	praat_dataChanged (me);
+END2 }
+
+FORM (OTGrammar_to_Distributions, L"OTGrammar: Compute output distributions", L"OTGrammar: To output Distributions...") {
+	NATURAL (L"Trials per input", L"100000")
+	REAL (L"Evaluation noise", L"2.0")
+	OK2
+DO
+	LOOP {
+		iam (OTGrammar);
+		try {
+			autoDistributions thee = OTGrammar_to_Distribution (me, GET_INTEGER (L"Trials per input"), GET_REAL (L"Evaluation noise"));
+			praat_new (thee.transfer(), my name, L"_out");
+			praat_dataChanged (me);
+		} catch (MelderError) {
+			praat_dataChanged (me);
+			throw;
+		}
+	}
+END2 }
+
+FORM (OTGrammar_to_PairDistribution, L"OTGrammar: Compute output distributions", 0) {
+	NATURAL (L"Trials per input", L"100000")
+	REAL (L"Evaluation noise", L"2.0")
+	OK2
+DO
+	LOOP try {
+		iam (OTGrammar);
+		autoPairDistribution thee = OTGrammar_to_PairDistribution (me, GET_INTEGER (L"Trials per input"), GET_REAL (L"Evaluation noise"));
+		praat_new (thee.transfer(), my name, L"_out");
+		praat_dataChanged (me);
+	} catch (MelderError) {
+		praat_dataChanged (OBJECT);
+		throw;
+	}
+END2 }
+
+#pragma mark Modify ranking
+
+FORM (OTGrammar_setRanking, L"OTGrammar: Set ranking", 0) {
+	NATURAL (L"Constraint", L"1")
+	REAL (L"Ranking", L"100.0")
+	REAL (L"Disharmony", L"100.0")
+	OK2
+DO
+	LOOP {
+		iam (OTGrammar);
+		OTGrammar_setRanking (me, GET_INTEGER (L"Constraint"), GET_REAL (L"Ranking"), GET_REAL (L"Disharmony"));
+		praat_dataChanged (me);
+	}
+END2 }
+
+FORM (OTGrammar_resetAllRankings, L"OTGrammar: Reset all rankings", 0) {
+	REAL (L"Ranking", L"100.0")
+	OK2
+DO
+	LOOP {
+		iam (OTGrammar);
+		OTGrammar_reset (me, GET_REAL (L"Ranking"));
+		praat_dataChanged (me);
+	}
+END2 }
 
-FORM (OTGrammar_learn, L"OTGrammar: Learn", L"OTGrammar & 2 Strings: Learn...")
-	REAL (L"Evaluation noise", L"2.0")
-	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
-	REAL (L"Plasticity", L"0.1")
-	REAL (L"Rel. plasticity spreading", L"0.1")
-	BOOLEAN (L"Honour local rankings", 1)
-	NATURAL (L"Number of chews", L"1")
-	OK
+FORM (OTGrammar_resetToRandomRanking, L"OTGrammar: Reset to random ranking", 0) {
+	REAL (L"Mean", L"10.0")
+	POSITIVE (L"Standard deviation", L"0.0001")
+	OK2
 DO
-	iam_ONLY (OTGrammar);
-	Strings inputs = NULL, outputs = NULL;
-	WHERE (SELECTED && CLASS == classStrings) { if (! inputs) inputs = (Strings) OBJECT; else outputs = (Strings) OBJECT; }
-	try {
-		OTGrammar_learn (me, inputs, outputs,
-			GET_REAL (L"Evaluation noise"),
-			GET_ENUM (kOTGrammar_rerankingStrategy, L"Update rule"),
-			GET_INTEGER (L"Honour local rankings"),
-			GET_REAL (L"Plasticity"), GET_REAL (L"Rel. plasticity spreading"), GET_INTEGER (L"Number of chews"));
+	LOOP {
+		iam (OTGrammar);
+		OTGrammar_resetToRandomRanking (me, GET_REAL (L"Mean"), GET_REAL (L"Standard deviation"));
 		praat_dataChanged (me);
-	} catch (MelderError) {
-		praat_dataChanged (me);   // partial change
-		throw;
 	}
-END
+END2 }
 
-FORM (OTGrammar_learnFromPartialOutputs, L"OTGrammar: Learn from partial adult outputs", 0)
-	REAL (L"Evaluation noise", L"2.0")
-	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
-	REAL (L"Plasticity", L"0.1")
-	REAL (L"Rel. plasticity spreading", L"0.1")
-	BOOLEAN (L"Honour local rankings", 1)
-	NATURAL (L"Number of chews", L"1")
-	INTEGER (L"Store history every", L"0")
-	OK
+FORM (OTGrammar_resetToRandomTotalRanking, L"OTGrammar: Reset to random total ranking", 0) {
+	REAL (L"Maximum ranking", L"100.0")
+	POSITIVE (L"Ranking distance", L"1.0")
+	OK2
 DO
-	iam_ONLY (OTGrammar);
-	thouart_ONLY (Strings);
-	OTHistory history = NULL;
-	try {
-		OTGrammar_learnFromPartialOutputs (me, thee,
-			GET_REAL (L"Evaluation noise"),
-			GET_ENUM (kOTGrammar_rerankingStrategy, L"Update rule"),
-			GET_INTEGER (L"Honour local rankings"),
-			GET_REAL (L"Plasticity"), GET_REAL (L"Rel. plasticity spreading"), GET_INTEGER (L"Number of chews"),
-			GET_INTEGER (L"Store history every"), & history);
+	LOOP {
+		iam (OTGrammar);
+		OTGrammar_resetToRandomTotalRanking (me, GET_REAL (L"Maximum ranking"), GET_REAL (L"Ranking distance"));
 		praat_dataChanged (me);
-	} catch (MelderError) {
-		praat_dataChanged (me);   // e.g. in case of partial learning
-		Melder_flushError (NULL);
-		// trickle down to save history
 	}
-	if (history) praat_new (history, my name);
-END
+END2 }
 
-FORM (OTGrammar_learnOne, L"OTGrammar: Learn one", L"OTGrammar: Learn one...")
+FORM (OTGrammar_learnOne, L"OTGrammar: Learn one", L"OTGrammar: Learn one...") {
 	SENTENCE (L"Input string", L"")
 	SENTENCE (L"Output string", L"")
 	REAL (L"Evaluation noise", L"2.0")
@@ -794,7 +856,7 @@ FORM (OTGrammar_learnOne, L"OTGrammar: Learn one", L"OTGrammar: Learn one...")
 	REAL (L"Plasticity", L"0.1")
 	REAL (L"Rel. plasticity spreading", L"0.1")
 	BOOLEAN (L"Honour local rankings", 1)
-	OK
+	OK2
 DO
 	LOOP try {
 		iam (OTGrammar);
@@ -808,9 +870,9 @@ DO
 		praat_dataChanged (OBJECT);
 		throw;
 	}
-END
+END2 }
 
-FORM (OTGrammar_learnOneFromPartialOutput, L"OTGrammar: Learn one from partial adult output", 0)
+FORM (OTGrammar_learnOneFromPartialOutput, L"OTGrammar: Learn one from partial adult output", 0) {
 	LABEL (L"", L"Partial adult surface form (e.g. overt form):")
 	SENTENCE (L"Partial output", L"")
 	REAL (L"Evaluation noise", L"2.0")
@@ -819,7 +881,7 @@ FORM (OTGrammar_learnOneFromPartialOutput, L"OTGrammar: Learn one from partial a
 	REAL (L"Rel. plasticity spreading", L"0.1")
 	BOOLEAN (L"Honour local rankings", 1)
 	NATURAL (L"Number of chews", L"1")
-	OK
+	OK2
 DO
 	LOOP try {
 		iam (OTGrammar);
@@ -833,105 +895,154 @@ DO
 		praat_dataChanged (OBJECT);
 		throw;
 	}
-END
+END2 }
 
-FORM (OTGrammar_removeConstraint, L"OTGrammar: Remove constraint", 0)
-	SENTENCE (L"Constraint name", L"")
-	OK
+#pragma mark Modify behaviour
+
+FORM (OTGrammar_setDecisionStrategy, L"OTGrammar: Set decision strategy", 0) {
+	RADIO_ENUM (L"Decision strategy", kOTGrammar_decisionStrategy, DEFAULT)
+	OK2
+iam_ONLY (OTGrammar);
+SET_ENUM (L"Decision strategy", kOTGrammar_decisionStrategy, my decisionStrategy);
 DO
-	LOOP {
-		iam (OTGrammar);
-		OTGrammar_removeConstraint (me, GET_STRING (L"Constraint name"));
-		praat_dataChanged (me);
-	}
-END
+	iam_ONLY (OTGrammar);
+	my decisionStrategy = GET_ENUM (kOTGrammar_decisionStrategy, L"Decision strategy");
+	praat_dataChanged (me);
+END2 }
 
-FORM (OTGrammar_removeHarmonicallyBoundedCandidates, L"OTGrammar: Remove harmonically bounded candidates", 0)
-	BOOLEAN (L"Singly", 0)
-	OK
+FORM (OTGrammar_setLeak, L"OTGrammar: Set leak", 0) {
+	REAL (L"Leak", L"0.0")
+	OK2
+iam_ONLY (OTGrammar);
+SET_REAL (L"Leak", my leak);
 DO
-	LOOP {
-		iam (OTGrammar);
-		OTGrammar_removeHarmonicallyBoundedCandidates (me, GET_INTEGER (L"Singly"));
-		praat_dataChanged (me);
-	}
-END
+	iam_ONLY (OTGrammar);
+	my leak = GET_REAL (L"Leak");
+	praat_dataChanged (me);
+END2 }
 
-FORM (OTGrammar_resetAllRankings, L"OTGrammar: Reset all rankings", 0)
-	REAL (L"Ranking", L"100.0")
-	OK
+FORM (OTGrammar_setConstraintPlasticity, L"OTGrammar: Set constraint plasticity", 0) {
+	NATURAL (L"Constraint", L"1")
+	REAL (L"Plasticity", L"1.0")
+	OK2
 DO
 	LOOP {
 		iam (OTGrammar);
-		OTGrammar_reset (me, GET_REAL (L"Ranking"));
-		praat_dataChanged (me);
+		OTGrammar_setConstraintPlasticity (me, GET_INTEGER (L"Constraint"), GET_REAL (L"Plasticity"));
+		praat_dataChanged (OBJECT);
 	}
-END
+END2 }
 
-FORM (OTGrammar_resetToRandomTotalRanking, L"OTGrammar: Reset to random total ranking", 0)
-	REAL (L"Maximum ranking", L"100.0")
-	POSITIVE (L"Ranking distance", L"1.0")
-	OK
+#pragma mark Modify structure
+
+FORM (OTGrammar_removeConstraint, L"OTGrammar: Remove constraint", 0) {
+	SENTENCE (L"Constraint name", L"")
+	OK2
 DO
 	LOOP {
 		iam (OTGrammar);
-		OTGrammar_resetToRandomTotalRanking (me, GET_REAL (L"Maximum ranking"), GET_REAL (L"Ranking distance"));
+		OTGrammar_removeConstraint (me, GET_STRING (L"Constraint name"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (OTGrammar_setConstraintPlasticity, L"OTGrammar: Set constraint plasticity", 0)
-	NATURAL (L"Constraint", L"1")
-	REAL (L"Plasticity", L"1.0")
-	OK
+FORM (OTGrammar_removeHarmonicallyBoundedCandidates, L"OTGrammar: Remove harmonically bounded candidates", 0) {
+	BOOLEAN (L"Singly", 0)
+	OK2
 DO
 	LOOP {
 		iam (OTGrammar);
-		OTGrammar_setConstraintPlasticity (me, GET_INTEGER (L"Constraint"), GET_REAL (L"Plasticity"));
-		praat_dataChanged (OBJECT);
+		OTGrammar_removeHarmonicallyBoundedCandidates (me, GET_INTEGER (L"Singly"));
+		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (OTGrammar_setDecisionStrategy, L"OTGrammar: Set decision strategy", 0)
-	RADIO_ENUM (L"Decision strategy", kOTGrammar_decisionStrategy, DEFAULT)
-	OK
-iam_ONLY (OTGrammar);
-SET_ENUM (L"Decision strategy", kOTGrammar_decisionStrategy, my decisionStrategy);
+#pragma mark OTGRAMMAR & STRINGS
+
+FORM (OTGrammar_Strings_inputsToOutputs, L"OTGrammar: Inputs to outputs", L"OTGrammar: Inputs to outputs...") {
+	REAL (L"Evaluation noise", L"2.0")
+	OK2
 DO
 	iam_ONLY (OTGrammar);
-	my decisionStrategy = GET_ENUM (kOTGrammar_decisionStrategy, L"Decision strategy");
+	thouart_ONLY (Strings);
+	autoStrings him = OTGrammar_inputsToOutputs (me, thee, GET_REAL (L"Evaluation noise"));
+	praat_new (him.transfer(), my name, L"_out");
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (OTGrammar_setLeak, L"OTGrammar: Set leak", 0)
-	REAL (L"Leak", L"0.0")
-	OK
-iam_ONLY (OTGrammar);
-SET_REAL (L"Leak", my leak);
+DIRECT2 (OTGrammar_Strings_areAllPartialOutputsGrammatical) {
+	iam_ONLY (OTGrammar);
+	thouart_ONLY (Strings);
+	Melder_information (Melder_integer (OTGrammar_areAllPartialOutputsGrammatical (me, thee)));
+END2 }
+
+DIRECT2 (OTGrammar_Strings_areAllPartialOutputsSinglyGrammatical) {
+	iam_ONLY (OTGrammar);
+	thouart_ONLY (Strings);
+	Melder_information (Melder_integer (OTGrammar_areAllPartialOutputsSinglyGrammatical (me, thee)));
+END2 }
+
+FORM (OTGrammar_Stringses_learn, L"OTGrammar: Learn", L"OTGrammar & 2 Strings: Learn...") {
+	REAL (L"Evaluation noise", L"2.0")
+	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
+	REAL (L"Plasticity", L"0.1")
+	REAL (L"Rel. plasticity spreading", L"0.1")
+	BOOLEAN (L"Honour local rankings", 1)
+	NATURAL (L"Number of chews", L"1")
+	OK2
 DO
 	iam_ONLY (OTGrammar);
-	my leak = GET_REAL (L"Leak");
-	praat_dataChanged (me);
-END
+	Strings inputs = NULL, outputs = NULL;
+	WHERE (SELECTED && CLASS == classStrings) { if (! inputs) inputs = (Strings) OBJECT; else outputs = (Strings) OBJECT; }
+	try {
+		OTGrammar_learn (me, inputs, outputs,
+			GET_REAL (L"Evaluation noise"),
+			GET_ENUM (kOTGrammar_rerankingStrategy, L"Update rule"),
+			GET_INTEGER (L"Honour local rankings"),
+			GET_REAL (L"Plasticity"), GET_REAL (L"Rel. plasticity spreading"), GET_INTEGER (L"Number of chews"));
+		praat_dataChanged (me);
+	} catch (MelderError) {
+		praat_dataChanged (me);   // partial change
+		throw;
+	}
+END2 }
 
-FORM (OTGrammar_setRanking, L"OTGrammar: Set ranking", 0)
-	NATURAL (L"Constraint", L"1")
-	REAL (L"Ranking", L"100.0")
-	REAL (L"Disharmony", L"100.0")
-	OK
+FORM (OTGrammar_Strings_learnFromPartialOutputs, L"OTGrammar: Learn from partial adult outputs", 0) {
+	REAL (L"Evaluation noise", L"2.0")
+	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
+	REAL (L"Plasticity", L"0.1")
+	REAL (L"Rel. plasticity spreading", L"0.1")
+	BOOLEAN (L"Honour local rankings", 1)
+	NATURAL (L"Number of chews", L"1")
+	INTEGER (L"Store history every", L"0")
+	OK2
 DO
-	LOOP {
-		iam (OTGrammar);
-		OTGrammar_setRanking (me, GET_INTEGER (L"Constraint"), GET_REAL (L"Ranking"), GET_REAL (L"Disharmony"));
+	iam_ONLY (OTGrammar);
+	thouart_ONLY (Strings);
+	OTHistory history = NULL;
+	try {
+		OTGrammar_learnFromPartialOutputs (me, thee,
+			GET_REAL (L"Evaluation noise"),
+			GET_ENUM (kOTGrammar_rerankingStrategy, L"Update rule"),
+			GET_INTEGER (L"Honour local rankings"),
+			GET_REAL (L"Plasticity"), GET_REAL (L"Rel. plasticity spreading"), GET_INTEGER (L"Number of chews"),
+			GET_INTEGER (L"Store history every"), & history);
 		praat_dataChanged (me);
+	} catch (MelderError) {
+		praat_dataChanged (me);   // e.g. in case of partial learning
+		Melder_flushError (NULL);
+		// trickle down to save history
 	}
-END
+	if (history) praat_new (history, my name);
+END2 }
 
-FORM (OTGrammar_Distributions_getFractionCorrect, L"OTGrammar & Distributions: Get fraction correct...", 0)
+#pragma mark OTGRAMMAR & DISTRIBUTIONS
+
+FORM (OTGrammar_Distributions_getFractionCorrect, L"OTGrammar & Distributions: Get fraction correct...", 0) {
 	NATURAL (L"Column number", L"1")
 	REAL (L"Evaluation noise", L"2.0")
 	INTEGER (L"Replications", L"100000")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (Distributions);
@@ -939,9 +1050,9 @@ DO
 		GET_REAL (L"Evaluation noise"), GET_INTEGER (L"Replications"));
 	praat_dataChanged (me);
 	Melder_informationReal (result, NULL);
-END
+END2 }
 
-FORM (OTGrammar_Distributions_learnFromPartialOutputs, L"OTGrammar & Distributions: Learn from partial outputs", L"OT learning 6. Shortcut to grammar learning")
+FORM (OTGrammar_Distributions_learnFromPartialOutputs, L"OTGrammar & Distributions: Learn from partial outputs", L"OT learning 6. Shortcut to grammar learning") {
 	NATURAL (L"Column number", L"1")
 	REAL (L"Evaluation noise", L"2.0")
 	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
@@ -953,7 +1064,7 @@ FORM (OTGrammar_Distributions_learnFromPartialOutputs, L"OTGrammar & Distributio
 	BOOLEAN (L"Honour local rankings", 1)
 	NATURAL (L"Number of chews", L"1")
 	INTEGER (L"Store history every", L"0")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (Distributions);
@@ -966,40 +1077,76 @@ DO
 			GET_REAL (L"Initial plasticity"), GET_INTEGER (L"Replications per plasticity"),
 			GET_REAL (L"Plasticity decrement"), GET_INTEGER (L"Number of plasticities"),
 			GET_REAL (L"Rel. plasticity spreading"), GET_INTEGER (L"Number of chews"),
-			GET_INTEGER (L"Store history every"), & history);
+			GET_INTEGER (L"Store history every"), & history, false, false);
+		praat_dataChanged (me);
+	} catch (MelderError) {
+		praat_dataChanged (me);
+		Melder_flushError (NULL);
+	}
+	if (history) praat_new (history, my name);
+END2 }
+
+FORM (OTGrammar_Distributions_learnFromPartialOutputs_rrip, L"OTGrammar & Distributions: Learn from partial outputs (rrip)", L"OT learning 6. Shortcut to grammar learning") {
+	NATURAL (L"Column number", L"1")
+	REAL (L"Evaluation noise", L"2.0")
+	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
+	REAL (L"Initial plasticity", L"1.0")
+	NATURAL (L"Replications per plasticity", L"100000")
+	REAL (L"Plasticity decrement", L"0.1")
+	NATURAL (L"Number of plasticities", L"4")
+	REAL (L"Rel. plasticity spreading", L"0.1")
+	BOOLEAN (L"Honour local rankings", 1)
+	NATURAL (L"Number of chews", L"1")
+	INTEGER (L"Store history every", L"0")
+	OK2
+DO
+	iam_ONLY (OTGrammar);
+	thouart_ONLY (Distributions);
+	OTHistory history = NULL;
+	try {
+		OTGrammar_Distributions_learnFromPartialOutputs (me, thee, GET_INTEGER (L"Column number"),
+			GET_REAL (L"Evaluation noise"),
+			GET_ENUM (kOTGrammar_rerankingStrategy, L"Update rule"),
+			GET_INTEGER (L"Honour local rankings"),
+			GET_REAL (L"Initial plasticity"), GET_INTEGER (L"Replications per plasticity"),
+			GET_REAL (L"Plasticity decrement"), GET_INTEGER (L"Number of plasticities"),
+			GET_REAL (L"Rel. plasticity spreading"), GET_INTEGER (L"Number of chews"),
+			GET_INTEGER (L"Store history every"), & history, true, true);
 		praat_dataChanged (me);
 	} catch (MelderError) {
 		praat_dataChanged (me);
 		Melder_flushError (NULL);
 	}
 	if (history) praat_new (history, my name);
-END
+END2 }
 
-FORM (OTGrammar_Distributions_listObligatoryRankings, L"OTGrammar & Distributions: Get fraction correct...", 0)
+FORM (OTGrammar_Distributions_listObligatoryRankings, L"OTGrammar & Distributions: Get fraction correct...", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (Distributions);
 	OTGrammar_Distributions_listObligatoryRankings (me, thee, GET_INTEGER (L"Column number"));
-END
+END2 }
 
-FORM (OTGrammar_PairDistribution_findPositiveWeights, L"OTGrammar & PairDistribution: Find positive weights", L"OTGrammar & PairDistribution: Find positive weights...")
+#pragma mark OTGRAMMAR & PAIRDISTRIBUTION
+
+FORM (OTGrammar_PairDistribution_findPositiveWeights, L"OTGrammar & PairDistribution: Find positive weights", L"OTGrammar & PairDistribution: Find positive weights...") {
 	POSITIVE (L"Weight floor", L"1.0")
 	POSITIVE (L"Margin of separation", L"1.0")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (PairDistribution);
 	OTGrammar_PairDistribution_findPositiveWeights_e (me, thee,
 		GET_REAL (L"Weight floor"), GET_REAL (L"Margin of separation"));
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (OTGrammar_PairDistribution_getFractionCorrect, L"OTGrammar & PairDistribution: Get fraction correct...", 0)
+FORM (OTGrammar_PairDistribution_getFractionCorrect, L"OTGrammar & PairDistribution: Get fraction correct...", 0) {
 	REAL (L"Evaluation noise", L"2.0")
 	INTEGER (L"Replications", L"100000")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (PairDistribution);
@@ -1013,12 +1160,12 @@ DO
 		throw;
 	}
 	Melder_informationReal (result, NULL);
-END
+END2 }
 
-FORM (OTGrammar_PairDistribution_getMinimumNumberCorrect, L"OTGrammar & PairDistribution: Get minimum number correct...", 0)
+FORM (OTGrammar_PairDistribution_getMinimumNumberCorrect, L"OTGrammar & PairDistribution: Get minimum number correct...", 0) {
 	REAL (L"Evaluation noise", L"2.0")
 	INTEGER (L"Replications per input", L"1000")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (PairDistribution);
@@ -1032,9 +1179,9 @@ DO
 		throw;
 	}
 	Melder_information (Melder_integer (result));
-END
+END2 }
 
-FORM (OTGrammar_PairDistribution_learn, L"OTGrammar & PairDistribution: Learn", L"OT learning 6. Shortcut to grammar learning")
+FORM (OTGrammar_PairDistribution_learn, L"OTGrammar & PairDistribution: Learn", L"OT learning 6. Shortcut to grammar learning") {
 	REAL (L"Evaluation noise", L"2.0")
 	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
 	POSITIVE (L"Initial plasticity", L"1.0")
@@ -1044,7 +1191,7 @@ FORM (OTGrammar_PairDistribution_learn, L"OTGrammar & PairDistribution: Learn",
 	REAL (L"Rel. plasticity spreading", L"0.1")
 	BOOLEAN (L"Honour local rankings", 1)
 	NATURAL (L"Number of chews", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (PairDistribution);
@@ -1059,70 +1206,49 @@ DO
 		praat_dataChanged (me);
 		throw;
 	}
-END
+END2 }
 
-DIRECT (OTGrammar_PairDistribution_listObligatoryRankings)
+DIRECT2 (OTGrammar_PairDistribution_listObligatoryRankings) {
 	iam_ONLY (OTGrammar);
 	thouart_ONLY (PairDistribution);
 	OTGrammar_PairDistribution_listObligatoryRankings (me, thee);
-END
+END2 }
 
-FORM (OTGrammar_to_Distributions, L"OTGrammar: Compute output distributions", L"OTGrammar: To output Distributions...")
-	NATURAL (L"Trials per input", L"100000")
-	REAL (L"Evaluation noise", L"2.0")
-	OK
-DO
-	LOOP {
-		iam (OTGrammar);
-		try {
-			autoDistributions thee = OTGrammar_to_Distribution (me, GET_INTEGER (L"Trials per input"), GET_REAL (L"Evaluation noise"));
-			praat_new (thee.transfer(), my name, L"_out");
-			praat_dataChanged (me);
-		} catch (MelderError) {
-			praat_dataChanged (me);
-			throw;
-		}
-	}
-END
+#pragma mark -
+#pragma mark OTMULTI
 
-FORM (OTGrammar_to_PairDistribution, L"OTGrammar: Compute output distributions", 0)
-	NATURAL (L"Trials per input", L"100000")
-	REAL (L"Evaluation noise", L"2.0")
-	OK
+FORM (Create_multi_level_metrics_grammar, L"Create multi-level metrics grammar", 0) {
+	OPTIONMENU (L"Initial ranking", 1)
+		OPTION (L"Equal")
+		OPTION (L"Foot form high")
+		OPTION (L"WSP high")
+	OPTIONMENU (L"Trochaicity constraint", 1)
+		OPTION (L"FtNonfinal")
+		OPTION (L"Trochaic")
+	BOOLEAN (L"Include FootBimoraic", 0)
+	BOOLEAN (L"Include FootBisyllabic", 0)
+	BOOLEAN (L"Include Peripheral", 0)
+	OPTIONMENU (L"Nonfinality constraint", 1)
+		OPTION (L"Nonfinal")
+		OPTION (L"MainNonfinal")
+		OPTION (L"HeadNonfinal")
+	BOOLEAN (L"Overt forms have secondary stress", 1)
+	BOOLEAN (L"Include *Clash and *Lapse", 0)
+	BOOLEAN (L"Include codas", 0)
+	OK2
 DO
-	LOOP try {
-		iam (OTGrammar);
-		autoPairDistribution thee = OTGrammar_to_PairDistribution (me, GET_INTEGER (L"Trials per input"), GET_REAL (L"Evaluation noise"));
-		praat_new (thee.transfer(), my name, L"_out");
-		praat_dataChanged (me);
-	} catch (MelderError) {
-		praat_dataChanged (OBJECT);
-		throw;
-	}
-END
-
-DIRECT (OTGrammar_measureTypology)
-	LOOP try {
-		iam (OTGrammar);
-		autoDistributions thee = OTGrammar_measureTypology (me);
-		praat_new (thee.transfer(), my name, L"_out");
-		praat_dataChanged (me);
-	} catch (MelderError) {
-		praat_dataChanged (OBJECT);
-		throw;
-	}
-END
-
-FORM_WRITE (OTGrammar_writeToHeaderlessSpreadsheetFile, L"Write OTGrammar to spreadsheet", 0, L"txt")
-	iam_ONLY (OTGrammar);
-	OTGrammar_writeToHeaderlessSpreadsheetFile (me, file);
-END
+	praat_new (OTMulti_create_metrics (GET_INTEGER (L"Initial ranking"), GET_INTEGER (L"Trochaicity constraint"),
+		GET_INTEGER (L"Include FootBimoraic"), GET_INTEGER (L"Include FootBisyllabic"),
+		GET_INTEGER (L"Include Peripheral"), GET_INTEGER (L"Nonfinality constraint"),
+		GET_INTEGER (L"Overt forms have secondary stress"), GET_INTEGER (L"Include *Clash and *Lapse"), GET_INTEGER (L"Include codas")),
+		GET_STRING (L"Initial ranking"));
+END2 }
 
-FORM (OTMulti_drawTableau, L"Draw tableau", L"OT learning")
+FORM (OTMulti_drawTableau, L"Draw tableau", L"OT learning") {
 	SENTENCE (L"Partial form 1", L"")
 	SENTENCE (L"Partial form 2", L"")
 	BOOLEAN (L"Show disharmonies", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -1130,13 +1256,13 @@ DO
 		OTMulti_drawTableau (me, GRAPHICS, GET_STRING (L"Partial form 1"), GET_STRING (L"Partial form 2"),
 			false, GET_INTEGER (L"Show disharmonies"));
 	}
-END
+END2 }
 
-FORM (OTMulti_drawTableau_narrowly, L"Draw tableau (narrowly)", L"OT learning")
+FORM (OTMulti_drawTableau_narrowly, L"Draw tableau (narrowly)", L"OT learning") {
 	SENTENCE (L"Partial form 1", L"")
 	SENTENCE (L"Partial form 2", L"")
 	BOOLEAN (L"Show disharmonies", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -1144,95 +1270,95 @@ DO
 		OTMulti_drawTableau (me, GRAPHICS, GET_STRING (L"Partial form 1"), GET_STRING (L"Partial form 2"),
 			true, GET_INTEGER (L"Show disharmonies"));
 	}
-END
+END2 }
 
-DIRECT (OTMulti_edit)
+DIRECT2 (OTMulti_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot edit an OTMulti from batch.");
 	LOOP {
 		iam (OTMulti);
 		autoOTMultiEditor editor = OTMultiEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.transfer(), IOBJECT);
 	}
-END
+END2 }
 
-FORM (OTMulti_evaluate, L"OTMulti: Evaluate", 0)
+FORM (OTMulti_evaluate, L"OTMulti: Evaluate", 0) {
 	REAL (L"Evaluation noise", L"2.0")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	OTMulti_newDisharmonies (me, GET_REAL (L"Evaluation noise"));
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (OTMulti_generateOptimalForms, L"OTMulti: Generate optimal forms", 0)
+FORM (OTMulti_generateOptimalForms, L"OTMulti: Generate optimal forms", 0) {
 	SENTENCE (L"Partial form 1", L"")
 	SENTENCE (L"Partial form 2", L"")
 	NATURAL (L"Number of trials", L"1000")
 	REAL (L"Evaluation noise", L"2.0")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	autoStrings thee = OTMulti_generateOptimalForms (me, GET_STRING (L"Partial form 1"), GET_STRING (L"Partial form 2"),
 		GET_INTEGER (L"Number of trials"), GET_REAL (L"Evaluation noise"));
 	praat_new (thee.transfer(), my name, L"_out");
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (OTMulti_getCandidate, L"Get candidate", 0)
+FORM (OTMulti_getCandidate, L"Get candidate", 0) {
 	NATURAL (L"Candidate", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	long icand = GET_INTEGER (L"Candidate");
 	if (icand > my numberOfCandidates)
 		Melder_throw ("The specified candidate number should not exceed the number of candidates.");
 	Melder_information (my candidates [icand]. string);
-END
+END2 }
 
-FORM (OTMulti_getConstraint, L"Get constraint name", 0)
+FORM (OTMulti_getConstraint, L"Get constraint name", 0) {
 	NATURAL (L"Constraint number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	long icons = GET_INTEGER (L"Constraint number");
 	if (icons > my numberOfConstraints)
 		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
 	Melder_information (my constraints [icons]. name);
-END
+END2 }
 
-FORM (OTMulti_getConstraintIndexFromName, L"OTMulti: Get constraint number", 0)
+FORM (OTMulti_getConstraintIndexFromName, L"OTMulti: Get constraint number", 0) {
 	SENTENCE (L"Constraint name", L"")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	Melder_information (Melder_integer (OTMulti_getConstraintIndexFromName (me, GET_STRING (L"Constraint name"))));
-END
+END2 }
 
-FORM (OTMulti_getDisharmony, L"Get disharmony", 0)
+FORM (OTMulti_getDisharmony, L"Get disharmony", 0) {
 	NATURAL (L"Constraint number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	long icons = GET_INTEGER (L"Constraint number");
 	if (icons > my numberOfConstraints)
 		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
 	Melder_information (Melder_double (my constraints [icons]. disharmony));
-END
+END2 }
 
-DIRECT (OTMulti_getNumberOfCandidates)
+DIRECT2 (OTMulti_getNumberOfCandidates) {
 	iam_ONLY (OTMulti);
 	Melder_information (Melder_integer (my numberOfCandidates));
-END
+END2 }
 
-DIRECT (OTMulti_getNumberOfConstraints)
+DIRECT2 (OTMulti_getNumberOfConstraints) {
 	iam_ONLY (OTMulti);
 	Melder_information (Melder_integer (my numberOfConstraints));
-END
+END2 }
 
-FORM (OTMulti_getNumberOfViolations, L"Get number of violations", 0)
+FORM (OTMulti_getNumberOfViolations, L"Get number of violations", 0) {
 	NATURAL (L"Candidate number", L"1")
 	NATURAL (L"Constraint number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	long icand = GET_INTEGER (L"Candidate number");
@@ -1242,33 +1368,33 @@ DO
 	if (icons > my numberOfConstraints)
 		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
 	Melder_information (Melder_integer (my candidates [icand]. marks [icons]));
-END
+END2 }
 
-FORM (OTMulti_getRankingValue, L"Get ranking value", 0)
+FORM (OTMulti_getRankingValue, L"Get ranking value", 0) {
 	NATURAL (L"Constraint number", L"1")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	long icons = GET_INTEGER (L"Constraint number");
 	if (icons > my numberOfConstraints)
 		Melder_throw ("The specified constraint number should not exceed the number of constraints.");
 	Melder_information (Melder_double (my constraints [icons]. ranking));
-END
+END2 }
 
-FORM (OTMulti_getWinner, L"OTMulti: Get winner", 0)
+FORM (OTMulti_getWinner, L"OTMulti: Get winner", 0) {
 	SENTENCE (L"Partial form 1", L"")
 	SENTENCE (L"Partial form 2", L"")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	Melder_information (Melder_integer (OTMulti_getWinner (me, GET_STRING (L"Partial form 1"), GET_STRING (L"Partial form 2"))));
-END
+END2 }
 
-FORM (OTMulti_generateOptimalForm, L"OTMulti: Generate optimal form", 0)
+FORM (OTMulti_generateOptimalForm, L"OTMulti: Generate optimal form", 0) {
 	SENTENCE (L"Partial form 1", L"")
 	SENTENCE (L"Partial form 2", L"")
 	REAL (L"Evaluation noise", L"2.0")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	wchar_t output [100];
@@ -1276,9 +1402,9 @@ DO
 		output, GET_REAL (L"Evaluation noise"));
 	Melder_information (output);
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (OTMulti_learnOne, L"OTMulti: Learn one", 0)
+FORM (OTMulti_learnOne, L"OTMulti: Learn one", 0) {
 	SENTENCE (L"Partial form 1", L"")
 	SENTENCE (L"Partial form 2", L"")
 	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
@@ -1288,7 +1414,7 @@ FORM (OTMulti_learnOne, L"OTMulti: Learn one", 0)
 		OPTION (L"bidirectionally")
 	POSITIVE (L"Plasticity", L"0.1")
 	REAL (L"Rel. plasticity spreading", L"0.1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (OTMulti);
@@ -1302,83 +1428,83 @@ DO
 			throw;
 		}
 	}
-END
+END2 }
 
-FORM (OTMulti_removeConstraint, L"OTMulti: Remove constraint", 0)
+FORM (OTMulti_removeConstraint, L"OTMulti: Remove constraint", 0) {
 	SENTENCE (L"Constraint name", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (OTMulti);
 		OTMulti_removeConstraint (me, GET_STRING (L"Constraint name"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (OTMulti_resetAllRankings, L"OTMulti: Reset all rankings", 0)
+FORM (OTMulti_resetAllRankings, L"OTMulti: Reset all rankings", 0) {
 	REAL (L"Ranking", L"100.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (OTMulti);
 		OTMulti_reset (me, GET_REAL (L"Ranking"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (OTMulti_setConstraintPlasticity, L"OTMulti: Set constraint plasticity", 0)
+FORM (OTMulti_setConstraintPlasticity, L"OTMulti: Set constraint plasticity", 0) {
 	NATURAL (L"Constraint", L"1")
 	REAL (L"Plasticity", L"1.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (OTMulti);
 		OTMulti_setConstraintPlasticity (me, GET_INTEGER (L"Constraint"), GET_REAL (L"Plasticity"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (OTMulti_setDecisionStrategy, L"OTMulti: Set decision strategy", 0)
+FORM (OTMulti_setDecisionStrategy, L"OTMulti: Set decision strategy", 0) {
 	RADIO_ENUM (L"Decision strategy", kOTGrammar_decisionStrategy, DEFAULT)
-	OK
+	OK2
 iam_ONLY (OTMulti);
 SET_ENUM (L"Decision strategy", kOTGrammar_decisionStrategy, my decisionStrategy);
 DO
 	iam_ONLY (OTMulti);
 	my decisionStrategy = GET_ENUM (kOTGrammar_decisionStrategy, L"Decision strategy");
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (OTMulti_setLeak, L"OTGrammar: Set leak", 0)
+FORM (OTMulti_setLeak, L"OTGrammar: Set leak", 0) {
 	REAL (L"Leak", L"0.0")
-	OK
+	OK2
 iam_ONLY (OTMulti);
 SET_REAL (L"Leak", my leak);
 DO
 	iam_ONLY (OTMulti);
 	my leak = GET_REAL (L"Leak");
 	praat_dataChanged (me);
-END
+END2 }
 
-FORM (OTMulti_setRanking, L"OTMulti: Set ranking", 0)
+FORM (OTMulti_setRanking, L"OTMulti: Set ranking", 0) {
 	NATURAL (L"Constraint", L"1")
 	REAL (L"Ranking", L"100.0")
 	REAL (L"Disharmony", L"100.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (OTMulti);
 		OTMulti_setRanking (me, GET_INTEGER (L"Constraint"), GET_REAL (L"Ranking"), GET_REAL (L"Disharmony"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (OTMulti_to_Distribution, L"OTMulti: Compute output distribution", 0)
+FORM (OTMulti_to_Distribution, L"OTMulti: Compute output distribution", 0) {
 	SENTENCE (L"Partial form 1", L"")
 	SENTENCE (L"Partial form 2", L"")
 	NATURAL (L"Number of trials", L"100000")
 	POSITIVE (L"Evaluation noise", L"2.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (OTMulti);
@@ -1392,9 +1518,9 @@ DO
 			throw;
 		}
 	}
-END
+END2 }
 
-FORM (OTMulti_PairDistribution_learn, L"OTMulti & PairDistribution: Learn", 0)
+FORM (OTMulti_PairDistribution_learn, L"OTMulti & PairDistribution: Learn", 0) {
 	REAL (L"Evaluation noise", L"2.0")
 	OPTIONMENU_ENUM (L"Update rule", kOTGrammar_rerankingStrategy, SYMMETRIC_ALL)
 	OPTIONMENU (L"Direction", 3)
@@ -1407,7 +1533,7 @@ FORM (OTMulti_PairDistribution_learn, L"OTMulti & PairDistribution: Learn", 0)
 	NATURAL (L"Number of plasticities", L"4")
 	REAL (L"Rel. plasticity spreading", L"0.1")
 	INTEGER (L"Store history every", L"0")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	thouart_ONLY (PairDistribution);
@@ -1428,18 +1554,18 @@ DO
 		// trickle down to save history
 	}
 	if (history) praat_new (history, my name);
-END
+END2 }
 
-FORM (OTMulti_Strings_generateOptimalForms, L"OTGrammar: Inputs to outputs", L"OTGrammar: Inputs to outputs...")
+FORM (OTMulti_Strings_generateOptimalForms, L"OTGrammar: Inputs to outputs", L"OTGrammar: Inputs to outputs...") {
 	REAL (L"Evaluation noise", L"2.0")
-	OK
+	OK2
 DO
 	iam_ONLY (OTMulti);
 	thouart_ONLY (Strings);
 	autoStrings him = OTMulti_Strings_generateOptimalForms (me, thee, GET_REAL (L"Evaluation noise"));
 	praat_new (him.transfer(), my name, L"_out");
 	praat_dataChanged (me);
-END
+END2 }
 
 /***** buttons *****/
 
@@ -1456,56 +1582,59 @@ void praat_uvafon_gram_init (void) {
 		praat_addMenuCommand (L"Objects", L"New", L"Create place assimilation distribution", 0, 1, DO_Create_NPA_distribution);
 		praat_addMenuCommand (L"Objects", L"New", L"Create tongue-root grammar...", 0, 1, DO_Create_tongue_root_grammar);
 		praat_addMenuCommand (L"Objects", L"New", L"Create metrics grammar...", 0, 1, DO_Create_metrics_grammar);
+		praat_addMenuCommand (L"Objects", L"New", L"Create multi-level metrics grammar...", 0, 1, DO_Create_multi_level_metrics_grammar);
+	praat_addAction1 (classOTGrammar, 1, L"Save as headerless spreadsheet file...", 0, 0, DO_OTGrammar_writeToHeaderlessSpreadsheetFile);
+	praat_addAction1 (classOTGrammar, 1, L"Write to headerless spreadsheet file...", 0, praat_HIDDEN, DO_OTGrammar_writeToHeaderlessSpreadsheetFile);
 
 	praat_addAction1 (classOTGrammar, 0, L"OTGrammar help", 0, 0, DO_OTGrammar_help);
+	praat_addAction1 (classOTGrammar, 0, L"Draw -", 0, 0, 0);
+		praat_addAction1 (classOTGrammar, 0, L"Draw tableau...", 0, 0, DO_OTGrammar_drawTableau);
+		praat_addAction1 (classOTGrammar, 0, L"Draw tableau (narrowly)...", 0, 0, DO_OTGrammar_drawTableau_narrowly);
 	praat_addAction1 (classOTGrammar, 0, L"View & Edit", 0, praat_ATTRACTIVE, DO_OTGrammar_edit);
 	praat_addAction1 (classOTGrammar, 0, L"Edit", 0, praat_HIDDEN, DO_OTGrammar_edit);
-	praat_addAction1 (classOTGrammar, 0, L"Draw tableau...", 0, 0, DO_OTGrammar_drawTableau);
-	praat_addAction1 (classOTGrammar, 0, L"Draw tableau (narrowly)...", 0, 0, DO_OTGrammar_drawTableau_narrowly);
-	praat_addAction1 (classOTGrammar, 1, L"Save as headerless spreadsheet file...", 0, 0, DO_OTGrammar_writeToHeaderlessSpreadsheetFile);
-	praat_addAction1 (classOTGrammar, 1, L"Write to headerless spreadsheet file...", 0, praat_HIDDEN, DO_OTGrammar_writeToHeaderlessSpreadsheetFile);
 	praat_addAction1 (classOTGrammar, 0, L"Query -", 0, 0, 0);
-	praat_addAction1 (classOTGrammar, 1, L"Get number of constraints", 0, 1, DO_OTGrammar_getNumberOfConstraints);
-	praat_addAction1 (classOTGrammar, 1, L"Get constraint...", 0, 1, DO_OTGrammar_getConstraint);
-	praat_addAction1 (classOTGrammar, 1, L"Get ranking value...", 0, 1, DO_OTGrammar_getRankingValue);
-	praat_addAction1 (classOTGrammar, 1, L"Get disharmony...", 0, 1, DO_OTGrammar_getDisharmony);
-	praat_addAction1 (classOTGrammar, 1, L"Get number of tableaus", 0, 1, DO_OTGrammar_getNumberOfTableaus);
-	praat_addAction1 (classOTGrammar, 1, L"Get input...", 0, 1, DO_OTGrammar_getInput);
-	praat_addAction1 (classOTGrammar, 1, L"Get number of candidates...", 0, 1, DO_OTGrammar_getNumberOfCandidates);
-	praat_addAction1 (classOTGrammar, 1, L"Get candidate...", 0, 1, DO_OTGrammar_getCandidate);
-	praat_addAction1 (classOTGrammar, 1, L"Get number of violations...", 0, 1, DO_OTGrammar_getNumberOfViolations);
-	praat_addAction1 (classOTGrammar, 1, L"-- parse --", 0, 1, 0);
-	praat_addAction1 (classOTGrammar, 1, L"Get winner...", 0, 1, DO_OTGrammar_getWinner);
-	praat_addAction1 (classOTGrammar, 1, L"Compare candidates...", 0, 1, DO_OTGrammar_compareCandidates);
-	praat_addAction1 (classOTGrammar, 1, L"Get number of optimal candidates...", 0, 1, DO_OTGrammar_getNumberOfOptimalCandidates);
-	praat_addAction1 (classOTGrammar, 1, L"Is candidate grammatical...", 0, 1, DO_OTGrammar_isCandidateGrammatical);
-	praat_addAction1 (classOTGrammar, 1, L"Is candidate singly grammatical...", 0, 1, DO_OTGrammar_isCandidateSinglyGrammatical);
-	praat_addAction1 (classOTGrammar, 1, L"Get interpretive parse...", 0, 1, DO_OTGrammar_getInterpretiveParse);
-	praat_addAction1 (classOTGrammar, 1, L"Is partial output grammatical...", 0, 1, DO_OTGrammar_isPartialOutputGrammatical);
-	praat_addAction1 (classOTGrammar, 1, L"Is partial output singly grammatical...", 0, 1, DO_OTGrammar_isPartialOutputSinglyGrammatical);
+		praat_addAction1 (classOTGrammar, 1, L"Get number of constraints", 0, 1, DO_OTGrammar_getNumberOfConstraints);
+		praat_addAction1 (classOTGrammar, 1, L"Get constraint...", 0, 1, DO_OTGrammar_getConstraint);
+		praat_addAction1 (classOTGrammar, 1, L"Get ranking value...", 0, 1, DO_OTGrammar_getRankingValue);
+		praat_addAction1 (classOTGrammar, 1, L"Get disharmony...", 0, 1, DO_OTGrammar_getDisharmony);
+		praat_addAction1 (classOTGrammar, 1, L"Get number of tableaus", 0, 1, DO_OTGrammar_getNumberOfTableaus);
+		praat_addAction1 (classOTGrammar, 1, L"Get input...", 0, 1, DO_OTGrammar_getInput);
+		praat_addAction1 (classOTGrammar, 1, L"Get number of candidates...", 0, 1, DO_OTGrammar_getNumberOfCandidates);
+		praat_addAction1 (classOTGrammar, 1, L"Get candidate...", 0, 1, DO_OTGrammar_getCandidate);
+		praat_addAction1 (classOTGrammar, 1, L"Get number of violations...", 0, 1, DO_OTGrammar_getNumberOfViolations);
+		praat_addAction1 (classOTGrammar, 1, L"-- parse --", 0, 1, 0);
+		praat_addAction1 (classOTGrammar, 1, L"Get winner...", 0, 1, DO_OTGrammar_getWinner);
+		praat_addAction1 (classOTGrammar, 1, L"Compare candidates...", 0, 1, DO_OTGrammar_compareCandidates);
+		praat_addAction1 (classOTGrammar, 1, L"Get number of optimal candidates...", 0, 1, DO_OTGrammar_getNumberOfOptimalCandidates);
+		praat_addAction1 (classOTGrammar, 1, L"Is candidate grammatical...", 0, 1, DO_OTGrammar_isCandidateGrammatical);
+		praat_addAction1 (classOTGrammar, 1, L"Is candidate singly grammatical...", 0, 1, DO_OTGrammar_isCandidateSinglyGrammatical);
+		praat_addAction1 (classOTGrammar, 1, L"Get interpretive parse...", 0, 1, DO_OTGrammar_getInterpretiveParse);
+		praat_addAction1 (classOTGrammar, 1, L"Is partial output grammatical...", 0, 1, DO_OTGrammar_isPartialOutputGrammatical);
+		praat_addAction1 (classOTGrammar, 1, L"Is partial output singly grammatical...", 0, 1, DO_OTGrammar_isPartialOutputSinglyGrammatical);
 	praat_addAction1 (classOTGrammar, 0, L"Generate inputs...", 0, 0, DO_OTGrammar_generateInputs);
 	praat_addAction1 (classOTGrammar, 0, L"Get inputs", 0, 0, DO_OTGrammar_getInputs);
 	praat_addAction1 (classOTGrammar, 0, L"Measure typology", 0, 0, DO_OTGrammar_measureTypology);
 	praat_addAction1 (classOTGrammar, 0, L"Evaluate", 0, 0, 0);
-	praat_addAction1 (classOTGrammar, 0, L"Evaluate...", 0, 0, DO_OTGrammar_evaluate);
-	praat_addAction1 (classOTGrammar, 0, L"Input to output...", 0, 0, DO_OTGrammar_inputToOutput);
-	praat_addAction1 (classOTGrammar, 0, L"Input to outputs...", 0, 0, DO_OTGrammar_inputToOutputs);
-	praat_addAction1 (classOTGrammar, 0, L"To output Distributions...", 0, 0, DO_OTGrammar_to_Distributions);
-	praat_addAction1 (classOTGrammar, 0, L"To PairDistribution...", 0, 0, DO_OTGrammar_to_PairDistribution);
+		praat_addAction1 (classOTGrammar, 0, L"Evaluate...", 0, 0, DO_OTGrammar_evaluate);
+		praat_addAction1 (classOTGrammar, 0, L"Input to output...", 0, 0, DO_OTGrammar_inputToOutput);
+		praat_addAction1 (classOTGrammar, 0, L"Input to outputs...", 0, 0, DO_OTGrammar_inputToOutputs);
+		praat_addAction1 (classOTGrammar, 0, L"To output Distributions...", 0, 0, DO_OTGrammar_to_Distributions);
+		praat_addAction1 (classOTGrammar, 0, L"To PairDistribution...", 0, 0, DO_OTGrammar_to_PairDistribution);
 	praat_addAction1 (classOTGrammar, 0, L"Modify ranking -", 0, 0, 0);
-	praat_addAction1 (classOTGrammar, 0, L"Set ranking...", 0, 1, DO_OTGrammar_setRanking);
-	praat_addAction1 (classOTGrammar, 0, L"Reset all rankings...", 0, 1, DO_OTGrammar_resetAllRankings);
-	praat_addAction1 (classOTGrammar, 0, L"Reset to random total ranking...", 0, 1, DO_OTGrammar_resetToRandomTotalRanking);
-	praat_addAction1 (classOTGrammar, 0, L"Learn one...", 0, 1, DO_OTGrammar_learnOne);
-	praat_addAction1 (classOTGrammar, 0, L"Learn one from partial output...", 0, 1, DO_OTGrammar_learnOneFromPartialOutput);
+		praat_addAction1 (classOTGrammar, 0, L"Set ranking...", 0, 1, DO_OTGrammar_setRanking);
+		praat_addAction1 (classOTGrammar, 0, L"Reset all rankings...", 0, 1, DO_OTGrammar_resetAllRankings);
+		praat_addAction1 (classOTGrammar, 0, L"Reset to random ranking...", 0, 1, DO_OTGrammar_resetToRandomRanking);
+		praat_addAction1 (classOTGrammar, 0, L"Reset to random total ranking...", 0, 1, DO_OTGrammar_resetToRandomTotalRanking);
+		praat_addAction1 (classOTGrammar, 0, L"Learn one...", 0, 1, DO_OTGrammar_learnOne);
+		praat_addAction1 (classOTGrammar, 0, L"Learn one from partial output...", 0, 1, DO_OTGrammar_learnOneFromPartialOutput);
 	praat_addAction1 (classOTGrammar, 0, L"Modify behaviour -", 0, 0, 0);
-	praat_addAction1 (classOTGrammar, 1, L"Set harmony computation method...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_OTGrammar_setDecisionStrategy);
-	praat_addAction1 (classOTGrammar, 1, L"Set decision strategy...", 0, 1, DO_OTGrammar_setDecisionStrategy);
-	praat_addAction1 (classOTGrammar, 1, L"Set leak...", 0, 1, DO_OTGrammar_setLeak);
-	praat_addAction1 (classOTGrammar, 1, L"Set constraint plasticity...", 0, 1, DO_OTGrammar_setConstraintPlasticity);
+		praat_addAction1 (classOTGrammar, 1, L"Set harmony computation method...", 0, praat_DEPTH_1 + praat_HIDDEN, DO_OTGrammar_setDecisionStrategy);
+		praat_addAction1 (classOTGrammar, 1, L"Set decision strategy...", 0, 1, DO_OTGrammar_setDecisionStrategy);
+		praat_addAction1 (classOTGrammar, 1, L"Set leak...", 0, 1, DO_OTGrammar_setLeak);
+		praat_addAction1 (classOTGrammar, 1, L"Set constraint plasticity...", 0, 1, DO_OTGrammar_setConstraintPlasticity);
 	praat_addAction1 (classOTGrammar, 0, L"Modify structure -", 0, 0, 0);
-	praat_addAction1 (classOTGrammar, 0, L"Remove constraint...", 0, 1, DO_OTGrammar_removeConstraint);
-	praat_addAction1 (classOTGrammar, 0, L"Remove harmonically bounded candidates...", 0, 1, DO_OTGrammar_removeHarmonicallyBoundedCandidates);
+		praat_addAction1 (classOTGrammar, 0, L"Remove constraint...", 0, 1, DO_OTGrammar_removeConstraint);
+		praat_addAction1 (classOTGrammar, 0, L"Remove harmonically bounded candidates...", 0, 1, DO_OTGrammar_removeHarmonicallyBoundedCandidates);
 
 	{ void praat_TableOfReal_init (ClassInfo klas); praat_TableOfReal_init (classOTHistory); }
 
@@ -1540,7 +1669,14 @@ void praat_uvafon_gram_init (void) {
 	praat_addAction1 (classOTMulti, 0, L"Modify structure -", 0, 0, 0);
 	praat_addAction1 (classOTMulti, 0, L"Remove constraint...", 0, 1, DO_OTMulti_removeConstraint);
 
+	praat_addAction2 (classOTGrammar, 1, classStrings, 1, L"Query -", 0, 0, 0);
+		praat_addAction2 (classOTGrammar, 1, classStrings, 1, L"Are all partial outputs grammatical?", 0, 1, DO_OTGrammar_Strings_areAllPartialOutputsGrammatical);
+		praat_addAction2 (classOTGrammar, 1, classStrings, 1, L"Are all partial outputs singly grammatical?", 0, 1, DO_OTGrammar_Strings_areAllPartialOutputsSinglyGrammatical);
+	praat_addAction2 (classOTGrammar, 1, classStrings, 1, L"Inputs to outputs...", 0, 0, DO_OTGrammar_Strings_inputsToOutputs);
+	praat_addAction2 (classOTGrammar, 1, classStrings, 1, L"Learn from partial outputs...", 0, 0, DO_OTGrammar_Strings_learnFromPartialOutputs);
+	praat_addAction2 (classOTGrammar, 1, classStrings, 2, L"Learn...", 0, 0, DO_OTGrammar_Stringses_learn);
 	praat_addAction2 (classOTGrammar, 1, classDistributions, 1, L"Learn from partial outputs...", 0, 0, DO_OTGrammar_Distributions_learnFromPartialOutputs);
+	praat_addAction2 (classOTGrammar, 1, classDistributions, 1, L"Learn from partial outputs (rrip)...", 0, 0, DO_OTGrammar_Distributions_learnFromPartialOutputs_rrip);
 	praat_addAction2 (classOTGrammar, 1, classDistributions, 1, L"Get fraction correct...", 0, 0, DO_OTGrammar_Distributions_getFractionCorrect);
 	praat_addAction2 (classOTGrammar, 1, classDistributions, 1, L"List obligatory rankings...", 0, praat_HIDDEN, DO_OTGrammar_Distributions_listObligatoryRankings);
 	praat_addAction2 (classOTGrammar, 1, classPairDistribution, 1, L"Learn...", 0, 0, DO_OTGrammar_PairDistribution_learn);
@@ -1548,9 +1684,6 @@ void praat_uvafon_gram_init (void) {
 	praat_addAction2 (classOTGrammar, 1, classPairDistribution, 1, L"Get fraction correct...", 0, 0, DO_OTGrammar_PairDistribution_getFractionCorrect);
 	praat_addAction2 (classOTGrammar, 1, classPairDistribution, 1, L"Get minimum number correct...", 0, 0, DO_OTGrammar_PairDistribution_getMinimumNumberCorrect);
 	praat_addAction2 (classOTGrammar, 1, classPairDistribution, 1, L"List obligatory rankings", 0, 0, DO_OTGrammar_PairDistribution_listObligatoryRankings);
-	praat_addAction2 (classOTGrammar, 1, classStrings, 1, L"Inputs to outputs...", 0, 0, DO_OTGrammar_inputsToOutputs);
-	praat_addAction2 (classOTGrammar, 1, classStrings, 1, L"Learn from partial outputs...", 0, 0, DO_OTGrammar_learnFromPartialOutputs);
-	praat_addAction2 (classOTGrammar, 1, classStrings, 2, L"Learn...", 0, 0, DO_OTGrammar_learn);
 	praat_addAction2 (classOTMulti, 1, classPairDistribution, 1, L"Learn...", 0, 0, DO_OTMulti_PairDistribution_learn);
 	praat_addAction2 (classOTMulti, 1, classStrings, 1, L"Get outputs...", 0, 0, DO_OTMulti_Strings_generateOptimalForms);
 
diff --git a/kar/longchar.cpp b/kar/longchar.cpp
index 9ec2d43..d419ea9 100644
--- a/kar/longchar.cpp
+++ b/kar/longchar.cpp
@@ -463,8 +463,8 @@ static struct structLongchar_Info Longchar_database [] = {
 
 { 'a', 't', 2, { "/aturn",          444, 0,   462, 520,  444, 0,    444, 0,   0,   0   }, 140, 140, 140, 140, UNICODE_LATIN_SMALL_LETTER_TURNED_A },
 { 'a', 's', 2, { "/ascript",        500, 0,   520, 578,  500, 0,    500, 0,   0,   0   },  65,  65,  65,  65, UNICODE_LATIN_SMALL_LETTER_ALPHA },
-{ 'a', 'b', 2, { "/ascriptturn",    500, 0,   520, 578,  500, 0,    500, 0,   0,   0   }, 129, 129, 129, 129, UNICODE_LATIN_SMALL_LETTER_TURNED_ALPHA }, // Am. pot
 { 'a', 'y', 2, { "/ascriptturn",    500, 0,   520, 578,  500, 0,    500, 0,   0,   0   }, 129, 129, 129, 129, UNICODE_LATIN_SMALL_LETTER_TURNED_ALPHA }, // Am. pot
+{ 'a', 'b', 2, { "/ascriptturn",    500, 0,   520, 578,  500, 0,    500, 0,   0,   0   }, 129, 129, 129, 129, UNICODE_LATIN_SMALL_LETTER_TURNED_ALPHA }, // Am. pot
 { 'b', '^', 2, { "/bhooktop",       475, 0,   510, 580,  475, 0,    475, 0,   0,   0   }, 186, 186, 186, 186, UNICODE_LATIN_SMALL_LETTER_B_WITH_HOOK },
 { '[', 'f', 2, { "/bracketleft",    333, 0,   346, 356,  333, 0,    333, 0,   0,   0   },  91,  91,  91,  91, UNICODE_LEFT_SQUARE_BRACKET }, // second version
 { ']', 'f', 2, { "/bracketright",   333, 0,   346, 356,  333, 0,    333, 0,   0,   0   },  93,  93,  93,  93, UNICODE_RIGHT_SQUARE_BRACKET }, // second version
@@ -475,7 +475,7 @@ static struct structLongchar_Info Longchar_database [] = {
 { 'd', '^', 2, { "/dhooktop",       500, 0,   523, 578,  500, 0,    500, 0,   0,   0   }, 235, 235, 235, 235, UNICODE_LATIN_SMALL_LETTER_D_WITH_HOOK },
 { 'e', '-', 2, { "/erev",           444, 0,   462, 462,  444, 0,    444, 0,   0,   0   }, 251, 130, 130, 251, UNICODE_LATIN_SMALL_LETTER_REVERSED_E }, // 1993 addition
 { 's', 'w', 2, { "/schwa",          444, 0,   462, 462,  444, 0,    444, 0,   0,   0   }, 171, 171, 171, 171, UNICODE_LATIN_SMALL_LETTER_SCHWA },
-{ 's', 'r', 2, { "/schwarighthook", 600, 0, 600.0,600.0, 600, 0,    600, 0,   0,   0   }, 212,   0,   0, 212, UNICODE_LATIN_SMALL_LETTER_SCHWA_WITH_HOOK }, // Am. bird
+{ 's', 'r', 2, { "/schwarighthook", 600, 0,   600, 600,  600, 0,    600, 0,   0,   0   }, 212,   0,   0, 212, UNICODE_LATIN_SMALL_LETTER_SCHWA_WITH_HOOK }, // Am. bird
 { 'e', 'f', 2, { "/epsilonphonetic",444, 0,   441, 471,  444, 0,    444, 0,   0,   0   },  69,  69,  69,  69, UNICODE_LATIN_SMALL_LETTER_OPEN_E },
 { 'e', 'r', 2, { "/epsilonrev",     444, 0,   441, 471,  444, 0,    444, 0,   0,   0   }, 206, 206, 206, 206, UNICODE_LATIN_SMALL_LETTER_REVERSED_OPEN_E },
 { 'k', 'b', 2, { "/kidneybean",     500, 0,   471, 514,  500, 0,    500, 0,   0,   0   }, 185, 207, 207, 185, UNICODE_LATIN_SMALL_LETTER_CLOSED_REVERSED_OPEN_E }, // 1993 addition, 1996 correction
diff --git a/main/palias b/main/palias
new file mode 100644
index 0000000..f17ddb5
--- /dev/null
+++ b/main/palias
@@ -0,0 +1,66 @@
+echo
+echo '***************************************************'
+echo '*                                                 *'
+echo "*   Paul's aliases, version 12 April 2014...      *"
+echo '*                                                 *'
+echo '***************************************************'
+echo
+if ($?DISPLAY == 0) then
+	if ($?REMOTEHOST) then
+		setenv DISPLAY ${REMOTEHOST}:0
+	else
+		setenv DISPLAY :0
+	endif
+endif
+alias g 'cd \!*; set prompt = "#${HOST}:`pwd` > "'
+alias f 'df -m'
+alias clean 'rm */*.[oa] */*/*.[oa]'
+alias e edit
+alias u 'ssh -X paul at uvafon.hum.uva.nl'
+alias u201 'ssh -X pboersma at 145.18.230.201'
+alias lisa 'ssh -X pboersma at lisa.surfsara.nl'
+alias d 'ls -al'
+if ($HOSTTYPE == "intel-mac") then
+	alias d 'ls -alwFG'
+	alias h 'g /Users/pboersma/'
+	alias i 'g /Users/pboersma/www'
+	alias v 'd /var/vm'
+	alias e edit
+	alias m32 '(cd /src/w32; cp makefiles/makefile.defs.mingw32 makefile.defs; make)'
+	alias m64 '(cd /src/w64; cp makefiles/makefile.defs.mingw64 makefile.defs; make)'
+	alias m32c '(cd /src/w32c; cp makefiles/makefile.defs.mingw32c makefile.defs; make)'
+	alias m64c '(cd /src/w64c; cp makefiles/makefile.defs.mingw64c makefile.defs; make)'
+	alias p '/Users/pboersma/Praats/DerivedData/praat64/Build/Products/Configuration64/Praat.app/Contents/MacOS/Praat'
+	alias p32 '/Users/pboersma/Praats/DerivedData/praat32/Build/Products/Configuration1/Praat.app/Contents/MacOS/Praat'
+endif
+if ($HOST == "uvafon") then
+	alias w 'g /u/praat/www'
+	alias h 'g /u/paul'
+	alias i 'g /u/paul/www'
+	alias m '(cd /u/paul/src; cp makefiles/makefile.defs.linux makefile.defs; make)'
+	alias p '~/src/praat'
+	alias d 'ls -alF --color=always'
+endif
+if ($HOSTTYPE == "i686-linux") then
+	alias h 'g /home/pboersma'
+	alias s 'g /media/psf/srcl1232'
+	alias p '/media/psf/srcl1232/praat'
+	alias e 'gedit \!* &'
+	alias m '(cd /media/psf/srcl1232; cp makefiles/makefile.defs.linux.alsa makefile.defs; make)'
+	alias d 'ls -al --color=always'
+endif
+if ($HOST == "paul-ubuntu-12-64") then
+	alias h 'g /home/pboersma'
+	alias s  'g /media/psf/srcl1264'
+	alias sc 'g /media/psf/srcl1264c'
+	alias ss 'g /media/psf/srcl1264s'
+	alias p  '/media/psf/srcl1264/praat'
+	alias pc '/media/psf/srcl1264c/praat'
+	alias e 'gedit \!* &'
+	alias m  '(cd /media/psf/srcl1264;  cp makefiles/makefile.defs.linux.alsa  makefile.defs; make)'
+	alias mc '(cd /media/psf/srcl1264c; cp makefiles/makefile.defs.linuxc.alsa makefile.defs; make)'
+	alias ms '(cd /media/psf/srcl1264s; cp makefiles/makefile.defs.linuxs.alsa makefile.defs; make)'
+	alias d 'ls -al --color=always'
+endif
+h
+alias
diff --git a/main/praat.exe.manifest b/main/praat.exe.manifest
index e2e9df4..df8220a 100644
--- a/main/praat.exe.manifest
+++ b/main/praat.exe.manifest
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
   <assemblyIdentity type="win32" 
                     name="UvA.IFA.Praat" 
                     version="6.0.0.0" 
@@ -17,4 +17,9 @@
       />
     </dependentAssembly>
   </dependency>
+  <asmv3:application>
+    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+      <dpiAware>true</dpiAware>
+    </asmv3:windowsSettings>
+  </asmv3:application>
 </assembly>
diff --git a/main/praat_mac.plist b/main/praat_carbon.plist
similarity index 97%
copy from main/praat_mac.plist
copy to main/praat_carbon.plist
index d5e0498..64474ef 100644
--- a/main/praat_mac.plist
+++ b/main/praat_carbon.plist
@@ -446,6 +446,18 @@
 			<key>NSPersistentStoreTypeKey</key>
 			<string>NSBinaryStoreType</string>
 		</dict>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>edf</string>
+			</array>
+			<key>CFBundleTypeName</key>
+			<string>EDF electro-encephalogram</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>NSPersistentStoreTypeKey</key>
+			<string>NSBinaryStoreType</string>
+		</dict>
 	</array>
 	<key>CFBundleExecutable</key>
 	<string>Praat</string>
@@ -470,7 +482,7 @@
 	<key>LSApplicationCategoryType</key>
 	<string>public.app-category.education</string>
 	<key>LSRequiresCarbon</key>
-	<false/>
+	<true/>
 	<key>NSHumanReadableCopyright</key>
 	<string>Copyright © 1992-PRAAT_YEAR by Paul Boersma and David Weenink</string>
 </dict>
diff --git a/main/praat_mac.plist b/main/praat_cocoa.plist
similarity index 95%
rename from main/praat_mac.plist
rename to main/praat_cocoa.plist
index d5e0498..d37f9fe 100644
--- a/main/praat_mac.plist
+++ b/main/praat_cocoa.plist
@@ -446,11 +446,23 @@
 			<key>NSPersistentStoreTypeKey</key>
 			<string>NSBinaryStoreType</string>
 		</dict>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>edf</string>
+			</array>
+			<key>CFBundleTypeName</key>
+			<string>EDF electro-encephalogram</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>NSPersistentStoreTypeKey</key>
+			<string>NSBinaryStoreType</string>
+		</dict>
 	</array>
 	<key>CFBundleExecutable</key>
 	<string>Praat</string>
 	<key>CFBundleGetInfoString</key>
-	<string>Praat PRAAT_VERSION_STR, © 1992-PRAAT_YEAR by Paul Boersma and David Weenink</string>
+	<string>Praat PRAAT_VERSION_STR, © 1992-PRAAT_YEAR by Paul Boersma & David Weenink</string>
 	<key>CFBundleIconFile</key>
 	<string>Praat</string>
 	<key>CFBundleIdentifier</key>
@@ -471,7 +483,11 @@
 	<string>public.app-category.education</string>
 	<key>LSRequiresCarbon</key>
 	<false/>
+	<key>NSHighResolutionCapable</key>
+	<true/>
 	<key>NSHumanReadableCopyright</key>
-	<string>Copyright © 1992-PRAAT_YEAR by Paul Boersma and David Weenink</string>
+	<string>Copyright © 1992-PRAAT_YEAR by Paul Boersma & David Weenink</string>
+	<key>NSPrincipleClass</key>
+	<string>NSApplication</string>
 </dict>
 </plist>
diff --git a/makefile b/makefile
index 4520398..965db26 100644
--- a/makefile
+++ b/makefile
@@ -62,5 +62,5 @@ clean:
 	$(MAKE) -C FFNet clean
 	$(MAKE) -C artsynth clean
 	$(MAKE) -C contrib/ola clean
-	# $(MAKE) -C main clean
+	$(MAKE) -C main clean
 	$(RM) praat
diff --git a/makefiles/makefile.defs.linux.alsa b/makefiles/makefile.defs.linux.alsa
index c43b651..60447f5 100644
--- a/makefiles/makefile.defs.linux.alsa
+++ b/makefiles/makefile.defs.linux.alsa
@@ -1,13 +1,13 @@
 # File: makefile.defs.linux.alsa
 
 # System: Linux
-# Paul Boersma, 26 October 2013
+# Paul Boersma, 12 June 2014
 
 CC = gcc -std=gnu99
 
-CXX = g++
+CXX = g++ -std=c++0x
 
-CFLAGS = -DUNIX -Dlinux -DALSA `pkg-config --cflags gtk+-2.0` -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O1 -g1
+CFLAGS = -DUNIX -Dlinux -DALSA `pkg-config --cflags gtk+-2.0` -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O1 -g1 -pthread
 
 CXXFLAGS = $(CFLAGS) -Wshadow
 
diff --git a/makefiles/makefile.defs.linux.silent b/makefiles/makefile.defs.linux.silent
index c3baa5c..74d7648 100644
--- a/makefiles/makefile.defs.linux.silent
+++ b/makefiles/makefile.defs.linux.silent
@@ -1,13 +1,13 @@
 # File: makefile.defs.linux.silent
 
 # System: Linux without sound
-# Paul Boersma, 26 October 2013
+# Paul Boersma, 28 May 2014
 
 CC = gcc -std=gnu99
 
-CXX = g++
+CXX = g++ -std=c++0x
 
-CFLAGS = -DUNIX -Dlinux `pkg-config --cflags gtk+-2.0` -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O1 -g1
+CFLAGS = -DUNIX -Dlinux `pkg-config --cflags gtk+-2.0` -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O1 -g1 -pthread
 
 CXXFLAGS = $(CFLAGS) -Wshadow
 
diff --git a/makefiles/makefile.defs.linuxc.alsa b/makefiles/makefile.defs.linuxc.alsa
new file mode 100644
index 0000000..655507f
--- /dev/null
+++ b/makefiles/makefile.defs.linuxc.alsa
@@ -0,0 +1,23 @@
+# File: makefile.defs.linuxc.alsa
+
+# System: Linux
+# Paul Boersma, 28 May 2014
+
+CC = gcc -std=gnu99
+
+CXX = g++ -std=c++0x
+
+CFLAGS = -DCONSOLE_APPLICATION -DUNIX -Dlinux -DALSA -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O1 -g1 -pthread
+
+CXXFLAGS = $(CFLAGS) -Wshadow
+
+LINK = g++
+
+EXECUTABLE = praat
+
+LIBS = -lm -lasound -lpthread
+
+AR = ar
+RANLIB = ls
+ICON =
+MAIN_ICON =
diff --git a/makefiles/makefile.defs.linuxs.alsa b/makefiles/makefile.defs.linuxs.alsa
new file mode 100644
index 0000000..21af920
--- /dev/null
+++ b/makefiles/makefile.defs.linuxs.alsa
@@ -0,0 +1,23 @@
+# File: makefile.defs.linuxc.alsa
+
+# System: Linux
+# Paul Boersma, 28 May 2014
+
+CC = gcc -std=gnu99
+
+CXX = g++ -std=c++0x
+
+CFLAGS = -DNO_GRAPHICS -DUNIX -Dlinux -DALSA -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O1 -g1 -pthread
+
+CXXFLAGS = $(CFLAGS) -Wshadow
+
+LINK = g++
+
+EXECUTABLE = praat
+
+LIBS = -lm -lasound -lpthread
+
+AR = ar
+RANLIB = ls
+ICON =
+MAIN_ICON =
diff --git a/makefiles/makefile.defs.mingw32 b/makefiles/makefile.defs.mingw32
index be6333e..863d6d4 100644
--- a/makefiles/makefile.defs.mingw32
+++ b/makefiles/makefile.defs.mingw32
@@ -1,13 +1,13 @@
 # File: makefile.defs.mingw32
 
 # System: MinGW
-# Paul Boersma, 8 August 2012
+# Paul Boersma, 12 June 2014
 
 CC = /mingw32/bin/gcc -std=gnu99 -isystem /mingw32/include
 
-CXX = /mingw32/bin/g++ -isystem /mingw32/include/c++/4.7.0 -isystem /mingw32/include -Wshadow
+CXX = /mingw32/bin/g++ -std=c++0x -isystem /mingw32/include/c++/4.7.0 -isystem /mingw32/include -Wshadow
 
-CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -Dmain=wingwmain -O1
+CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -Dmain=wingwmain -O1 -pthread
 
 CXXFLAGS = $(CFLAGS)
 
diff --git a/makefiles/makefile.defs.mingw32-490 b/makefiles/makefile.defs.mingw32-490
new file mode 100644
index 0000000..e4aba07
--- /dev/null
+++ b/makefiles/makefile.defs.mingw32-490
@@ -0,0 +1,24 @@
+# File: makefile.defs.mingw32-490
+
+# System: MinGW
+# Paul Boersma, 9 June 2014
+
+CC = /mingw32-490/bin/x86_64-w64-mingw32-gcc -std=gnu99 -isystem /mingw32-490/mingw/include
+
+CXX = /mingw32-490/bin/x86_64-w64-mingw32-g++ -std=c++0x -isystem /mingw32-490/mingw/include/c++/4.9.0 -isystem /mingw32-490/mingw/include -Wshadow -m64 -flto
+
+CFLAGS = -DWINVER=0x0600 -D_WIN32_WINNT=0x0600 -D_WIN32_IE=0x0700 -DUNICODE -Dmain=wingwmain -O1
+
+CXXFLAGS = $(CFLAGS) -pthread
+
+LINK = /mingw32-490/mingw/bin/g++
+
+EXECUTABLE = Praat.exe
+
+LIBS = -L/mingw32-490/x86_64-w64-mingw32/lib -lwinmm -lwsock32 -lcomctl32 -lole32 -lgdi32 -lgdiplus -lcomdlg32 -static-libgcc -static-libstdc++ -mwindows
+
+AR = /mingw32-490/mingw/bin/ar
+RANLIB = /mingw32-490/mingw/bin/ranlib
+WINDRES = /mingw32-490/bin/x86_64-w64-mingw32-windres
+ICON = praat_win.o
+MAIN_ICON = main/praat_win.o
diff --git a/makefiles/makefile.defs.mingw32c b/makefiles/makefile.defs.mingw32c
index f5f8155..ade9b61 100644
--- a/makefiles/makefile.defs.mingw32c
+++ b/makefiles/makefile.defs.mingw32c
@@ -1,13 +1,13 @@
 # File: makefile.defs.mingw32c
 
 # System: MinGW
-# Paul Boersma, 8 August 2012
+# Paul Boersma, 12 June 2014
 
 CC = /mingw32/bin/gcc -std=gnu99 -isystem /mingw32/include
 
-CXX = /mingw32/bin/g++ -isystem /mingw32/include/c++/4.7.0 -isystem /mingw32/include -Wshadow
+CXX = /mingw32/bin/g++ -std=c++0x -isystem /mingw32/include/c++/4.7.0 -isystem /mingw32/include -Wshadow
 
-CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -DCONSOLE_APPLICATION -O1
+CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -DCONSOLE_APPLICATION -O1 -pthread
 
 CXXFLAGS = $(CFLAGS)
 
diff --git a/makefiles/makefile.defs.mingw64 b/makefiles/makefile.defs.mingw64
index dc5c6b6..8a78ce8 100644
--- a/makefiles/makefile.defs.mingw64
+++ b/makefiles/makefile.defs.mingw64
@@ -1,13 +1,13 @@
 # File: makefile.defs.mingw64
 
 # System: MinGW
-# Paul Boersma, 8 August 2012
+# Paul Boersma, 12 June 2014
 
 CC = /mingw64/bin/gcc -std=gnu99 -isystem /mingw64/include
 
-CXX = /mingw64/bin/g++ -isystem /mingw64/include/c++/4.7.0 -isystem /mingw64/include -Wshadow -m64
+CXX = /mingw64/bin/g++ -std=c++0x -isystem /mingw64/include/c++/4.7.0 -isystem /mingw64/include -Wshadow -m64
 
-CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -Dmain=wingwmain -O1
+CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -Dmain=wingwmain -O1 -pthread
 
 CXXFLAGS = $(CFLAGS)
 
diff --git a/makefiles/makefile.defs.mingw64-490 b/makefiles/makefile.defs.mingw64-490
new file mode 100644
index 0000000..ba6ff5e
--- /dev/null
+++ b/makefiles/makefile.defs.mingw64-490
@@ -0,0 +1,24 @@
+# File: makefile.defs.mingw64-490
+
+# System: MinGW
+# Paul Boersma, 9 June 2014
+
+CC = /mingw64-490/bin/x86_64-w64-mingw32-gcc -std=gnu99 -isystem /mingw64-490/mingw/include
+
+CXX = /mingw64-490/bin/x86_64-w64-mingw32-g++ -std=c++0x -isystem /mingw64-490/mingw/include/c++/4.9.0 -isystem /mingw64-490/mingw/include -Wshadow -m64
+
+CFLAGS = -DWINVER=0x0600 -D_WIN32_WINNT=0x0600 -D_WIN32_IE=0x0700 -DUNICODE -Dmain=wingwmain -O1 -flto
+
+CXXFLAGS = $(CFLAGS) -pthread
+
+LINK = /mingw64-490/mingw/bin/g++
+
+EXECUTABLE = Praat.exe
+
+LIBS = -L/mingw64-490/x86_64-w64-mingw32/lib -lwinmm -lwsock32 -lcomctl32 -lole32 -lgdi32 /mingw64/lib/GdiPlus.dll -lcomdlg32 -static-libgcc -static-libstdc++ -mwindows
+
+AR = /mingw64-490/mingw/bin/ar
+RANLIB = /mingw64-490/mingw/bin/ranlib
+WINDRES = /mingw64-490/bin/x86_64-w64-mingw32-windres
+ICON = praat_win.o
+MAIN_ICON = main/praat_win.o
diff --git a/makefiles/makefile.defs.mingw64c b/makefiles/makefile.defs.mingw64c
index 3692878..b2ad9b1 100644
--- a/makefiles/makefile.defs.mingw64c
+++ b/makefiles/makefile.defs.mingw64c
@@ -1,13 +1,13 @@
 # File: makefile.defs.mingw64c
 
 # System: MinGW
-# Paul Boersma, 8 August 2012
+# Paul Boersma, 12 June 2014
 
 CC = /mingw64/bin/gcc -std=gnu99 -isystem /mingw64/include
 
-CXX = /mingw64/bin/g++ -isystem /mingw64/include/c++/4.7.0 -isystem /mingw64/include -Wshadow -m64
+CXX = /mingw64/bin/g++ -std=c++0x -isystem /mingw64/include/c++/4.7.0 -isystem /mingw64/include -Wshadow -m64
 
-CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -DCONSOLE_APPLICATION -O1
+CFLAGS = -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 -DUNICODE -DCONSOLE_APPLICATION -O1 -pthread
 
 CXXFLAGS = $(CFLAGS)
 
diff --git a/num/NUM.cpp b/num/NUM.cpp
index a82911a..ca2fe48 100644
--- a/num/NUM.cpp
+++ b/num/NUM.cpp
@@ -1,6 +1,6 @@
 /* NUM.cpp
  *
- * Copyright (C) 1992-2008,2011,2012 Paul Boersma
+ * Copyright (C) 1992-2008,2011,2012,2014 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
@@ -60,6 +60,7 @@ void NUMscale (double *x, double xminfrom, double xmaxfrom, double xminto, doubl
 
 void NUMinit (void) {
 	gsl_set_error_handler_off ();
+	NUMrandom_init ();
 }
 
 void NUMfbtoa (double formant, double bandwidth, double dt, double *a1, double *a2) {
diff --git a/num/NUM.h b/num/NUM.h
index f95d691..6cebf8f 100644
--- a/num/NUM.h
+++ b/num/NUM.h
@@ -50,7 +50,7 @@
 #include "abcio.h"
 #define NUMlog2(x)  (log (x) * NUMlog2e)
 
-void NUMinit (void);
+void NUMinit ();
 
 double NUMpow (double base, double exponent);   /* Zero for non-positive base. */
 void NUMshift (double *x, double xfrom, double xto);
@@ -218,7 +218,7 @@ bool NUMmatrix_equal (long elementSize, void *m1, void *m2, long row1, long row2
 
 long NUM_getTotalNumberOfArrays (void);   /* For debugging. */
 
-/********** Special functions (NUM.c) **********/
+/********** Special functions (NUM.cpp) **********/
 
 double NUMlnGamma (double x);
 double NUMbeta (double z, double w);
@@ -245,7 +245,7 @@ double NUMbinomialQ (double p, double k, double n);
 double NUMinvBinomialP (double p, double k, double n);
 double NUMinvBinomialQ (double p, double k, double n);
 
-/********** Auditory modelling (NUMear.c) **********/
+/********** Auditory modelling (NUMear.cpp) **********/
 
 double NUMhertzToBark (double hertz);
 double NUMbarkToHertz (double bark);
@@ -260,7 +260,7 @@ double NUMerb (double f);
 double NUMhertzToErb (double hertz);
 double NUMerbToHertz (double erb);
 
-/********** Sorting (NUMsort.c) **********/
+/********** Sorting (NUMsort.cpp) **********/
 
 void NUMsort_d (long n, double ra []);   /* Heap sort. */
 void NUMsort_i (long n, int ra []);
@@ -277,7 +277,7 @@ double NUMquantile (long n, double a [], double factor);
 	If your array has not been sorted, first sort it with NUMsort (n, a).
 */
 
-/********** Interpolation and optimization (NUM.c) **********/
+/********** Interpolation and optimization (NUM.cpp) **********/
 
 // Special values for interpolationDepth:
 #define NUM_VALUE_INTERPOLATE_NEAREST  0
@@ -313,7 +313,7 @@ void NUM_viterbi_multi (
 	void (*putResult) (long iframe, long place, int itrack, void *closure),
 	void *closure);
 
-/********** Metrics (NUM.c) **********/
+/********** Metrics (NUM.cpp) **********/
 
 int NUMrotationsPointInPolygon
 	(double x0, double y0, long n, double x [], double y []);
@@ -326,22 +326,20 @@ int NUMrotationsPointInPolygon
 	If the point is on the polygon, the result is unpredictable.
 */
 
-/********** Random numbers (NUMrandom.c) **********/
+/********** Random numbers (NUMrandom.cpp) **********/
 
-void NUMrandomRestart (unsigned long seed);
-/*
-	Not needed for starting the random generator for the first time;
-	that will be done on the basis of the current time.
-*/
+void NUMrandom_init ();   // automatically called by NUMinit ();
 
-double NUMrandomFraction (void);
+double NUMrandomFraction ();
+double NUMrandomFraction_mt (int threadNumber);
 
 double NUMrandomUniform (double lowest, double highest);
 
 long NUMrandomInteger (long lowest, long highest);
 
 double NUMrandomGauss (double mean, double standardDeviation);
-	
+double NUMrandomGauss_mt (int threadNumber, double mean, double standardDeviation);
+
 double NUMrandomPoisson (double mean);
 
 void NUMfbtoa (double formant, double bandwidth, double dt, double *a1, double *a2);
diff --git a/num/NUMrandom.cpp b/num/NUMrandom.cpp
index 7974afd..7f57dba 100644
--- a/num/NUMrandom.cpp
+++ b/num/NUMrandom.cpp
@@ -1,6 +1,6 @@
 /* NUMrandom.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -23,198 +23,252 @@
  * pb 2011/03/29 C++
  */
 
-/*
-	Based on:
-	Donald E. Knuth (1998): The art of computer programming. Third edition. Vol. 2: seminumerical algorithms.
-		Boston: Addison-Wesley.
+/* 
+   A C-program for MT19937-64 (2014/2/23 version).
+   Coded by Takuji Nishimura and Makoto Matsumoto.
+
+   This is a 64-bit version of Mersenne Twister pseudorandom number
+   generator.
+
+   Before using, initialize the state by using init_genrand64(seed)  
+   or init_by_array64(init_key, key_length).
+
+   Copyright (C) 2004, 2014, Makoto Matsumoto and Takuji Nishimura,
+   All rights reserved.                          
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+     1. Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+
+     2. Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+
+     3. The names of its contributors may not be used to endorse or promote 
+        products derived from this software without specific prior written 
+        permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   References:
+   T. Nishimura, ``Tables of 64-bit Mersenne Twisters''
+     ACM Transactions on Modeling and 
+     Computer Simulation 10. (2000) 348--357.
+   M. Matsumoto and T. Nishimura,
+     ``Mersenne Twister: a 623-dimensionally equidistributed
+       uniform pseudorandom number generator''
+     ACM Transactions on Modeling and 
+     Computer Simulation 8. (Jan. 1998) 3--30.
+
+   Any feedback is very welcome.
+   http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+   email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces)
 */
 
+#include <inttypes.h>
+#if defined (__MINGW32__) || defined (linux)
+	#define UINT64_C(n)  n ## ULL
+#endif
+#include <unistd.h>
+#include "melder.h"
 #include "NUM.h"
-//#include "melder.h"
-#include <time.h>
+#define my me ->
 
-/*
-	The random sequence is based on lagged Fibonacci on real numbers in [0; 1) (Knuth:27,29):
+#define NN  312
+#define MM  156
+#define MATRIX_A  UINT64_C (0xB5026F5AA96619E9)
+#define UM  UINT64_C (0xFFFFFFFF80000000) /* Most significant 33 bits */
+#define LM  UINT64_C (0x7FFFFFFF) /* Least significant 31 bits */
 
-		X [n] = (X [n - 100] + X [n - 37]) mod 1.0
+class NUMrandom_State { public:
 
-	The routine NUMrandomRestart relies on a 52-bit mantissa, so we will get 10^15 different random numbers.
-	The period is infinitely larger than that.
+	/** The state vector.
+	 */
+	uint64_t array [NN];
 
-	To compute x mod 1, Knuth uses the formula
+	/** The pointer into the state vector.
+		Equals NN + 1 iff the array has not been initialized.
+	 */
+	int index;
+	NUMrandom_State () : index (NN + 1) {}
+		// this initialization will lead to an immediate crash
+		// when NUMrandomFraction() is called without NUMrandom_init() having been called before;
+		// without this initialization, it would be detected only after 312 calls to NUMrandomFraction()
+
+	int secondAvailable;
+	double y;
+
+	/**
+		Initialize the whole array with one seed.
+		This can be used for testing whether our implementation is correct (i.e. predicts the correct published sequence)
+		and perhaps for generating reproducible sequences.
+	 */
+	void init_genrand64 (uint64_t seed) {
+		array [0] = seed;
+		for (index = 1; index < NN; index ++) {
+			array [index] =
+				(UINT64_C (6364136223846793005) * (array [index - 1] ^ (array [index - 1] >> 62))
+				+ (uint64_t) index);
+		}
+	}
 
-		x - (int) x
+	/* initialize by an array with array-length */
+	/* init_key is the array for initializing keys */
+	/* key_length is its length */
+	void init_by_array64 (uint64_t init_key[], unsigned int key_length);
+
+} states [17];
+
+/* initialize the array with a number of seeds */
+void NUMrandom_State :: init_by_array64 (uint64_t init_key [], unsigned int key_length)
+{
+    init_genrand64 (UINT64_C (19650218));   // warm it up
+
+    unsigned int i = 1, j = 0;
+    unsigned int k = ( NN > key_length ? NN : key_length );
+    for (; k; k --) {
+        array [i] = (array [i] ^ ((array [i - 1] ^ (array [i - 1] >> 62)) * UINT64_C (3935559000370003845)))
+          + init_key [j] + (uint64_t) j;   /* non linear */
+        i ++, j ++;
+        if (i >= NN) { array [0] = array [NN - 1]; i = 1; }
+        if (j >= key_length) j = 0;
+    }
+    for (k = NN - 1; k; k --) {
+        array [i] = (array [i] ^ ((array [i - 1] ^ (array [i - 1] >> 62)) * UINT64_C (2862933555777941757)))
+          - (uint64_t) i;   /* non linear */
+        i ++;
+        if (i >= NN) { array [0] = array [NN - 1]; i = 1; }
+    }
+
+    array [0] = UINT64_C (1) << 63; /* MSB is 1; assuring non-zero initial array */
+}
 
-	However, we use the following, which is faster:
+static bool theInited = false;
+void NUMrandom_init () {
+	for (int threadNumber = 0; threadNumber <= 16; threadNumber ++) {
+		const int numberOfKeys = 5;
+		uint64_t keys [numberOfKeys];
+		keys [0] = round (1e6 * Melder_clock ());   // unique between boots of the same computer
+		keys [1] = UINT64_C (7320321686725470078) + (uint64_t) threadNumber;   // unique between threads in the same process
+		switch (threadNumber) {
+			case  0: keys [2] = UINT64_C  (4492812493098689432), keys [3] = UINT64_C  (8902321878452586268); break;
+			case  1: keys [2] = UINT64_C  (1875086582568685862), keys [3] = UINT64_C (12243257483652989599); break;
+			case  2: keys [2] = UINT64_C  (9040925727554857487), keys [3] = UINT64_C  (8037578605604605534); break;
+			case  3: keys [2] = UINT64_C (11168476768576857685), keys [3] = UINT64_C  (7862359785763816517); break;
+			case  4: keys [2] = UINT64_C  (3878901748368466876), keys [3] = UINT64_C  (3563078257726526076); break;
+			case  5: keys [2] = UINT64_C  (2185735817578415800), keys [3] = UINT64_C   (198502654671560756); break;
+			case  6: keys [2] = UINT64_C (12248047509814562486), keys [3] = UINT64_C  (9836250167165762757); break;
+			case  7: keys [2] = UINT64_C    (28362088588870143), keys [3] = UINT64_C  (8756376201767075602); break;
+			case  8: keys [2] = UINT64_C  (5758130586486546775), keys [3] = UINT64_C  (4213784157469743413); break;
+			case  9: keys [2] = UINT64_C  (8508416536565170756), keys [3] = UINT64_C  (2856175717654375656); break;
+			case 10: keys [2] = UINT64_C  (2802356275260644756), keys [3] = UINT64_C  (2309872134087235167); break;
+			case 11: keys [2] = UINT64_C   (230875784065064545), keys [3] = UINT64_C  (1209802371478023476); break;
+			case 12: keys [2] = UINT64_C  (6520185868568714577), keys [3] = UINT64_C  (2173615001556504015); break;
+			case 13: keys [2] = UINT64_C  (9082605608605765650), keys [3] = UINT64_C  (1204167447560475647); break;
+			case 14: keys [2] = UINT64_C  (1238716515545475765), keys [3] = UINT64_C  (8435674023875847388); break;
+			case 15: keys [2] = UINT64_C  (6127715675014756456), keys [3] = UINT64_C  (2435788450287508457); break;
+			case 16: keys [2] = UINT64_C  (1081237546238975884), keys [3] = UINT64_C  (2939783238574293882); break;
+			default: Melder_fatal ("Thread number too high.");
+		}
+		keys [4] = getpid ();   // unique between processes that run simultaneously on the same computer
+		/*
+			TODO: need to add a seed that is unique between computers. gethostid?
+		*/
+		states [threadNumber]. init_by_array64 (keys, numberOfKeys);
+	}
+	theInited = true;
+}
 
-		if (x > 1.0) x -= 1.0;
-*/
-#define LONG_LAG  100
-#define SHORT_LAG  37
-#define LAG_DIFF  (LONG_LAG - SHORT_LAG)
-/*
-	Each of the 4 billion possible seeds comes with its own stream of random numbers.
-	These streams must be guaranteed not to overlap within the first 2^70 draws (Knuth:187).
-	That's 30 thousand years if one random number takes 1 nanosecond.
-*/
-#define STREAM_SEPARATION  70
-/*
-	The following value has to be at least 100, i.e. equal to the long lag.
-	If it is 100, then the entire random sequence will be output by NUMrandomFraction.
-	In that case, however, the resulting numbers will not be completely random (Knuth:72), i.e.:
-
-		X [n] - X [n - 37] = X [n - 63] - X [n - 163]   (modulo 1.0)
-
-	The following Praat script will show this:
-
-		echo Birthday spacings test:
-		for i to 200
-		   a'i' = randomUniform (0,1)
-		endfor
-		for i from 164 to 200
-		   xn = a'i'
-		   j = i - 37
-		   xn37 = a'j'
-		   j = i - 63
-		   xn63 = a'j'
-		   j = i - 163
-		   xn163 = a'j'
-		   diff1 = xn - xn37
-		   diff2 = xn63 - xn163
-		   printline 'diff1' 'diff2'
-		endfor
-
-	We see that diff1 and diff2 are equal modulo 1.
-	This is why Knuth advises (p.35,72,188) to throw away 909 numbers after generating 100,
-	i.e. the following value will be 1009.
-*/
-#define QUALITY  1009
+/* Throughout the years, several versions for "zero or magic" have been proposed. Choose the fastest. */
 
-/*
-	A test for the integrity of NUMrandomRestart and NUMrandomFraction (Knuth:188,603):
+#define ZERO_OR_MAGIC_VERSION  3
 
-		NUMrandomRestart (310952);
-		for (i = 1; i <= 1009 * 2009 - 100 + 1; i ++)
-			x = NUMrandomFraction ();
-		Melder_information (Melder_double (x));
+#if ZERO_OR_MAGIC_VERSION == 1  // M&N 1999
+	#define ZERO_OR_MAGIC  ( (x & UINT64_C (1)) ? MATRIX_A : UINT64_C (0) )
+#elif ZERO_OR_MAGIC_VERSION == 2
+	#define ZERO_OR_MAGIC  ( (x & UINT64_C (1)) * MATRIX_A )
+#else   // M&N 2014
+	const uint64_t mag01 [2] = { UINT64_C (0), MATRIX_A };
+	#define ZERO_OR_MAGIC  mag01 [(int) (x & UINT64_C (1))]
+#endif
 
-	With QUALITY set to 100, this should give 0.27452626307394156768 (Knuth:603).
-	With QUALITY set to 1009, it should give 0.73203216867254750078.
-*/
+double NUMrandomFraction () {
+	NUMrandom_State *me = & states [0];
+    uint64_t x;
 
-static double randomArray [LONG_LAG];
-static int randomInited = 0;
-static long randomArrayPointer1, randomArrayPointer2, iquality;
+    if (my index >= NN) { /* generate NN words at a time */
 
-void NUMrandomRestart (unsigned long seed) {
-	/*
-		Based on Knuth, p. 187,602.
-
-		Knuth had:
-			int s = seed;
-		This is incorrect (even if an int is 32 bit), since a negative value causes a loop in the test
-			if (s != 0)
-				s >>= 1;
-		because the >> operator on negative integers adds a sign bit to the left.
-	 */
-	long t, j;
-	double u [2 * LONG_LAG - 1], ul [2 * LONG_LAG - 1];
-	double ulp = 1.0 / (1L << 30) / (1L << 22), ss;
-	ss = 2.0 * ulp * (seed + 2);   /* QUESTION: does this work if seed exceeds 2^32 - 3? See Knuth p. 187. */
-	for (j = 0; j < LONG_LAG; j ++) {
-		u [j] = ss;
-		ul [j] = 0.0;
-		ss += ss;
-		if (ss >= 1.0) ss -= 1.0 - 2 * ulp;
-	}
-	for (; j < 2 * LONG_LAG - 1; j ++)
-		u [j] = ul [j] = 0.0;
-	u [1] += ulp;
-	ul [1] = ulp;
-	t = STREAM_SEPARATION - 1;
-	while (t > 0) {
-		for (j = LONG_LAG - 1; j > 0; j --) {
-			ul [j + j] = ul [j];
-			u [j + j] = u [j];
-		}
-		for (j = 2 * LONG_LAG - 2; j > LAG_DIFF; j -= 2) {
-			ul [2 * LONG_LAG - 1 - j] = 0.0;
-			u [2 * LONG_LAG - 1 - j] = u [j] - ul [j];
-		}
-		for (j = 2 * LONG_LAG - 2; j >= LONG_LAG; j --) if (ul [j] != 0) {
-			ul [j - LAG_DIFF] = ulp - ul [j - LAG_DIFF];
-			u [j - LAG_DIFF] += u [j];
-			if (u [j - LAG_DIFF] >= 1.0) u [j - LAG_DIFF] -= 1.0;
-			ul [j - LONG_LAG] = ulp - ul [j - LONG_LAG];
-			u [j - LONG_LAG] += u [j];
-			if (u [j - LONG_LAG] >= 1.0) u [j - LONG_LAG] -= 1.0;
-		}
-		if ((seed & 1) != 0) {
-			for (j = LONG_LAG; j > 0; j --) {
-				ul [j] = ul [j - 1];
-				u [j] = u [j - 1];
-			}
-			ul [0] = ul [LONG_LAG];
-			u [0] = u [LONG_LAG];
-			if (ul [LONG_LAG] != 0) {
-				ul [SHORT_LAG] = ulp - ul [SHORT_LAG];
-				u [SHORT_LAG] += u [LONG_LAG];
-				if (u [SHORT_LAG] >= 1.0) u [SHORT_LAG] -= 1.0;
-			}
-		}
-		if (seed != 0) {
-			seed >>= 1;
-		} else {
-			t --;
-		}
-	}
-	for (j = 0; j < SHORT_LAG; j ++)
-		randomArray [j + LAG_DIFF] = u [j];
-	for (; j < LONG_LAG; j ++)
-		randomArray [j - SHORT_LAG] = u [j];
-	randomArrayPointer1 = 0;
-	randomArrayPointer2 = LAG_DIFF;
-	iquality = 0;
-	randomInited = 1;
+		Melder_assert (theInited);   // if NUMrandom_init() hasn't been called, we'll detect that here, probably in the first call
+
+		int i;
+        for (i = 0; i < NN - MM; i ++) {
+            x = (my array [i] & UM) | (my array [i + 1] & LM);
+            my array [i] = my array [i + MM] ^ (x >> 1) ^ ZERO_OR_MAGIC;
+        }
+        for (; i < NN - 1; i ++) {
+            x = (my array [i] & UM) | (my array [i + 1] & LM);
+            my array [i] = my array [i + (MM - NN)] ^ (x >> 1) ^ ZERO_OR_MAGIC;
+        }
+        x = (my array [NN - 1] & UM) | (my array [0] & LM);
+        my array [NN - 1] = my array [MM - 1] ^ (x >> 1) ^ ZERO_OR_MAGIC;
+
+        my index = 0;
+    }
+
+    x = my array [my index ++];
+
+    x ^= (x >> 29) & UINT64_C (0x5555555555555555);
+    x ^= (x << 17) & UINT64_C (0x71D67FFFEDA60000);
+    x ^= (x << 37) & UINT64_C (0xFFF7EEE000000000);
+    x ^= (x >> 43);
+
+	return (x >> 11) * (1.0/9007199254740992.0);
 }
 
-double NUMrandomFraction (void) {
-	/*
-		Knuth uses a long random array of length QUALITY to copy values from randomArray.
-		We save 8 kilobytes by using randomArray as a cyclic array (10% speed loss).
-	*/
-	long p1, p2;
-	double newValue;
-	if (! randomInited) NUMrandomRestart (time (NULL));
-	p1 = randomArrayPointer1, p2 = randomArrayPointer2;
-	if (p1 >= LONG_LAG) p1 = 0;
-	if (p2 >= LONG_LAG) p2 = 0;
-	newValue = randomArray [p1] + randomArray [p2];
-	if (newValue >= 1.0) newValue -= 1.0;
-	randomArray [p1] = newValue;
-	p1 ++;
-	p2 ++;
-	if (++ iquality == LONG_LAG) {
-		for (; iquality < QUALITY; iquality ++) {
-			double newValue2;
-			/*
-				Possible future minor speed improvement:
-					the cyclic array is walked down instead of up.
-					The following tests will then be for 0.
-			*/
-			if (p1 >= LONG_LAG) p1 = 0;
-			if (p2 >= LONG_LAG) p2 = 0;
-			newValue2 = randomArray [p1] + randomArray [p2];
-			if (newValue2 >= 1.0) newValue2 -= 1.0;
-			randomArray [p1] = newValue2;
-			p1 ++;
-			p2 ++;
-		}
-		iquality = 0;
-	}
-	randomArrayPointer1 = p1;
-	randomArrayPointer2 = p2;
-	return newValue;
+double NUMrandomFraction_mt (int threadNumber) {
+	NUMrandom_State *me = & states [threadNumber];
+    uint64_t x;
+
+    if (my index >= NN) { /* generate NN words at a time */
+
+		Melder_assert (theInited);
+
+		int i;
+        for (i = 0; i < NN - MM; i ++) {
+            x = (my array [i] & UM) | (my array [i + 1] & LM);
+            my array [i] = my array [i + MM] ^ (x >> 1) ^ ZERO_OR_MAGIC;
+        }
+        for (; i < NN - 1; i ++) {
+            x = (my array [i] & UM) | (my array [i + 1] & LM);
+            my array [i] = my array [i + (MM - NN)] ^ (x >> 1) ^ ZERO_OR_MAGIC;
+        }
+        x = (my array [NN - 1] & UM) | (my array [0] & LM);
+        my array [NN - 1] = my array [MM - 1] ^ (x >> 1) ^ ZERO_OR_MAGIC;
+
+        my index = 0;
+    }
+
+    x = my array [my index ++];
+
+    x ^= (x >> 29) & UINT64_C (0x5555555555555555);
+    x ^= (x << 17) & UINT64_C (0x71D67FFFEDA60000);
+    x ^= (x << 37) & UINT64_C (0xFFF7EEE000000000);
+    x ^= (x >> 43);
+
+	return (x >> 11) * (1.0/9007199254740992.0);
 }
 
 double NUMrandomUniform (double lowest, double highest) {
@@ -222,34 +276,59 @@ double NUMrandomUniform (double lowest, double highest) {
 }
 
 long NUMrandomInteger (long lowest, long highest) {
-	return lowest + (long) ((highest - lowest + 1) * NUMrandomFraction ());
+	return lowest + (long) ((highest - lowest + 1) * NUMrandomFraction ());   // round down by truncation, because positive
 }
 
 #define repeat  do
 #define until(cond)  while (! (cond))
 double NUMrandomGauss (double mean, double standardDeviation) {
+	NUMrandom_State *me = & states [0];
 	/*
 		Knuth, p. 122.
 	*/
-	static int secondAvailable = 0;
-	static double y;
 	double s, x;
-	if (secondAvailable) {
-		secondAvailable = FALSE;
-		return mean + standardDeviation * y;
+	if (my secondAvailable) {
+		my secondAvailable = FALSE;
+		return mean + standardDeviation * my y;
 	} else {
 		repeat {
 			x = 2.0 * NUMrandomFraction () - 1.0;   /* Inside the square [-1; 1] x [-1; 1]. */
-			y = 2.0 * NUMrandomFraction () - 1.0;
-			s = x * x + y * y;
+			my y = 2.0 * NUMrandomFraction () - 1.0;
+			s = x * x + my y * my y;
+		} until (s < 1.0);   /* Inside the unit circle. */
+		if (s == 0.0) {
+			x = my y = 0.0;
+		} else {
+			double factor = sqrt (-2.0 * log (s) / s);
+			x *= factor, my y *= factor;
+		}
+		my secondAvailable = TRUE;
+		return mean + standardDeviation * x;
+	}
+}
+
+double NUMrandomGauss_mt (int threadNumber, double mean, double standardDeviation) {
+	NUMrandom_State *me = & states [threadNumber];
+	/*
+		Knuth, p. 122.
+	*/
+	double s, x;
+	if (my secondAvailable) {
+		my secondAvailable = FALSE;
+		return mean + standardDeviation * my y;
+	} else {
+		repeat {
+			x = 2.0 * NUMrandomFraction_mt (threadNumber) - 1.0;   /* Inside the square [-1; 1] x [-1; 1]. */
+			my y = 2.0 * NUMrandomFraction_mt (threadNumber) - 1.0;
+			s = x * x + my y * my y;
 		} until (s < 1.0);   /* Inside the unit circle. */
 		if (s == 0.0) {
-			x = y = 0.0;
+			x = my y = 0.0;
 		} else {
 			double factor = sqrt (-2.0 * log (s) / s);
-			x *= factor, y *= factor;
+			x *= factor, my y *= factor;
 		}
-		secondAvailable = TRUE;
+		my secondAvailable = TRUE;
 		return mean + standardDeviation * x;
 	}
 }
diff --git a/stat/Distributions.cpp b/stat/Distributions.cpp
index aed7c77..595e79d 100644
--- a/stat/Distributions.cpp
+++ b/stat/Distributions.cpp
@@ -1,6 +1,6 @@
 /* Distributions.cpp
  *
- * Copyright (C) 1997-2012 Paul Boersma
+ * Copyright (C) 1997-2012,2014 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
@@ -44,31 +44,7 @@ void Distributions_checkSpecifiedColumnNumberWithinRange (Distributions me, long
 		Melder_throw (me, ": the specified column number is ", columnNumber, ", but should be at most my number of columns (", my numberOfColumns, ").");
 }
 
-int Distributions_peek (Distributions me, long column, wchar_t **string) {
-	Distributions_checkSpecifiedColumnNumberWithinRange (me, column);
-	if (my numberOfRows < 1)
-		Melder_throw (me, ": I have no candidates.");
-	double total = 0.0;
-	for (long irow = 1; irow <= my numberOfRows; irow ++) {
-		total += my data [irow] [column];
-	}
-	if (total <= 0.0)
-		Melder_throw (me, ": the total weight of column ", column, " is not positive.");
-	long irow;
-	do {
-		double rand = NUMrandomUniform (0, total), sum = 0.0;
-		for (irow = 1; irow <= my numberOfRows; irow ++) {
-			sum += my data [irow] [column];
-			if (rand <= sum) break;
-		}
-	} while (irow > my numberOfRows);   // guard against rounding errors
-	*string = my rowLabels [irow];
-	if (! *string)
-		Melder_throw (me, ": no string in row ", irow, ".");
-	return 1;
-}
-
-int Distributions_peek_opt (Distributions me, long column, long *number) {
+void Distributions_peek (Distributions me, long column, wchar_t **string, long *number) {
 	Distributions_checkSpecifiedColumnNumberWithinRange (me, column);
 	if (my numberOfRows < 1)
 		Melder_throw (me, ": I have no candidates.");
@@ -88,8 +64,10 @@ int Distributions_peek_opt (Distributions me, long column, long *number) {
 	} while (irow > my numberOfRows);   /* Guard against rounding errors. */
 	if (my rowLabels [irow] == NULL)
 		Melder_throw (me, ": no string in row ", irow, ".");
-	*number = irow;
-	return 1;
+	if (string)
+		*string = my rowLabels [irow];
+	if (number)
+		*number = irow;
 }
 
 double Distributions_getProbability (Distributions me, const wchar_t *string, long column) {
diff --git a/stat/Distributions.h b/stat/Distributions.h
index 28f525a..d21dfbd 100644
--- a/stat/Distributions.h
+++ b/stat/Distributions.h
@@ -2,7 +2,7 @@
 #define _Distributions_h_
 /* Distributions.h
  *
- * Copyright (C) 1997-2011 Paul Boersma
+ * Copyright (C) 1997-2011,2014 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
@@ -30,8 +30,7 @@ Thing_define (Distributions, TableOfReal) {
 
 Distributions Distributions_create (long numberOfRows, long numberOfColumns);
 
-int Distributions_peek (Distributions me, long column, wchar_t **string);
-int Distributions_peek_opt (Distributions me, long column, long *row);
+void Distributions_peek (Distributions me, long column, wchar_t **string, long *row);
 
 double Distributions_getProbability (Distributions me, const wchar_t *string, long column);
 double Distributionses_getMeanAbsoluteDifference (Distributions me, Distributions thee, long column);
diff --git a/stat/Distributions_and_Strings.cpp b/stat/Distributions_and_Strings.cpp
index e075475..11f1445 100644
--- a/stat/Distributions_and_Strings.cpp
+++ b/stat/Distributions_and_Strings.cpp
@@ -1,6 +1,6 @@
 /* Distributions_and_Strings.cpp
  *
- * Copyright (C) 1997-2011 Paul Boersma
+ * Copyright (C) 1997-2011,2014 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
@@ -33,7 +33,7 @@ Strings Distributions_to_Strings (Distributions me, long column, long numberOfSt
 		thy strings = NUMvector <wchar_t*> (1, numberOfStrings);
 		for (long istring = 1; istring <= numberOfStrings; istring ++) {
 			wchar_t *string;
-			Distributions_peek (me, column, & string);
+			Distributions_peek (me, column, & string, NULL);
 			thy strings [istring] = Melder_wcsdup (string);
 		}
 		return thee.transfer();
diff --git a/stat/Regression.cpp b/stat/Regression.cpp
index e2f28fe..e6f7652 100644
--- a/stat/Regression.cpp
+++ b/stat/Regression.cpp
@@ -1,6 +1,6 @@
 /* Regression.cpp
  *
- * Copyright (C) 2005-2011 Paul Boersma
+ * Copyright (C) 2005-2011,2014 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,13 +65,11 @@ void structRegression :: v_info () {
 
 Thing_implement (Regression, Data, 0);
 
-void Regression_init (I) {
-	iam (Regression);
+void Regression_init (Regression me) {
 	my parameters = Ordered_create ();
 }
 
-void Regression_addParameter (I, const wchar_t *label, double minimum, double maximum, double value) {
-	iam (Regression);
+void Regression_addParameter (Regression me, const wchar_t *label, double minimum, double maximum, double value) {
 	try {
 		autoRegressionParameter thee = Thing_new (RegressionParameter);
 		thy label = Melder_wcsdup (label);
@@ -84,8 +82,7 @@ void Regression_addParameter (I, const wchar_t *label, double minimum, double ma
 	}
 }
 
-long Regression_getFactorIndexFromFactorName_e (I, const wchar_t *factorName) {
-	iam (Regression);
+long Regression_getFactorIndexFromFactorName_e (Regression me, const wchar_t *factorName) {
 	for (long iparm = 1; iparm <= my parameters -> size; iparm ++) {
 		RegressionParameter parm = static_cast<RegressionParameter> (my parameters -> item [iparm]);
 		if (Melder_wcsequ (factorName, parm -> label)) return iparm;
diff --git a/stat/Regression.h b/stat/Regression.h
index a4fd4ff..06743c8 100644
--- a/stat/Regression.h
+++ b/stat/Regression.h
@@ -2,7 +2,7 @@
 #define _Regression_h_
 /* Regression.h
  *
- * Copyright (C) 2005-2011 Paul Boersma
+ * Copyright (C) 2005-2011,2014 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
@@ -25,9 +25,9 @@
 oo_CLASS_CREATE (RegressionParameter, Data);
 oo_CLASS_CREATE (Regression, Data);
 
-void Regression_init (I);
-void Regression_addParameter (I, const wchar_t *label, double minimum, double maximum, double value);
-long Regression_getFactorIndexFromFactorName_e (I, const wchar_t *factorName);
+void Regression_init (Regression me);
+void Regression_addParameter (Regression me, const wchar_t *label, double minimum, double maximum, double value);
+long Regression_getFactorIndexFromFactorName_e (Regression me, const wchar_t *factorName);
 
 Thing_define (LinearRegression, Regression) {
 };
diff --git a/stat/Table.cpp b/stat/Table.cpp
index 22e6fd4..5f2d418 100644
--- a/stat/Table.cpp
+++ b/stat/Table.cpp
@@ -1,6 +1,6 @@
 /* Table.cpp
  *
- * Copyright (C) 2002-2012 Paul Boersma
+ * Copyright (C) 2002-2012,2013,2014 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
@@ -121,8 +121,7 @@ static TableRow TableRow_create (long numberOfColumns) {
 	return me.transfer();
 }
 
-void Table_initWithoutColumnNames (I, long numberOfRows, long numberOfColumns) {
-	iam (Table);
+void Table_initWithoutColumnNames (Table me, long numberOfRows, long numberOfColumns) {
 	if (numberOfColumns < 1)
 		Melder_throw ("Cannot create table without columns.");
 	my numberOfColumns = numberOfColumns;
@@ -143,8 +142,7 @@ Table Table_createWithoutColumnNames (long numberOfRows, long numberOfColumns) {
 	}
 }
 
-void Table_initWithColumnNames (I, long numberOfRows, const wchar_t *columnNames) {
-	iam (Table);
+void Table_initWithColumnNames (Table me, long numberOfRows, const wchar_t *columnNames) {
 	Table_initWithoutColumnNames (me, numberOfRows, Melder_countTokens (columnNames));
 	long icol = 0;
 	for (wchar_t *columnName = Melder_firstToken (columnNames); columnName != NULL; columnName = Melder_nextToken ()) {
@@ -751,35 +749,35 @@ Table Table_collapseRows (Table me, const wchar_t *factors_string, const wchar_t
 			Melder_throw ("In order to pool table data, you must supply at least one independent variable.");
 		Table_columns_checkExist (me, factors.peek(), numberOfFactors);
 
-		long numberToSum;
+		long numberToSum = 0;
 		autoMelderTokens columnsToSum;
 		if (columnsToSum_string) {
 			columnsToSum.reset (columnsToSum_string, & numberToSum);
 			Table_columns_checkExist (me, columnsToSum.peek(), numberToSum);
 			Table_columns_checkCrossSectionEmpty (factors.peek(), numberOfFactors, columnsToSum.peek(), numberToSum);
 		}
-		long numberToAverage;
+		long numberToAverage = 0;
 		autoMelderTokens columnsToAverage;
 		if (columnsToAverage_string) {
 			columnsToAverage.reset (columnsToAverage_string, & numberToAverage);
 			Table_columns_checkExist (me, columnsToAverage.peek(), numberToAverage);
 			Table_columns_checkCrossSectionEmpty (factors.peek(), numberOfFactors, columnsToAverage.peek(), numberToAverage);
 		}
-		long numberToMedianize;
+		long numberToMedianize = 0;
 		autoMelderTokens columnsToMedianize;
 		if (columnsToMedianize_string) {
 			columnsToMedianize.reset (columnsToMedianize_string, & numberToMedianize);
 			Table_columns_checkExist (me, columnsToMedianize.peek(), numberToMedianize);
 			Table_columns_checkCrossSectionEmpty (factors.peek(), numberOfFactors, columnsToMedianize.peek(), numberToMedianize);
 		}
-		long numberToAverageLogarithmically;
+		long numberToAverageLogarithmically = 0;
 		autoMelderTokens columnsToAverageLogarithmically;
 		if (columnsToAverageLogarithmically_string) {
 			columnsToAverageLogarithmically.reset (columnsToAverageLogarithmically_string, & numberToAverageLogarithmically);
 			Table_columns_checkExist (me, columnsToAverageLogarithmically.peek(), numberToAverageLogarithmically);
 			Table_columns_checkCrossSectionEmpty (factors.peek(), numberOfFactors, columnsToAverageLogarithmically.peek(), numberToAverageLogarithmically);
 		}
-		long numberToMedianizeLogarithmically;
+		long numberToMedianizeLogarithmically = 0;
 		autoMelderTokens columnsToMedianizeLogarithmically;
 		if (columnsToMedianizeLogarithmically_string) {
 			columnsToMedianizeLogarithmically.reset (columnsToMedianizeLogarithmically_string, & numberToMedianizeLogarithmically);
diff --git a/stat/Table.h b/stat/Table.h
index d975221..d8352be 100644
--- a/stat/Table.h
+++ b/stat/Table.h
@@ -2,7 +2,7 @@
 #define _Table_h_
 /* Table.h
  *
- * Copyright (C) 2002-2011,2012 Paul Boersma
+ * Copyright (C) 2002-2011,2012,2014 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,9 +27,9 @@
 oo_CLASS_CREATE (TableRow, Data);
 oo_CLASS_CREATE (Table, Data);
 
-void Table_initWithColumnNames (I, long numberOfRows, const wchar_t *columnNames);
+void Table_initWithColumnNames (Table me, long numberOfRows, const wchar_t *columnNames);
 Table Table_createWithColumnNames (long numberOfRows, const wchar_t *columnNames);
-void Table_initWithoutColumnNames (I, long numberOfRows, long numberOfColumns);
+void Table_initWithoutColumnNames (Table me, long numberOfRows, long numberOfColumns);
 Table Table_createWithoutColumnNames (long numberOfRows, long numberOfColumns);
 #define Table_create Table_createWithoutColumnNames
 
diff --git a/stat/TableOfReal.cpp b/stat/TableOfReal.cpp
index c9dd407..ebe93d8 100644
--- a/stat/TableOfReal.cpp
+++ b/stat/TableOfReal.cpp
@@ -1,6 +1,6 @@
 /* TableOfReal.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
diff --git a/stat/praat_Stat.cpp b/stat/praat_Stat.cpp
index c735269..1bac4c6 100644
--- a/stat/praat_Stat.cpp
+++ b/stat/praat_Stat.cpp
@@ -1,6 +1,6 @@
 /* praat_Stat.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -42,66 +42,70 @@ static wchar_t * Table_messageColumn (Table me, long column) {
 }
 
 /***** DISTRIBUTIONS *****/
+#pragma mark -
+#pragma mark DISTRIBUTIONS
 
-DIRECT (Distributionses_add)
+DIRECT2 (Distributionses_add) {
 	autoCollection me = praat_getSelectedObjects ();
 	autoDistributions thee = Distributions_addMany (me.peek());
 	praat_new (thee.transfer(), L"added");
-END
+END2 }
 
-FORM (Distributionses_getMeanAbsoluteDifference, L"Get mean difference", 0)
+FORM (Distributionses_getMeanAbsoluteDifference, L"Get mean difference", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	Distributions me = NULL, thee = NULL;
 	LOOP {
 		(me ? thee : me) = (Distributions) OBJECT;
 	}
 	Melder_informationReal (Distributionses_getMeanAbsoluteDifference (me, thee, GET_INTEGER (L"Column number")), NULL);
-END
+END2 }
 
-FORM (Distributions_getProbability, L"Get probability", 0)
+FORM (Distributions_getProbability, L"Get probability", 0) {
 	NATURAL (L"Column number", L"1")
 	SENTENCE (L"String", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Distributions);
 		double probability = Distributions_getProbability (me, GET_STRING (L"String"), GET_INTEGER (L"Column number"));
 		Melder_informationReal (probability, NULL);
 	}
-END
+END2 }
 
-DIRECT (Distributions_help)
+DIRECT2 (Distributions_help) {
 	Melder_help (L"Distributions");
-END
+END2 }
 
-FORM (Distributions_to_Strings, L"To Strings", 0)
+FORM (Distributions_to_Strings, L"To Strings", 0) {
 	NATURAL (L"Column number", L"1")
 	NATURAL (L"Number of strings", L"1000")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Distributions);
 		autoStrings thee = Distributions_to_Strings (me, GET_INTEGER (L"Column number"), GET_INTEGER (L"Number of strings"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (Distributions_to_Strings_exact, L"To Strings (exact)", 0)
+FORM (Distributions_to_Strings_exact, L"To Strings (exact)", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Distributions);
 		autoStrings thee = Distributions_to_Strings_exact (me, GET_INTEGER (L"Column number"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
 /***** LOGISTICREGRESSION *****/
+#pragma mark -
+#pragma mark LOGISTICREGRESSION
 
-FORM (LogisticRegression_drawBoundary, L"LogisticRegression: Draw boundary", 0)
+FORM (LogisticRegression_drawBoundary, L"LogisticRegression: Draw boundary", 0) {
 	WORD (L"Horizontal factor", L"")
 	REAL (L"left Horizontal range", L"0.0")
 	REAL (L"right Horizontal range", L"0.0 (= auto)")
@@ -109,7 +113,7 @@ FORM (LogisticRegression_drawBoundary, L"LogisticRegression: Draw boundary", 0)
 	REAL (L"left Vertical range", L"0.0")
 	REAL (L"right Vertical range", L"0.0 (= auto)")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -121,89 +125,91 @@ DO
 			yfactor, GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
 			GET_INTEGER (L"Garnish"));
 	}
-END
+END2 }
 
 /***** PAIRDISTRIBUTION *****/
+#pragma mark -
+#pragma mark PAIRDISTRIBUTION
 
-DIRECT (PairDistribution_getFractionCorrect_maximumLikelihood)
+DIRECT2 (PairDistribution_getFractionCorrect_maximumLikelihood) {
 	LOOP {
 		iam (PairDistribution);
 		double fractionCorrect = PairDistribution_getFractionCorrect_maximumLikelihood (me);
 		Melder_informationReal (fractionCorrect, NULL);
 	}
-END
+END2 }
 
-DIRECT (PairDistribution_getFractionCorrect_probabilityMatching)
+DIRECT2 (PairDistribution_getFractionCorrect_probabilityMatching) {
 	LOOP {
 		iam (PairDistribution);
 		double fractionCorrect = PairDistribution_getFractionCorrect_probabilityMatching (me);
 		Melder_informationReal (fractionCorrect, NULL);
 	}
-END
+END2 }
 
-DIRECT (PairDistribution_getNumberOfPairs)
+DIRECT2 (PairDistribution_getNumberOfPairs) {
 	LOOP {
 		iam (PairDistribution);
 		Melder_information (Melder_integer (my pairs -> size));
 	}
-END
+END2 }
 
-FORM (PairDistribution_getString1, L"Get string1", 0)
+FORM (PairDistribution_getString1, L"Get string1", 0) {
 	NATURAL (L"Pair number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (PairDistribution);
 		const wchar_t *string1 = PairDistribution_getString1 (me, GET_INTEGER (L"Pair number"));
 		Melder_information (string1);
 	}
-END
+END2 }
 
-FORM (PairDistribution_getString2, L"Get string2", 0)
+FORM (PairDistribution_getString2, L"Get string2", 0) {
 	NATURAL (L"Pair number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (PairDistribution);
 		const wchar_t *string2 = PairDistribution_getString2 (me, GET_INTEGER (L"Pair number"));
 		Melder_information (string2);
 	}
-END
+END2 }
 
-FORM (PairDistribution_getWeight, L"Get weight", 0)
+FORM (PairDistribution_getWeight, L"Get weight", 0) {
 	NATURAL (L"Pair number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (PairDistribution);
 		double weight = PairDistribution_getWeight (me, GET_INTEGER (L"Pair number"));
 		Melder_information (Melder_double (weight));
 	}
-END
+END2 }
 
-DIRECT (PairDistribution_help) Melder_help (L"PairDistribution"); END
+DIRECT2 (PairDistribution_help) { Melder_help (L"PairDistribution"); END2 }
 
-DIRECT (PairDistribution_removeZeroWeights)
+DIRECT2 (PairDistribution_removeZeroWeights) {
 	LOOP {
 		iam (PairDistribution);
 		PairDistribution_removeZeroWeights (me);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (PairDistribution_swapInputsAndOutputs)
+DIRECT2 (PairDistribution_swapInputsAndOutputs) {
 	LOOP {
 		iam (PairDistribution);
 		my f_swapInputsAndOutputs ();
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (PairDistribution_to_Stringses, L"Generate two Strings objects", 0)
+FORM (PairDistribution_to_Stringses, L"Generate two Strings objects", 0) {
 	NATURAL (L"Number", L"1000")
 	SENTENCE (L"Name of first Strings", L"input")
 	SENTENCE (L"Name of second Strings", L"output")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (PairDistribution);
@@ -213,21 +219,23 @@ DO
 		praat_new (strings1.transfer(), GET_STRING (L"Name of first Strings"));
 		praat_new (strings2.transfer(), GET_STRING (L"Name of second Strings"));
 	}
-END
+END2 }
 
-DIRECT (PairDistribution_to_Table)
+DIRECT2 (PairDistribution_to_Table) {
 	LOOP {
 		iam (PairDistribution);
 		autoTable thee = PairDistribution_to_Table (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
 /***** PAIRDISTRIBUTION & DISTRIBUTIONS *****/
+#pragma mark -
+#pragma mark PAIRDISTRIBUTION & DISTRIBUTIONS
 
-FORM (PairDistribution_Distributions_getFractionCorrect, L"PairDistribution & Distributions: Get fraction correct", 0)
+FORM (PairDistribution_Distributions_getFractionCorrect, L"PairDistribution & Distributions: Get fraction correct", 0) {
 	NATURAL (L"Column", L"1")
-	OK
+	OK2
 DO
 	PairDistribution me = NULL;
 	Distributions thee = NULL;
@@ -237,11 +245,13 @@ DO
 	}
 	double fractionCorrect = PairDistribution_Distributions_getFractionCorrect (me, thee, GET_INTEGER (L"Column"));
 	Melder_informationReal (fractionCorrect, NULL);
-END
+END2 }
 
 /***** TABLE *****/
+#pragma mark -
+#pragma mark TABLE
 
-DIRECT (Tables_append)
+DIRECT2 (Tables_append) {
 	autoCollection collection = Collection_create (classTable, 10);
 	Collection_dontOwnItems (collection.peek());
 	LOOP {
@@ -250,24 +260,24 @@ DIRECT (Tables_append)
 	}
 	autoTable thee = Tables_append (collection.peek());
 	praat_new (thee.transfer(), L"appended");
-END
+END2 }
 
-FORM (Table_appendColumn, L"Table: Append column", 0)
+FORM (Table_appendColumn, L"Table: Append column", 0) {
 	WORD (L"Label", L"newcolumn")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_appendColumn (me, GET_STRING (L"Label"));
 		praat_dataChanged (OBJECT);
 	}
-END
+END2 }
 
-FORM (Table_appendDifferenceColumn, L"Table: Append difference column", 0)
+FORM (Table_appendDifferenceColumn, L"Table: Append difference column", 0) {
 	WORD (L"left Columns", L"")
 	WORD (L"right Columns", L"")
 	WORD (L"Label", L"diff")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -276,13 +286,13 @@ DO
 		Table_appendDifferenceColumn (me, icol, jcol, GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_appendProductColumn, L"Table: Append product column", 0)
+FORM (Table_appendProductColumn, L"Table: Append product column", 0) {
 	WORD (L"left Columns", L"")
 	WORD (L"right Columns", L"")
 	WORD (L"Label", L"diff")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -291,13 +301,13 @@ DO
 		Table_appendProductColumn (me, icol, jcol, GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_appendQuotientColumn, L"Table: Append quotient column", 0)
+FORM (Table_appendQuotientColumn, L"Table: Append quotient column", 0) {
 	WORD (L"left Columns", L"")
 	WORD (L"right Columns", L"")
 	WORD (L"Label", L"diff")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -306,13 +316,13 @@ DO
 		Table_appendQuotientColumn (me, icol, jcol, GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_appendSumColumn, L"Table: Append sum column", 0)
+FORM (Table_appendSumColumn, L"Table: Append sum column", 0) {
 	WORD (L"left Columns", L"")
 	WORD (L"right Columns", L"")
 	WORD (L"Label", L"diff")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -321,17 +331,17 @@ DO
 		Table_appendSumColumn (me, icol, jcol, GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (Table_appendRow)
+DIRECT2 (Table_appendRow) {
 	LOOP {
 		iam (Table);
 		Table_appendRow (me);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_collapseRows, L"Table: Collapse rows", 0)
+FORM (Table_collapseRows, L"Table: Collapse rows", 0) {
 	LABEL (L"", L"Columns with factors (independent variables):")
 	TEXTFIELD (L"factors", L"speaker dialect age vowel")
 	LABEL (L"", L"Columns to sum:")
@@ -345,7 +355,7 @@ FORM (Table_collapseRows, L"Table: Collapse rows", 0)
 	LABEL (L"", L"Columns to medianize logarithmically:")
 	TEXTFIELD (L"columnsToMedianizeLogarithmically", L"F0 F1 F2 F3")
 	LABEL (L"", L"Columns not mentioned above will be ignored.")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -355,30 +365,30 @@ DO
 			GET_STRING (L"columnsToAverageLogarithmically"), GET_STRING (L"columnsToMedianizeLogarithmically"));
 		praat_new (thee.transfer(), my name, L"_pooled");
 	}
-END
+END2 }
 
-FORM (Table_createWithColumnNames, L"Create Table with column names", 0)
+FORM (Table_createWithColumnNames, L"Create Table with column names", 0) {
 	WORD (L"Name", L"table")
 	INTEGER (L"Number of rows", L"10")
 	LABEL (L"", L"Column names:")
 	TEXTFIELD (L"columnNames", L"speaker dialect age vowel F0 F1 F2")
-	OK
+	OK2
 DO
 	autoTable me = Table_createWithColumnNames (GET_INTEGER (L"Number of rows"), GET_STRING (L"columnNames"));
 	praat_new (me.transfer(), GET_STRING (L"Name"));
-END
+END2 }
 
-FORM (Table_createWithoutColumnNames, L"Create Table without column names", 0)
+FORM (Table_createWithoutColumnNames, L"Create Table without column names", 0) {
 	WORD (L"Name", L"table")
 	INTEGER (L"Number of rows", L"10")
 	NATURAL (L"Number of columns", L"3")
-	OK
+	OK2
 DO
 	autoTable me = Table_createWithoutColumnNames (GET_INTEGER (L"Number of rows"), GET_INTEGER (L"Number of columns"));
 	praat_new (me.transfer(), GET_STRING (L"Name"));
-END
+END2 }
 
-FORM (Table_drawEllipse, L"Draw ellipse (standard deviation)", 0)
+FORM (Table_drawEllipse, L"Draw ellipse (standard deviation)", 0) {
 	WORD (L"Horizontal column", L"")
 	REAL (L"left Horizontal range", L"0.0")
 	REAL (L"right Horizontal range", L"0.0 (= auto)")
@@ -387,7 +397,7 @@ FORM (Table_drawEllipse, L"Draw ellipse (standard deviation)", 0)
 	REAL (L"right Vertical range", L"0.0 (= auto)")
 	POSITIVE (L"Number of sigmas", L"2.0")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -399,11 +409,11 @@ DO
 			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
 			GET_REAL (L"Number of sigmas"), GET_INTEGER (L"Garnish"));
 	}
-END
+END2 }
 
-FORM (Table_drawRowFromDistribution, L"Table: Draw row from distribution", 0)
+FORM (Table_drawRowFromDistribution, L"Table: Draw row from distribution", 0) {
 	WORD (L"Column with distribution", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -411,22 +421,22 @@ DO
 		long row = Table_drawRowFromDistribution (me, icol);
 		Melder_information (Melder_integer (row));
 	}
-END
+END2 }
 
-DIRECT (Table_edit)
+DIRECT2 (Table_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw ("Cannot edit a Table from batch.");
 	LOOP {
 		iam (Table);
 		autoTableEditor editor = TableEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.transfer(), IOBJECT);
 	}
-END
+END2 }
 
-FORM (Table_extractRowsWhereColumn_number, L"Table: Extract rows where column (number)", 0)
+FORM (Table_extractRowsWhereColumn_number, L"Table: Extract rows where column (number)", 0) {
 	WORD (L"Extract all rows where column...", L"")
 	RADIO_ENUM (L"...is...", kMelder_number, DEFAULT)
 	REAL (L"...the number", L"0.0")
-	OK
+	OK2
 DO
 	double value = GET_REAL (L"...the number");
 	LOOP {
@@ -436,13 +446,13 @@ DO
 		praat_new (thee.transfer(), my name, L"_", Table_messageColumn (static_cast <Table> OBJECT, icol), L"_", NUMdefined (value) ? Melder_integer ((long) round (value)) : L"undefined");
 		praat_dataChanged (me);   // WHY?
 	}
-END
+END2 }
 
-FORM (Table_extractRowsWhereColumn_text, L"Table: Extract rows where column (text)", 0)
+FORM (Table_extractRowsWhereColumn_text, L"Table: Extract rows where column (text)", 0) {
 	WORD (L"Extract all rows where column...", L"")
 	OPTIONMENU_ENUM (L"...", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"hi")
-	OK
+	OK2
 DO
 	const wchar_t *value = GET_STRING (L"...the text");
 	LOOP {
@@ -452,12 +462,12 @@ DO
 		praat_new (thee.transfer(), my name, L"_", value);
 		praat_dataChanged (me);   // WHY?
 	}
-END
+END2 }
 
-FORM (Table_formula, L"Table: Formula", L"Table: Formula...")
+FORM (Table_formula, L"Table: Formula", L"Table: Formula...") {
 	WORD (L"Column label", L"")
 	TEXTFIELD (L"formula", L"abs (self)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -470,13 +480,13 @@ DO
 			throw;
 		}
 	}
-END
+END2 }
 
-FORM (Table_formula_columnRange, L"Table: Formula (column range)", L"Table: Formula...")
+FORM (Table_formula_columnRange, L"Table: Formula (column range)", L"Table: Formula...") {
 	WORD (L"From column label", L"")
 	WORD (L"To column label", L"")
 	TEXTFIELD (L"formula", L"log10 (self)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -490,21 +500,21 @@ DO
 			throw;
 		}
 	}
-END
+END2 }
 
-FORM (Table_getColumnIndex, L"Table: Get column index", 0)
+FORM (Table_getColumnIndex, L"Table: Get column index", 0) {
 	SENTENCE (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Melder_information (Melder_integer (Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Column label"))));
 	}
-END
+END2 }
 
-FORM (Table_getColumnLabel, L"Table: Get column label", 0)
+FORM (Table_getColumnLabel, L"Table: Get column label", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -512,13 +522,13 @@ DO
 		if (icol > my numberOfColumns) Melder_throw ("Column number must not be greater than number of columns.");
 		Melder_information (my columnHeaders [icol]. label);
 	}
-END
+END2 }
 
-FORM (Table_getGroupMean, L"Table: Get group mean", 0)
+FORM (Table_getGroupMean, L"Table: Get group mean", 0) {
 	WORD (L"Column label", L"salary")
 	WORD (L"Group column", L"gender")
 	SENTENCE (L"Group", L"F")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -526,11 +536,11 @@ DO
 		long groupColumn = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Group column"));
 		Melder_information (Melder_double (Table_getGroupMean (static_cast <Table> ONLY_OBJECT, column, groupColumn, GET_STRING (L"Group"))));
 	}
-END
+END2 }
 
-FORM (Table_getMaximum, L"Table: Get maximum", 0)
+FORM (Table_getMaximum, L"Table: Get maximum", 0) {
 	SENTENCE (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -538,11 +548,11 @@ DO
 		double maximum = Table_getMaximum (me, icol);
 		Melder_information (Melder_double (maximum));
 	}
-END
+END2 }
 
-FORM (Table_getMean, L"Table: Get mean", 0)
+FORM (Table_getMean, L"Table: Get mean", 0) {
 	SENTENCE (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -550,11 +560,11 @@ DO
 		double mean = Table_getMean (me, icol);
 		Melder_information (Melder_double (mean));
 	}
-END
+END2 }
 
-FORM (Table_getMinimum, L"Table: Get minimum", 0)
+FORM (Table_getMinimum, L"Table: Get minimum", 0) {
 	SENTENCE (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -562,12 +572,12 @@ DO
 		double minimum = Table_getMinimum (me, icol);
 		Melder_information (Melder_double (minimum));
 	}
-END
+END2 }
 
-FORM (Table_getQuantile, L"Table: Get quantile", 0)
+FORM (Table_getQuantile, L"Table: Get quantile", 0) {
 	SENTENCE (L"Column label", L"")
 	POSITIVE (L"Quantile", L"0.50 (= median)")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -575,11 +585,11 @@ DO
 		double quantile = Table_getQuantile (me, icol, GET_REAL (L"Quantile"));
 		Melder_information (Melder_double (quantile));
 	}
-END
+END2 }
 
-FORM (Table_getStandardDeviation, L"Table: Get standard deviation", 0)
+FORM (Table_getStandardDeviation, L"Table: Get standard deviation", 0) {
 	SENTENCE (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -587,26 +597,26 @@ DO
 		double stdev = Table_getStdev (me, icol);
 		Melder_information (Melder_double (stdev));
 	}
-END
+END2 }
 
-DIRECT (Table_getNumberOfColumns)
+DIRECT2 (Table_getNumberOfColumns) {
 	LOOP {
 		iam (Table);
 		Melder_information (Melder_integer (my numberOfColumns));
 	}
-END
+END2 }
 
-DIRECT (Table_getNumberOfRows)
+DIRECT2 (Table_getNumberOfRows) {
 	LOOP {
 		iam (Table);
 		Melder_information (Melder_integer (my rows -> size));
 	}
-END
+END2 }
 
-FORM (Table_getValue, L"Table: Get value", 0)
+FORM (Table_getValue, L"Table: Get value", 0) {
 	NATURAL (L"Row number", L"1")
 	WORD (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -615,58 +625,58 @@ DO
 		long icol = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Column label"));
 		Melder_information (((TableRow) my rows -> item [rowNumber]) -> cells [icol]. string);
 	}
-END
+END2 }
 
-DIRECT (Table_help) Melder_help (L"Table"); END
+DIRECT2 (Table_help) { Melder_help (L"Table"); END2 }
 
-FORM (Table_insertColumn, L"Table: Insert column", 0)
+FORM (Table_insertColumn, L"Table: Insert column", 0) {
 	NATURAL (L"Position", L"1")
 	WORD (L"Label", L"newcolumn")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_insertColumn (me, GET_INTEGER (L"Position"), GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_insertRow, L"Table: Insert row", 0)
+FORM (Table_insertRow, L"Table: Insert row", 0) {
 	NATURAL (L"Position", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_insertRow (me, GET_INTEGER (L"Position"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_list, L"Table: List", 0)
+FORM (Table_list, L"Table: List", 0) {
 	BOOLEAN (L"Include row numbers", true)
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_list (me, GET_INTEGER (L"Include row numbers"));
 	}
-END
+END2 }
 
-FORM_READ (Table_readFromTableFile, L"Read Table from table file", 0, true)
+FORM_READ2 (Table_readFromTableFile, L"Read Table from table file", 0, true) {
 	praat_newWithFile (Table_readFromTableFile (file), MelderFile_name (file), file);
-END
+END2 }
 
-FORM_READ (Table_readFromCommaSeparatedFile, L"Read Table from comma-separated file", 0, true)
+FORM_READ2 (Table_readFromCommaSeparatedFile, L"Read Table from comma-separated file", 0, true) {
 	praat_newWithFile (Table_readFromCharacterSeparatedTextFile (file, ','), MelderFile_name (file), file);
-END
+END2 }
 
-FORM_READ (Table_readFromTabSeparatedFile, L"Read Table from tab-separated file", 0, true)
+FORM_READ2 (Table_readFromTabSeparatedFile, L"Read Table from tab-separated file", 0, true) {
 	praat_newWithFile (Table_readFromCharacterSeparatedTextFile (file, '\t'), MelderFile_name (file), file);
-END
+END2 }
 
-FORM (Table_removeColumn, L"Table: Remove column", 0)
+FORM (Table_removeColumn, L"Table: Remove column", 0) {
 	WORD (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -674,24 +684,24 @@ DO
 		Table_removeColumn (me, icol);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_removeRow, L"Table: Remove row", 0)
+FORM (Table_removeRow, L"Table: Remove row", 0) {
 	NATURAL (L"Row number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_removeRow (me, GET_INTEGER (L"Row number"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_reportCorrelation_kendallTau, L"Report correlation (Kendall tau)", 0)
+FORM (Table_reportCorrelation_kendallTau, L"Report correlation (Kendall tau)", 0) {
 	WORD (L"left Columns", L"")
 	WORD (L"right Columns", L"")
 	POSITIVE (L"One-tailed unconfidence", L"0.025")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -713,13 +723,13 @@ DO
 			L" (highest tau that cannot be rejected with " UNITEXT_GREEK_SMALL_LETTER_ALPHA " = ", Melder_double (unconfidence), L")");
 		MelderInfo_close ();
 	}
-END
+END2 }
 
-FORM (Table_reportCorrelation_pearsonR, L"Report correlation (Pearson r)", 0)
+FORM (Table_reportCorrelation_pearsonR, L"Report correlation (Pearson r)", 0) {
 	WORD (L"left Columns", L"")
 	WORD (L"right Columns", L"")
 	POSITIVE (L"One-tailed unconfidence", L"0.025")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -742,13 +752,13 @@ DO
 			L" (highest r that cannot be rejected with " UNITEXT_GREEK_SMALL_LETTER_ALPHA " = ", Melder_double (unconfidence), L")");
 		MelderInfo_close ();
 	}
-END
+END2 }
 	
-FORM (Table_reportDifference_studentT, L"Report difference (Student t)", 0)
+FORM (Table_reportDifference_studentT, L"Report difference (Student t)", 0) {
 	WORD (L"left Columns", L"")
 	WORD (L"right Columns", L"")
 	POSITIVE (L"One-tailed unconfidence", L"0.025")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -772,15 +782,15 @@ DO
 			L" (highest difference that cannot be rejected with " UNITEXT_GREEK_SMALL_LETTER_ALPHA " = ", Melder_double (unconfidence), L")");
 		MelderInfo_close ();
 	}
-END
+END2 }
 	
-FORM (Table_reportGroupDifference_studentT, L"Report group difference (Student t)", 0)
+FORM (Table_reportGroupDifference_studentT, L"Report group difference (Student t)", 0) {
 	WORD (L"Column", L"salary")
 	WORD (L"Group column", L"gender")
 	SENTENCE (L"Group 1", L"F")
 	SENTENCE (L"Group 2", L"M")
 	POSITIVE (L"One-tailed unconfidence", L"0.025")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -805,14 +815,14 @@ DO
 			L" (highest difference that cannot be rejected with " UNITEXT_GREEK_SMALL_LETTER_ALPHA " = ", Melder_double (unconfidence), L")");
 		MelderInfo_close ();
 	}
-END
+END2 }
 
-FORM (Table_reportGroupDifference_wilcoxonRankSum, L"Report group difference (Wilcoxon rank sum)", 0)
+FORM (Table_reportGroupDifference_wilcoxonRankSum, L"Report group difference (Wilcoxon rank sum)", 0) {
 	WORD (L"Column", L"salary")
 	WORD (L"Group column", L"gender")
 	SENTENCE (L"Group 1", L"F")
 	SENTENCE (L"Group 2", L"M")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -831,14 +841,14 @@ DO
 		MelderInfo_writeLine (L"Significance from zero: ", Melder_double (significanceFromZero), L" (one-tailed)");
 		MelderInfo_close ();
 	}
-END
+END2 }
 
-FORM (Table_reportGroupMean_studentT, L"Report group mean (Student t)", 0)
+FORM (Table_reportGroupMean_studentT, L"Report group mean (Student t)", 0) {
 	WORD (L"Column", L"salary")
 	WORD (L"Group column", L"gender")
 	SENTENCE (L"Group", L"F")
 	POSITIVE (L"One-tailed unconfidence", L"0.025")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -863,12 +873,12 @@ DO
 			L" (highest difference that cannot be rejected with " UNITEXT_GREEK_SMALL_LETTER_ALPHA " = ", Melder_double (unconfidence), L")");
 		MelderInfo_close ();
 	}
-END
+END2 }
 
-FORM (Table_reportMean_studentT, L"Report mean (Student t)", 0)
+FORM (Table_reportMean_studentT, L"Report mean (Student t)", 0) {
 	WORD (L"Column", L"")
 	POSITIVE (L"One-tailed unconfidence", L"0.025")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -890,16 +900,16 @@ DO
 			L" (highest value that cannot be rejected with " UNITEXT_GREEK_SMALL_LETTER_ALPHA " = ", Melder_double (unconfidence), L")");
 		MelderInfo_close ();
 	}
-END
+END2 }
 
-FORM (Table_rowsToColumns, L"Table: Rows to columns", 0)
+FORM (Table_rowsToColumns, L"Table: Rows to columns", 0) {
 	LABEL (L"", L"Columns with factors (independent variables):")
 	TEXTFIELD (L"factors", L"dialect gender speaker")
 	WORD (L"Column to transpose", L"vowel")
 	LABEL (L"", L"Columns to expand:")
 	TEXTFIELD (L"columnsToExpand", L"duration F0 F1 F2 F3")
 	LABEL (L"", L"Columns not mentioned above will be ignored.")
-	OK
+	OK2
 DO
 	const wchar_t *columnLabel = GET_STRING (L"Column to transpose");
 	LOOP {
@@ -908,9 +918,9 @@ DO
 		autoTable thee = Table_rowsToColumns (me, GET_STRING (L"factors"), icol, GET_STRING (L"columnsToExpand"));
 		praat_new (thee.transfer(), NAME, L"_nested");
 	}
-END
+END2 }
 
-FORM (Table_scatterPlot, L"Scatter plot", 0)
+FORM (Table_scatterPlot, L"Scatter plot", 0) {
 	WORD (L"Horizontal column", L"")
 	REAL (L"left Horizontal range", L"0.0")
 	REAL (L"right Horizontal range", L"0.0 (= auto)")
@@ -920,7 +930,7 @@ FORM (Table_scatterPlot, L"Scatter plot", 0)
 	WORD (L"Column with marks", L"")
 	NATURAL (L"Font size", L"12")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -933,9 +943,9 @@ DO
 			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
 			markColumn, GET_INTEGER (L"Font size"), GET_INTEGER (L"Garnish"));
 	}
-END
+END2 }
 
-FORM (Table_scatterPlot_mark, L"Scatter plot (marks)", 0)
+FORM (Table_scatterPlot_mark, L"Scatter plot (marks)", 0) {
 	WORD (L"Horizontal column", L"")
 	REAL (L"left Horizontal range", L"0.0")
 	REAL (L"right Horizontal range", L"0.0 (= auto)")
@@ -945,7 +955,7 @@ FORM (Table_scatterPlot_mark, L"Scatter plot (marks)", 0)
 	POSITIVE (L"Mark size (mm)", L"1.0")
 	BOOLEAN (L"Garnish", 1)
 	SENTENCE (L"Mark string (+xo.)", L"+")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -957,49 +967,49 @@ DO
 			GET_REAL (L"left Vertical range"), GET_REAL (L"right Vertical range"),
 			GET_REAL (L"Mark size"), GET_STRING (L"Mark string"), GET_INTEGER (L"Garnish"));
 	}
-END
+END2 }
 
-FORM (Table_searchColumn, L"Table: Search column", 0)
+FORM (Table_searchColumn, L"Table: Search column", 0) {
 	WORD (L"Column label", L"")
 	WORD (L"Value", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		long icol = Table_getColumnIndexFromColumnLabel (me, GET_STRING (L"Column label"));
 		Melder_information (Melder_integer (Table_searchColumn (me, icol, GET_STRING (L"Value"))));
 	}
-END
+END2 }
 	
-FORM (Table_setColumnLabel_index, L"Set column label", 0)
+FORM (Table_setColumnLabel_index, L"Set column label", 0) {
 	NATURAL (L"Column number", L"1")
 	SENTENCE (L"Label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_setColumnLabel (me, GET_INTEGER (L"Column number"), GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_setColumnLabel_label, L"Set column label", 0)
+FORM (Table_setColumnLabel_label, L"Set column label", 0) {
 	SENTENCE (L"Old label", L"")
 	SENTENCE (L"New label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_setColumnLabel (me, Table_findColumnIndexFromColumnLabel (me, GET_STRING (L"Old label")), GET_STRING (L"New label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_setNumericValue, L"Table: Set numeric value", 0)
+FORM (Table_setNumericValue, L"Table: Set numeric value", 0) {
 	NATURAL (L"Row number", L"1")
 	WORD (L"Column label", L"")
 	REAL_OR_UNDEFINED (L"Numeric value", L"1.5")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -1007,13 +1017,13 @@ DO
 		Table_setNumericValue (me, GET_INTEGER (L"Row number"), icol, GET_REAL (L"Numeric value"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_setStringValue, L"Table: Set string value", 0)
+FORM (Table_setStringValue, L"Table: Set string value", 0) {
 	NATURAL (L"Row number", L"1")
 	WORD (L"Column label", L"")
 	SENTENCE (L"String value", L"xx")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -1021,61 +1031,61 @@ DO
 		Table_setStringValue (me, GET_INTEGER (L"Row number"), icol, GET_STRING (L"String value"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (Table_randomizeRows)
+DIRECT2 (Table_randomizeRows) {
 	LOOP {
 		iam (Table);
 		Table_randomizeRows (me);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (Table_reflectRows)
+DIRECT2 (Table_reflectRows) {
 	LOOP {
 		iam (Table);
 		Table_reflectRows (me);
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (Table_sortRows, L"Table: Sort rows", 0)
+FORM (Table_sortRows, L"Table: Sort rows", 0) {
 	LABEL (L"", L"One or more column labels for sorting:")
 	TEXTFIELD (L"columnLabels", L"dialect gender name")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		Table_sortRows_string (me, GET_STRING (L"columnLabels"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (Table_to_LinearRegression)
+DIRECT2 (Table_to_LinearRegression) {
 	LOOP {
 		iam (Table);
 		autoLinearRegression thee = Table_to_LinearRegression (me);
 		praat_new (thee.transfer(), NAME);
 	}
-END
+END2 }
 
-FORM (Table_to_LogisticRegression, L"Table: To LogisticRegression", 0)
+FORM (Table_to_LogisticRegression, L"Table: To LogisticRegression", 0) {
 	LABEL (L"", L"Factors (column names):")
 	TEXTFIELD (L"factors", L"F0 F1 duration")
 	WORD (L"Dependent 1 (column name)", L"e")
 	WORD (L"Dependent 2 (column name)", L"i")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
 		autoLogisticRegression thee = Table_to_LogisticRegression (me, GET_STRING (L"factors"), GET_STRING (L"Dependent 1"), GET_STRING (L"Dependent 2"));
 		praat_new (thee.transfer(), NAME);
 	}
-END
+END2 }
 
-FORM (Table_to_TableOfReal, L"Table: Down to TableOfReal", 0)
+FORM (Table_to_TableOfReal, L"Table: Down to TableOfReal", 0) {
 	WORD (L"Column for row labels", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (Table);
@@ -1083,33 +1093,35 @@ DO
 		autoTableOfReal thee = Table_to_TableOfReal (me, icol);
 		praat_new (thee.transfer(), NAME);
 	}
-END
+END2 }
 
-DIRECT (Table_transpose)
+DIRECT2 (Table_transpose) {
 	LOOP {
 		iam (Table);
 		autoTable thee = Table_transpose (me);
 		praat_new (thee.transfer(), NAME, L"_transposed");
 	}
-END
+END2 }
 
-FORM_WRITE (Table_writeToCommaSeparatedFile, L"Save Table as comma-separated file", 0, L"Table")
+FORM_WRITE2 (Table_writeToCommaSeparatedFile, L"Save Table as comma-separated file", 0, L"Table") {
 	LOOP {
 		iam (Table);
 		Table_writeToCommaSeparatedFile (me, file);
 	}
-END
+END2 }
 
-FORM_WRITE (Table_writeToTabSeparatedFile, L"Save Table as tab-separated file", 0, L"Table")
+FORM_WRITE2 (Table_writeToTabSeparatedFile, L"Save Table as tab-separated file", 0, L"Table") {
 	LOOP {
 		iam (Table);
 		Table_writeToTabSeparatedFile (me, file);
 	}
-END
+END2 }
 
 /***** TABLEOFREAL *****/
+#pragma mark -
+#pragma mark TABLEOFREAL
 
-DIRECT (TablesOfReal_append)
+DIRECT2 (TablesOfReal_append) {
 	autoCollection tables = Collection_create (classTableOfReal, 10);
 	Collection_dontOwnItems (tables.peek());
 	LOOP {
@@ -1118,19 +1130,19 @@ DIRECT (TablesOfReal_append)
 	}
 	autoTableOfReal thee = static_cast <TableOfReal> (TablesOfReal_appendMany (tables.peek()));
 	praat_new (thee.transfer(), L"appended");
-END
+END2 }
 
-FORM (TableOfReal_create, L"Create TableOfReal", 0)
+FORM (TableOfReal_create, L"Create TableOfReal", 0) {
 	WORD (L"Name", L"table")
 	NATURAL (L"Number of rows", L"10")
 	NATURAL (L"Number of columns", L"3")
-	OK
+	OK2
 DO
 	autoTableOfReal me = TableOfReal_create (GET_INTEGER (L"Number of rows"), GET_INTEGER (L"Number of columns"));
 	praat_new (me.transfer(), GET_STRING (L"Name"));
-END
+END2 }
 
-FORM (TableOfReal_drawAsNumbers, L"Draw as numbers", 0)
+FORM (TableOfReal_drawAsNumbers, L"Draw as numbers", 0) {
 	NATURAL (L"From row", L"1")
 	INTEGER (L"To row", L"0 (= all)")
 	RADIO (L"Format", 3)
@@ -1139,7 +1151,7 @@ FORM (TableOfReal_drawAsNumbers, L"Draw as numbers", 0)
 		RADIOBUTTON (L"free")
 		RADIOBUTTON (L"rational")
 	NATURAL (L"Precision", L"5")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -1148,9 +1160,9 @@ DO
 			GET_INTEGER (L"From row"), GET_INTEGER (L"To row"),
 			GET_INTEGER (L"Format"), GET_INTEGER (L"Precision"));
 	}
-END
+END2 }
 
-FORM (TableOfReal_drawAsNumbers_if, L"Draw as numbers if...", 0)
+FORM (TableOfReal_drawAsNumbers_if, L"Draw as numbers if...", 0) {
 	NATURAL (L"From row", L"1")
 	INTEGER (L"To row", L"0 (= all)")
 	RADIO (L"Format", 3)
@@ -1161,7 +1173,7 @@ FORM (TableOfReal_drawAsNumbers_if, L"Draw as numbers if...", 0)
 	NATURAL (L"Precision", L"5")
 	LABEL (L"", L"Condition:")
 	TEXTFIELD (L"condition", L"self <> 0")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -1170,15 +1182,15 @@ DO
 			GET_INTEGER (L"From row"), GET_INTEGER (L"To row"),
 			GET_INTEGER (L"Format"), GET_INTEGER (L"Precision"), GET_STRING (L"condition"), interpreter);
 	}
-END
+END2 }
 
-FORM (TableOfReal_drawAsSquares, L"Draw table as squares", 0)
+FORM (TableOfReal_drawAsSquares, L"Draw table as squares", 0) {
 	INTEGER (L"From row", L"1")
 	INTEGER (L"To row", L"0")
 	INTEGER (L"From column", L"1")
 	INTEGER (L"To column", L"0")
 	BOOLEAN (L"Garnish", 1)
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
@@ -1187,94 +1199,94 @@ DO
 			GET_INTEGER (L"From row"), GET_INTEGER (L"To row"),
 			GET_INTEGER (L"From column"), GET_INTEGER (L"To column"),
 			GET_INTEGER (L"Garnish"));
-		}
-END
+	}
+END2 }
 
-FORM (TableOfReal_drawHorizontalLines, L"Draw horizontal lines", 0)
+FORM (TableOfReal_drawHorizontalLines, L"Draw horizontal lines", 0) {
 	NATURAL (L"From row", L"1")
 	INTEGER (L"To row", L"0 (= all)")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_drawHorizontalLines (me, GRAPHICS, GET_INTEGER (L"From row"), GET_INTEGER (L"To row"));
 	}
-END
+END2 }
 
-FORM (TableOfReal_drawLeftAndRightLines, L"Draw left and right lines", 0)
+FORM (TableOfReal_drawLeftAndRightLines, L"Draw left and right lines", 0) {
 	NATURAL (L"From row", L"1")
 	INTEGER (L"To row", L"0 (= all)")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_drawLeftAndRightLines (me, GRAPHICS, GET_INTEGER (L"From row"), GET_INTEGER (L"To row"));
 	}
-END
+END2 }
 
-FORM (TableOfReal_drawTopAndBottomLines, L"Draw top and bottom lines", 0)
+FORM (TableOfReal_drawTopAndBottomLines, L"Draw top and bottom lines", 0) {
 	NATURAL (L"From row", L"1")
 	INTEGER (L"To row", L"0 (= all)")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_drawTopAndBottomLines (me, GRAPHICS, GET_INTEGER (L"From row"), GET_INTEGER (L"To row"));
 	}
-END
+END2 }
 
-FORM (TableOfReal_drawVerticalLines, L"Draw vertical lines", 0)
+FORM (TableOfReal_drawVerticalLines, L"Draw vertical lines", 0) {
 	NATURAL (L"From row", L"1")
 	INTEGER (L"To row", L"0 (= all)")
-	OK
+	OK2
 DO
 	autoPraatPicture picture;
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_drawVerticalLines (me, GRAPHICS, GET_INTEGER (L"From row"), GET_INTEGER (L"To row"));
 	}
-END
+END2 }
 
-DIRECT (TableOfReal_extractColumnLabelsAsStrings)
+DIRECT2 (TableOfReal_extractColumnLabelsAsStrings) {
 	LOOP {
 		iam (TableOfReal);
 		autoStrings thee = TableOfReal_extractColumnLabelsAsStrings (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractColumnRanges, L"Extract column ranges", 0)
+FORM (TableOfReal_extractColumnRanges, L"Extract column ranges", 0) {
 	LABEL (L"", L"Create a new TableOfReal from the following columns:")
 	TEXTFIELD (L"ranges", L"1 2")
 	LABEL (L"", L"To supply rising or falling ranges, use e.g. 2:6 or 5:3.")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		autoTableOfReal thee = TableOfReal_extractColumnRanges (me, GET_STRING (L"ranges"));
 		praat_new (thee.transfer(), my name, L"_cols");
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractColumnsWhere, L"Extract columns where", 0)
+FORM (TableOfReal_extractColumnsWhere, L"Extract columns where", 0) {
 	LABEL (L"", L"Extract all columns with at least one cell where:")
 	TEXTFIELD (L"condition", L"col mod 3 = 0 ; this example extracts every third column")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		autoTableOfReal thee = TableOfReal_extractColumnsWhere (me, GET_STRING (L"condition"), interpreter);
 		praat_new (thee.transfer(), my name, L"_cols");
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractColumnsWhereLabel, L"Extract column where label", 0)
+FORM (TableOfReal_extractColumnsWhereLabel, L"Extract column where label", 0) {
 	OPTIONMENU_ENUM (L"Extract all columns whose label...", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"a")
-	OK
+	OK2
 DO
 	const wchar_t *text = GET_STRING (L"...the text");
 	LOOP {
@@ -1282,13 +1294,13 @@ DO
 		autoTableOfReal thee = TableOfReal_extractColumnsWhereLabel (me, GET_ENUM (kMelder_string, L"Extract all columns whose label..."), text);
 		praat_new (thee.transfer(), my name, L"_", text);
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractColumnsWhereRow, L"Extract columns where row", 0)
+FORM (TableOfReal_extractColumnsWhereRow, L"Extract columns where row", 0) {
 	NATURAL (L"Extract all columns where row...", L"1")
 	OPTIONMENU_ENUM (L"...is...", kMelder_number, DEFAULT)
 	REAL (L"...the value", L"0.0")
-	OK
+	OK2
 DO
 	long row = GET_INTEGER (L"Extract all columns where row...");
 	double value = GET_REAL (L"...the value");
@@ -1297,46 +1309,46 @@ DO
 		autoTableOfReal thee = TableOfReal_extractColumnsWhereRow (me, row, GET_ENUM (kMelder_number, L"...is..."), value);
 		praat_new (thee.transfer(), my name, L"_", Melder_integer (row), L"_", Melder_integer (round (value)));
 	}
-END
+END2 }
 
-DIRECT (TableOfReal_extractRowLabelsAsStrings)
+DIRECT2 (TableOfReal_extractRowLabelsAsStrings) {
 	LOOP {
 		iam (TableOfReal);
 		autoStrings thee = TableOfReal_extractRowLabelsAsStrings (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractRowRanges, L"Extract row ranges", 0)
+FORM (TableOfReal_extractRowRanges, L"Extract row ranges", 0) {
 	LABEL (L"", L"Create a new TableOfReal from the following rows:")
 	TEXTFIELD (L"ranges", L"1 2")
 	LABEL (L"", L"To supply rising or falling ranges, use e.g. 2:6 or 5:3.")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		autoTableOfReal thee = TableOfReal_extractRowRanges (me, GET_STRING (L"ranges"));
 		praat_new (thee.transfer(), my name, L"_rows");
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractRowsWhere, L"Extract rows where", 0)
+FORM (TableOfReal_extractRowsWhere, L"Extract rows where", 0) {
 	LABEL (L"", L"Extract all rows with at least one cell where:")
 	TEXTFIELD (L"condition", L"row mod 3 = 0 ; this example extracts every third row")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		autoTableOfReal thee = TableOfReal_extractRowsWhere (me, GET_STRING (L"condition"), interpreter);
 		praat_new (thee.transfer(), my name, L"_rows");
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractRowsWhereColumn, L"Extract rows where column", 0)
+FORM (TableOfReal_extractRowsWhereColumn, L"Extract rows where column", 0) {
 	NATURAL (L"Extract all rows where column...", L"1")
 	OPTIONMENU_ENUM (L"...is...", kMelder_number, DEFAULT)
 	REAL (L"...the value", L"0.0")
-	OK
+	OK2
 DO
 	long column = GET_INTEGER (L"Extract all rows where column...");
 	double value = GET_REAL (L"...the value");
@@ -1346,12 +1358,12 @@ DO
 			column, GET_ENUM (kMelder_number, L"...is..."), value);
 		praat_new (thee.transfer(), my name, L"_", Melder_integer (column), L"_", Melder_integer (round (value)));
 	}
-END
+END2 }
 
-FORM (TableOfReal_extractRowsWhereLabel, L"Extract rows where label", 0)
+FORM (TableOfReal_extractRowsWhereLabel, L"Extract rows where label", 0) {
 	OPTIONMENU_ENUM (L"Extract all rows whose label...", kMelder_string, DEFAULT)
 	SENTENCE (L"...the text", L"a")
-	OK
+	OK2
 DO
 	const wchar_t *text = GET_STRING (L"...the text");
 	LOOP {
@@ -1359,12 +1371,12 @@ DO
 		autoTableOfReal thee = TableOfReal_extractRowsWhereLabel (me, GET_ENUM (kMelder_string, L"Extract all rows whose label..."), text);
 		praat_new (thee.transfer(), my name, L"_", text);
 	}
-END
+END2 }
 
-FORM (TableOfReal_formula, L"TableOfReal: Formula", L"Formula...")
+FORM (TableOfReal_formula, L"TableOfReal: Formula", L"Formula...") {
 	LABEL (L"", L"for row from 1 to nrow do for col from 1 to ncol do self [row, col] = ...")
 	TEXTFIELD (L"formula", L"if col = 5 then self + self [6] else self fi")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1376,22 +1388,22 @@ DO
 			throw;
 		}
 	}
-END
+END2 }
 
-FORM (TableOfReal_getColumnIndex, L"Get column index", 0)
+FORM (TableOfReal_getColumnIndex, L"Get column index", 0) {
 	SENTENCE (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		long columnNumber = TableOfReal_columnLabelToIndex (me, GET_STRING (L"Column label"));
 		Melder_information (Melder_integer (columnNumber));
 	}
-END
+END2 }
 	
-FORM (TableOfReal_getColumnLabel, L"Get column label", 0)
+FORM (TableOfReal_getColumnLabel, L"Get column label", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1399,11 +1411,11 @@ DO
 		if (columnNumber > my numberOfColumns) Melder_throw (me, ": column number must not be greater than number of columns.");
 		Melder_information (my columnLabels == NULL ? L"" : my columnLabels [columnNumber]);
 	}
-END
+END2 }
 	
-FORM (TableOfReal_getColumnMean_index, L"Get column mean", 0)
+FORM (TableOfReal_getColumnMean_index, L"Get column mean", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1412,11 +1424,11 @@ DO
 		double columnMean = TableOfReal_getColumnMean (me, columnNumber);
 		Melder_informationReal (columnMean, NULL);
 	}
-END
+END2 }
 	
-FORM (TableOfReal_getColumnMean_label, L"Get column mean", 0)
+FORM (TableOfReal_getColumnMean_label, L"Get column mean", 0) {
 	SENTENCE (L"Column label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1425,11 +1437,11 @@ DO
 		double columnMean = TableOfReal_getColumnMean (me, columnNumber);
 		Melder_informationReal (columnMean, NULL);
 	}
-END
+END2 }
 	
-FORM (TableOfReal_getColumnStdev_index, L"Get column standard deviation", 0)
+FORM (TableOfReal_getColumnStdev_index, L"Get column standard deviation", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1438,11 +1450,11 @@ DO
 		double stdev = TableOfReal_getColumnStdev (me, columnNumber);
 		Melder_informationReal (stdev, NULL);
 	}
-END
+END2 }
 	
-FORM (TableOfReal_getColumnStdev_label, L"Get column standard deviation", 0)
+FORM (TableOfReal_getColumnStdev_label, L"Get column standard deviation", 0) {
 	SENTENCE (L"Column label", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1451,36 +1463,36 @@ DO
 		double stdev = TableOfReal_getColumnStdev (me, columnNumber);
 		Melder_informationReal (stdev, NULL);
 	}
-END
+END2 }
 
-DIRECT (TableOfReal_getNumberOfColumns)
+DIRECT2 (TableOfReal_getNumberOfColumns) {
 	LOOP {
 		iam (TableOfReal);
 		Melder_information (Melder_integer (my numberOfColumns));
 	}
-END
+END2 }
 
-DIRECT (TableOfReal_getNumberOfRows)
+DIRECT2 (TableOfReal_getNumberOfRows) {
 	LOOP {
 		iam (TableOfReal);
 		Melder_information (Melder_integer (my numberOfRows));
 	}
-END
+END2 }
 
-FORM (TableOfReal_getRowIndex, L"Get row index", 0)
+FORM (TableOfReal_getRowIndex, L"Get row index", 0) {
 	SENTENCE (L"Row label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		long rowNumber = TableOfReal_rowLabelToIndex (me, GET_STRING (L"Row label"));
 		Melder_information (Melder_integer (rowNumber));
 	}
-END
+END2 }
 	
-FORM (TableOfReal_getRowLabel, L"Get row label", 0)
+FORM (TableOfReal_getRowLabel, L"Get row label", 0) {
 	NATURAL (L"Row number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1488,12 +1500,12 @@ DO
 		if (rowNumber > my numberOfRows) Melder_throw (me, ": row number must not be greater than number of rows.");
 		Melder_information (my rowLabels == NULL ? L"" : my rowLabels [rowNumber]);
 	}
-END
+END2 }
 
-FORM (TableOfReal_getValue, L"Get value", 0)
+FORM (TableOfReal_getValue, L"Get value", 0) {
 	NATURAL (L"Row number", L"1")
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1502,74 +1514,74 @@ DO
 		if (columnNumber > my numberOfColumns) Melder_throw (me, ": column number must not exceed number of columns.");
 		Melder_informationReal (my data [rowNumber] [columnNumber], NULL);
 	}
-END
+END2 }
 
-DIRECT (TableOfReal_help) Melder_help (L"TableOfReal"); END
+DIRECT2 (TableOfReal_help) { Melder_help (L"TableOfReal"); END2 }
 
-FORM (TableOfReal_insertColumn, L"Insert column", 0)
+FORM (TableOfReal_insertColumn, L"Insert column", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_insertColumn (me, GET_INTEGER (L"Column number"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_insertRow, L"Insert row", 0)
+FORM (TableOfReal_insertRow, L"Insert row", 0) {
 	NATURAL (L"Row number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_insertRow (me, GET_INTEGER (L"Row number"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM_READ (TableOfReal_readFromHeaderlessSpreadsheetFile, L"Read TableOfReal from headerless spreadsheet file", 0, true)
+FORM_READ2 (TableOfReal_readFromHeaderlessSpreadsheetFile, L"Read TableOfReal from headerless spreadsheet file", 0, true) {
 	praat_newWithFile (TableOfReal_readFromHeaderlessSpreadsheetFile (file), MelderFile_name (file), file);
-END
+END2 }
 
-FORM (TableOfReal_removeColumn, L"Remove column", 0)
+FORM (TableOfReal_removeColumn, L"Remove column", 0) {
 	NATURAL (L"Column number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_removeColumn (me, GET_INTEGER (L"Column number"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_removeRow, L"Remove row", 0)
+FORM (TableOfReal_removeRow, L"Remove row", 0) {
 	NATURAL (L"Row number", L"1")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_removeRow (me, GET_INTEGER (L"Row number"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_setColumnLabel_index, L"Set column label", 0)
+FORM (TableOfReal_setColumnLabel_index, L"Set column label", 0) {
 	NATURAL (L"Column number", L"1")
 	SENTENCE (L"Label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_setColumnLabel (me, GET_INTEGER (L"Column number"), GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_setColumnLabel_label, L"Set column label", 0)
+FORM (TableOfReal_setColumnLabel_label, L"Set column label", 0) {
 	SENTENCE (L"Old label", L"")
 	SENTENCE (L"New label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1577,25 +1589,25 @@ DO
 		TableOfReal_setColumnLabel (me, columnNumber, GET_STRING (L"New label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_setRowLabel_index, L"Set row label", 0)
+FORM (TableOfReal_setRowLabel_index, L"Set row label", 0) {
 	NATURAL (L"Row number", L"1")
 	SENTENCE (L"Label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_setRowLabel (me, GET_INTEGER (L"Row number"), GET_STRING (L"Label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_setValue, L"Set value", L"TableOfReal: Set value...")
+FORM (TableOfReal_setValue, L"Set value", L"TableOfReal: Set value...") {
 	NATURAL (L"Row number", L"1")
 	NATURAL (L"Column number", L"1")
 	REAL_OR_UNDEFINED (L"New value", L"0.0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1605,12 +1617,12 @@ DO
 		my data [rowNumber] [columnNumber] = GET_REAL (L"New value");
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_setRowLabel_label, L"Set row label", 0)
+FORM (TableOfReal_setRowLabel_label, L"Set row label", 0) {
 	SENTENCE (L"Old label", L"")
 	SENTENCE (L"New label", L"")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
@@ -1618,61 +1630,61 @@ DO
 		TableOfReal_setRowLabel (me, rowNumber, GET_STRING (L"New label"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_sortByColumn, L"Sort rows by column", 0)
+FORM (TableOfReal_sortByColumn, L"Sort rows by column", 0) {
 	INTEGER (L"Column", L"1")
 	INTEGER (L"Secondary column", L"0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_sortByColumn (me, GET_INTEGER (L"Column"), GET_INTEGER (L"Secondary column"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-FORM (TableOfReal_sortByLabel, L"Sort rows by label", 0)
+FORM (TableOfReal_sortByLabel, L"Sort rows by label", 0) {
 	LABEL (L"", L"Secondary sorting keys:")
 	INTEGER (L"Column1", L"1")
 	INTEGER (L"Column2", L"0")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_sortByLabel (me, GET_INTEGER (L"Column1"), GET_INTEGER (L"Column2"));
 		praat_dataChanged (me);
 	}
-END
+END2 }
 
-DIRECT (TableOfReal_to_Matrix)
+DIRECT2 (TableOfReal_to_Matrix) {
 	LOOP {
 		iam (TableOfReal);
 		autoMatrix thee = TableOfReal_to_Matrix (me);
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM (TableOfReal_to_Table, L"TableOfReal: To Table", 0)
+FORM (TableOfReal_to_Table, L"TableOfReal: To Table", 0) {
 	SENTENCE (L"Label of first column", L"rowLabel")
-	OK
+	OK2
 DO
 	LOOP {
 		iam (TableOfReal);
 		autoTable thee = TableOfReal_to_Table (me, GET_STRING (L"Label of first column"));
 		praat_new (thee.transfer(), my name);
 	}
-END
+END2 }
 
-FORM_WRITE (TableOfReal_writeToHeaderlessSpreadsheetFile, L"Save TableOfReal as spreadsheet", 0, L"txt")
+FORM_WRITE2 (TableOfReal_writeToHeaderlessSpreadsheetFile, L"Save TableOfReal as spreadsheet", 0, L"txt") {
 	LOOP {
 		iam (TableOfReal);
 		TableOfReal_writeToHeaderlessSpreadsheetFile (me, file);
 	}
-END
+END2 }
 
 
-DIRECT (StatisticsTutorial) Melder_help (L"Statistics"); END
+DIRECT2 (StatisticsTutorial) { Melder_help (L"Statistics"); END2 }
 
 static bool isTabSeparated_8bit (int nread, const char *header) {
 	for (long i = 0; i < nread; i ++) {
diff --git a/sys/ButtonEditor.cpp b/sys/ButtonEditor.cpp
index b81e5bc..bea0c77 100644
--- a/sys/ButtonEditor.cpp
+++ b/sys/ButtonEditor.cpp
@@ -1,6 +1,6 @@
 /* ButtonEditor.cpp
  *
- * Copyright (C) 1996-2011,2013 Paul Boersma
+ * Copyright (C) 1996-2011,2013,2014 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
@@ -197,9 +197,8 @@ int structButtonEditor :: v_goToPage (const wchar_t *title) {
 			praat_Command action = praat_getAction (i);
 			if (! action || ! action -> callback) return 0;
 			if (action -> title) {
-				UiHistory_write (L"\ndo (\"");
-				UiHistory_write_expandQuotes (action -> title);
-				UiHistory_write (L"\")");
+				UiHistory_write (L"\n");
+				UiHistory_write_colonize (action -> title);
 			}
 			if (action -> script) {
 				try {
@@ -221,9 +220,8 @@ int structButtonEditor :: v_goToPage (const wchar_t *title) {
 			praat_Command menuCommand = praat_getMenuCommand (i);
 			if (! menuCommand || ! menuCommand -> callback) return 0;
 			if (menuCommand -> title) {
-				UiHistory_write (L"\ndo (\"");
-				UiHistory_write_expandQuotes (menuCommand -> title);
-				UiHistory_write (L"\")");
+				UiHistory_write (L"\n");
+				UiHistory_write_colonize (menuCommand -> title);
 			}
 			if (menuCommand -> script) {
 				try {
diff --git a/sys/Collection.cpp b/sys/Collection.cpp
index 5699cf7..2fcf47d 100644
--- a/sys/Collection.cpp
+++ b/sys/Collection.cpp
@@ -130,9 +130,9 @@ void structCollection :: v_readText (MelderReadText text) {
 					" while expecting ", i, ".");
 			if (stringsRead == 3 && ! strequ (nameTag, "name"))
 				Melder_throw ("Collection::readText: wrong header at object ", i, ".");
-			this -> item [i] = Thing_newFromClassNameA (klas);
+			our item [i] = Thing_newFromClassNameA (klas);
 			Thing_version = -1;   /* Override. */
-			this -> size ++;
+			our size ++;
 			if (! Thing_member ((Thing) item [i], classData) || ! Data_canReadText ((Data) item [i]))
 				Melder_throw ("Cannot read item of class ", Thing_className ((Thing) item [i]), " in collection.");
 			Data_readText ((Data) item [i], text);
@@ -149,8 +149,8 @@ void structCollection :: v_readText (MelderReadText text) {
 		for (long i = 1; i <= l_size; i ++) {
 			long saveVersion = Thing_version;   /* The version of the Collection... */
 			autostring8 className = texgets2 (text);
-			this -> item [i] = Thing_newFromClassNameA (className.peek());
-			this -> size ++;
+			our item [i] = Thing_newFromClassNameA (className.peek());
+			our size ++;
 			if (! Thing_member ((Thing) item [i], classData) || ! Data_canReadText ((Data) item [i]))
 				Melder_throw ("Cannot read item of class ", Thing_className ((Thing) item [i]), " in collection.");
 			autostring objectName = texgetw2 (text);
@@ -187,7 +187,7 @@ void structCollection :: v_readBinary (FILE *f) {
 				Melder_throw ("Cannot read class and name.");
 			item [i] = Thing_newFromClassNameA (klas);
 			Thing_version = -1;   /* Override. */
-			this -> size ++;
+			our size ++;
 			if (! Thing_member ((Thing) item [i], classData))
 				Melder_throw ("Cannot read item of class ", Thing_className ((Thing) item [i]), ".");
 			if (fgetc (f) != ' ')
@@ -206,7 +206,7 @@ void structCollection :: v_readBinary (FILE *f) {
 			if (Melder_debug == 44)
 				Melder_casual ("structCollection :: v_readBinary: Reading object of type %s", klas.peek());
 			item [i] = Thing_newFromClassNameA (klas.peek());
-			this -> size ++;
+			our size ++;
 			if (! Thing_member ((Thing) item [i], classData) || ! Data_canReadBinary ((Data) item [i]))
 				Melder_throw ("Objects of class ", Thing_className ((Thing) item [i]), " cannot be read.");
 			autostring name = bingetw2 (f);
diff --git a/sys/DataEditor.cpp b/sys/DataEditor.cpp
index 96b8e76..8d4e23c 100644
--- a/sys/DataEditor.cpp
+++ b/sys/DataEditor.cpp
@@ -117,8 +117,10 @@ static void gui_button_cb_change (I, GuiButtonEvent event) {
 		#elif gtk
 			gboolean visible;
 			g_object_get (G_OBJECT (my d_fieldData [irow]. text), "visible", & visible, NULL);
-		#elif ! useCarbon
-			bool visible = false;   // TODO
+		#elif defined (macintosh) && ! useCarbon
+			bool visible = ! [(GuiCocoaTextField *) my d_fieldData [irow]. text -> d_widget   isHidden];
+		#else
+			bool visible = false;
 		#endif
 		if (visible) {
 			int type = my d_fieldData [irow]. description -> type;
@@ -386,11 +388,11 @@ static void showStructMember (
 	if (type == inheritwa) {
 		MelderString_append (& buffer, L"Class part \"", memberDescription -> name, L"\":");
 	} else {
-		MelderString_append (& buffer, strip_d (memberDescription -> name),
+		MelderString_append (& buffer, L"   ", strip_d (memberDescription -> name),
 			rank == 0 ? L"" : rank == 1 || rank == 3 || rank < 0 ? L" [ ]" : L" [ ] [ ]");
 	}
 	fieldData -> label -> f_setString (buffer.string);
-	fieldData -> label -> f_move (type == inheritwa ? 0 : NAME_X, fieldData -> y);
+	//fieldData -> label -> f_move (type == inheritwa ? 0 : NAME_X, fieldData -> y);
 	fieldData -> label -> f_show ();
 
 	/* Show the value (for a single type) or a button (for a composite type). */
diff --git a/sys/DataEditor.h b/sys/DataEditor.h
index 667df8a..7bb632d 100644
--- a/sys/DataEditor.h
+++ b/sys/DataEditor.h
@@ -1,5 +1,5 @@
 #ifndef _DataEditor_h_
-#define _DataEditor_h
+#define _DataEditor_h_
 /* DataEditor.h
  *
  * Copyright (C) 1995-2011,2012 Paul Boersma
diff --git a/sys/DemoEditor.cpp b/sys/DemoEditor.cpp
index eed4904..54c018b 100644
--- a/sys/DemoEditor.cpp
+++ b/sys/DemoEditor.cpp
@@ -102,11 +102,10 @@ static void gui_drawingarea_cb_key (I, GuiDrawingAreaKeyEvent event) {
 static void gui_drawingarea_cb_resize (I, GuiDrawingAreaResizeEvent event) {
 	iam (DemoEditor);
 	if (my graphics == NULL) return;   // Could be the case in the very beginning.
-	int marginWidth = 0, marginHeight = 0;
 	trace ("%d %d", event -> width, event -> height);
-	Graphics_setWsViewport (my graphics, marginWidth, event -> width - marginWidth, marginHeight, event -> height - marginHeight);
+	Graphics_setWsViewport (my graphics, 0, event -> width, 0, event -> height);
 	Graphics_setWsWindow (my graphics, 0, 100, 0, 100);
-	Graphics_setViewport (my graphics, 0, 100, 0, 100);
+	//Graphics_setViewport (my graphics, 0, 100, 0, 100);
 	Graphics_updateWs (my graphics);
 }
 
@@ -123,14 +122,10 @@ void DemoEditor_init (DemoEditor me) {
 	Graphics_fillRectangle (my graphics, 0, 1, 0, 1);
 	Graphics_setColour (my graphics, Graphics_BLACK);
 	Graphics_startRecording (my graphics);
-	//Graphics_setViewport (my graphics, 0, 100, 0, 100);
-	//Graphics_setWindow (my graphics, 0, 100, 0, 100);
-	//Graphics_line (my graphics, 0, 100, 100, 0);
-
-struct structGuiDrawingAreaResizeEvent event = { my drawingArea, 0 };
-event. width  = my drawingArea -> f_getWidth  ();
-event. height = my drawingArea -> f_getHeight ();
-gui_drawingarea_cb_resize (me, & event);
+	Graphics_setWsViewport (my graphics, 0, my drawingArea -> f_getWidth (), 0, my drawingArea -> f_getHeight ());
+	Graphics_setWsWindow (my graphics, 0, 100, 0, 100);
+	Graphics_setViewport (my graphics, 0, 100, 0, 100);
+	Graphics_updateWs (my graphics);
 }
 
 DemoEditor DemoEditor_create () {
@@ -164,6 +159,7 @@ void Demo_open (void) {
 			theCurrentPraatPicture -> colour = Graphics_BLACK;
 			theCurrentPraatPicture -> lineWidth = 1.0;
 			theCurrentPraatPicture -> arrowSize = 1.0;
+			theCurrentPraatPicture -> speckleSize = 1.0;
 			theCurrentPraatPicture -> x1NDC = 0;
 			theCurrentPraatPicture -> x2NDC = 100;
 			theCurrentPraatPicture -> y1NDC = 0;
@@ -271,9 +267,13 @@ double Demo_x (void) {
 		Melder_throw ("You cannot work with the Demo window while it is waiting for input. "
 			"Please click or type into the Demo window or close it.");
 	}
+	trace ("NDC before: %f %f", theDemoEditor -> graphics -> d_x1NDC, theDemoEditor -> graphics -> d_x2NDC);
 	Graphics_setInner (theDemoEditor -> graphics);
+	trace ("NDC after: %f %f", theDemoEditor -> graphics -> d_x1NDC, theDemoEditor -> graphics -> d_x2NDC);
 	double xWC, yWC;
+	trace ("DC: x %ld, y %ld", theDemoEditor -> x, theDemoEditor -> y);
 	Graphics_DCtoWC (theDemoEditor -> graphics, theDemoEditor -> x, theDemoEditor -> y, & xWC, & yWC);
+	trace ("WC: x %f, y %f", xWC, yWC);
 	Graphics_unsetInner (theDemoEditor -> graphics);
 	return xWC;
 }
diff --git a/sys/Editor.cpp b/sys/Editor.cpp
index 2fcbcd0..ed5b092 100644
--- a/sys/Editor.cpp
+++ b/sys/Editor.cpp
@@ -1,6 +1,6 @@
 /* Editor.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma, 2008 Stefan de Konink, 2010 Franz Brausse
+ * Copyright (C) 1992-2012,2013,2014 Paul Boersma, 2008 Stefan de Konink, 2010 Franz Brausse
  *
  * 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
@@ -22,6 +22,7 @@
 #include "machine.h"
 #include "EditorM.h"
 #include "praat_script.h"
+#include "sendsocket.h"
 
 #include "enums_getText.h"
 #include "Editor_enums.h"
@@ -63,14 +64,15 @@ void structEditorMenu :: v_destroy () {
 static void commonCallback (GUI_ARGS) {
 	GUI_IAM (EditorCommand);
 	if (my d_editor && my d_editor -> v_scriptable () && ! wcsstr (my itemTitle, L"...")) {
-		UiHistory_write (L"\ndo (\"");
-		UiHistory_write_expandQuotes (my itemTitle);
-		UiHistory_write (L"\")");
+		UiHistory_write (L"\n");
+		UiHistory_write_colonize (my itemTitle);
 	}
 	try {
 		my commandCallback (my d_editor, me, NULL, 0, NULL, NULL, NULL);
 	} catch (MelderError) {
-		Melder_error_ ("Menu command \"", my itemTitle, "\" not completed.");
+		if (! Melder_hasError (L"Script exited.")) {
+			Melder_error_ ("Menu command \"", my itemTitle, "\" not completed.");
+		}
 		Melder_flushError (NULL);
 	}
 }
@@ -135,34 +137,35 @@ static void Editor_scriptCallback (Editor me, EditorCommand cmd, UiForm sendingF
 GuiMenuItem Editor_addCommandScript (Editor me, const wchar_t *menuTitle, const wchar_t *itemTitle, long flags,
 	const wchar_t *script)
 {
-	try {
-		long numberOfMenus = my menus -> size;
-		for (long imenu = 1; imenu <= numberOfMenus; imenu ++) {
-			EditorMenu menu = (EditorMenu) my menus -> item [imenu];
-			if (wcsequ (menuTitle, menu -> menuTitle)) {
-				autoEditorCommand cmd = Thing_new (EditorCommand);
-				cmd -> d_editor = me;
-				cmd -> menu = menu;
-				cmd -> itemTitle = Melder_wcsdup_f (itemTitle);
-				cmd -> itemWidget = script == NULL ? GuiMenu_addSeparator (menu -> menuWidget) :
-					GuiMenu_addItem (menu -> menuWidget, itemTitle, flags, commonCallback, cmd.peek());   // DANGLE BUG
-				cmd -> commandCallback = Editor_scriptCallback;
-				if (wcslen (script) == 0) {
-					cmd -> script = Melder_wcsdup_f (L"");
-				} else {
-					structMelderFile file = { 0 };
-					Melder_relativePathToFile (script, & file);
-					cmd -> script = Melder_wcsdup_f (Melder_fileToPath (& file));
-				}
-				GuiMenuItem result = cmd -> itemWidget;
-				Collection_addItem (menu -> commands, cmd.transfer());
-				return result;
+	long numberOfMenus = my menus -> size;
+	for (long imenu = 1; imenu <= numberOfMenus; imenu ++) {
+		EditorMenu menu = (EditorMenu) my menus -> item [imenu];
+		if (wcsequ (menuTitle, menu -> menuTitle)) {
+			autoEditorCommand cmd = Thing_new (EditorCommand);
+			cmd -> d_editor = me;
+			cmd -> menu = menu;
+			cmd -> itemTitle = Melder_wcsdup_f (itemTitle);
+			cmd -> itemWidget = script == NULL ? GuiMenu_addSeparator (menu -> menuWidget) :
+				GuiMenu_addItem (menu -> menuWidget, itemTitle, flags, commonCallback, cmd.peek());   // DANGLE BUG
+			cmd -> commandCallback = Editor_scriptCallback;
+			if (wcslen (script) == 0) {
+				cmd -> script = Melder_wcsdup_f (L"");
+			} else {
+				structMelderFile file = { 0 };
+				Melder_relativePathToFile (script, & file);
+				cmd -> script = Melder_wcsdup_f (Melder_fileToPath (& file));
 			}
+			GuiMenuItem result = cmd -> itemWidget;
+			Collection_addItem (menu -> commands, cmd.transfer());
+			return result;
 		}
-		Melder_throw ("Menu \"", menuTitle, L"\" does not exist.");
-	} catch (MelderError) {
-		Melder_throw ("Command \"", itemTitle, "\" not inserted in menu \"", menuTitle, ".");
 	}
+	Melder_warning (
+		"Menu \"", menuTitle, L"\" does not exist.\n"
+		"Command \"", itemTitle, "\" not inserted in menu \"", menuTitle, ".\n"
+		"To fix this, go to Praat->Preferences->Buttons->Editors, and remove the script from this menu.\n"
+		"You may want to install the script in a different menu.");
+	return NULL;
 }
 
 void Editor_setMenuSensitive (Editor me, const wchar_t *menuTitle, int sensitive) {
@@ -217,28 +220,29 @@ void structEditor :: v_destroy () {
 	 * The following command must be performed before the shell is destroyed.
 	 * Otherwise, we would be forgetting dangling command dialogs here.
 	 */
-	forget (menus);
+	forget (our menus);
 	broadcastDestruction ();
-	if (d_windowForm) {
+	if (our d_windowForm) {
 		#if gtk
-			if (d_windowForm -> d_gtkWindow) {
-				Melder_assert (GTK_IS_WIDGET (d_windowForm -> d_gtkWindow));
-				gtk_widget_destroy (GTK_WIDGET (d_windowForm -> d_gtkWindow));
+			if (our d_windowForm -> d_gtkWindow) {
+				Melder_assert (GTK_IS_WIDGET (our d_windowForm -> d_gtkWindow));
+				gtk_widget_destroy (GTK_WIDGET (our d_windowForm -> d_gtkWindow));
 			}
 		#elif cocoa
-			if (d_windowForm -> d_cocoaWindow) {
-				NSWindow *cocoaWindow = d_windowForm -> d_cocoaWindow;
+			if (our d_windowForm -> d_cocoaWindow) {
+				NSWindow *cocoaWindow = our d_windowForm -> d_cocoaWindow;
 				//d_windowForm -> d_cocoaWindow = NULL;
 				[cocoaWindow close];
 			}
 		#elif motif
-			if (d_windowForm -> d_xmShell) {
-				XtDestroyWidget (d_windowForm -> d_xmShell);
+			if (our d_windowForm -> d_xmShell) {
+				XtDestroyWidget (our d_windowForm -> d_xmShell);
 			}
 		#endif
 	}
-	forget (previousData);
-	if (d_ownData) forget (data);
+	forget (our previousData);
+	if (our d_ownData) forget (our data);
+	Melder_free (our callbackSocket);
 	Editor_Parent :: v_destroy ();
 }
 
@@ -269,6 +273,18 @@ void structEditor :: v_restoreData () {
 		Thing_swap (data, previousData);
 }
 
+static void menu_cb_sendBackToCallingProgram (EDITOR_ARGS) {
+	EDITOR_IAM (Editor);
+	if (my data) {
+		extern structMelderDir praatDir;
+		structMelderFile file;
+		MelderDir_getFile (& praatDir, L"praat_backToCaller.Data", & file);
+		Data_writeToBinaryFile (my data, & file);
+		sendsocket (my callbackSocket, NULL);
+	}
+	my v_goAway ();
+}
+
 static void menu_cb_close (EDITOR_ARGS) {
 	EDITOR_IAM (Editor);
 	my v_goAway ();
@@ -282,6 +298,8 @@ static void menu_cb_undo (EDITOR_ARGS) {
 	else wcscpy (my undoText, L"Undo?");
 	#if gtk
 		gtk_label_set_label (GTK_LABEL (gtk_bin_get_child (GTK_BIN (my undoButton -> d_widget))), Melder_peekWcsToUtf8 (my undoText));
+	#elif cocoa
+		[(GuiCocoaMenuItem *) my undoButton -> d_widget   setTitle: (NSString *) Melder_peekWcsToCfstring (my undoText)];
 	#elif motif
 		char *text_utf8 = Melder_peekWcsToUtf8 (my undoText);
 		XtVaSetValues (my undoButton -> d_widget, XmNlabelString, text_utf8, NULL);
@@ -457,7 +475,7 @@ void Editor_init (Editor me, int x, int y, int width, int height, const wchar_t
 		top += Machine_getTitleBarHeight ();
 		bottom += Machine_getTitleBarHeight ();
 	#endif
-	my d_windowForm = GuiWindow_create (left, top, width, height, title, gui_window_cb_goAway, me, my v_canFullScreen () ? GuiWindow_FULLSCREEN : 0);
+	my d_windowForm = GuiWindow_create (left, top, width, height, 450, 250, title, gui_window_cb_goAway, me, my v_canFullScreen () ? GuiWindow_FULLSCREEN : 0);
 	Thing_setName (me, title);
 	my data = data;
 	my v_copyPreferencesToInstance ();
@@ -467,6 +485,11 @@ void Editor_init (Editor me, int x, int y, int width, int height, const wchar_t
 	if (my v_hasMenuBar ()) {
 		my menus = Ordered_create ();
 		my d_windowForm -> f_addMenuBar ();
+	}
+
+	my v_createChildren ();
+
+	if (my v_hasMenuBar ()) {
 		my v_createMenus ();
 		EditorMenu helpMenu = Editor_addMenu (me, L"Help", 0);
 		my v_createHelpMenuItems (helpMenu);
@@ -481,10 +504,10 @@ void Editor_init (Editor me, int x, int y, int width, int height, const wchar_t
 		 * Add the scripted commands.
 		 */
 		praat_addCommandsToEditor (me);
+		if (my callbackSocket)
+			Editor_addCommand (me, L"File", L"Send back to calling program", 0, menu_cb_sendBackToCallingProgram);
 		Editor_addCommand (me, L"File", L"Close", 'W', menu_cb_close);
 	}
-
-	my v_createChildren ();
 	my d_windowForm -> f_show ();
 }
 
@@ -495,6 +518,8 @@ void Editor_save (Editor me, const wchar_t *text) {
 	swprintf (my undoText, 100, L"Undo %ls", text);
 	#if gtk
 		gtk_label_set_label (GTK_LABEL (gtk_bin_get_child (GTK_BIN (my undoButton -> d_widget))), Melder_peekWcsToUtf8 (my undoText));
+	#elif cocoa
+		[(GuiCocoaMenuItem *) my undoButton -> d_widget   setTitle: (NSString *) Melder_peekWcsToCfstring (my undoText)];
 	#elif motif
 		char *text_utf8 = Melder_peekWcsToUtf8 (my undoText);
 		XtVaSetValues (my undoButton -> d_widget, XmNlabelString, text_utf8, NULL);
diff --git a/sys/Editor.h b/sys/Editor.h
index b381957..4521723 100644
--- a/sys/Editor.h
+++ b/sys/Editor.h
@@ -2,7 +2,7 @@
 #define _Editor_h_
 /* Editor.h
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013.2014 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
@@ -68,6 +68,7 @@ Thing_define (Editor, Thing) {
 		void (*d_dataChangedCallback) (Editor me, void *closure);                   void *d_dataChangedClosure;
 		void (*d_destructionCallback) (Editor me, void *closure);                   void *d_destructionClosure;
 		void (*d_publicationCallback) (Editor me, void *closure, Data publication); void *d_publicationClosure;
+		const char *callbackSocket;
 
 	// new messages:
 	public:
diff --git a/sys/Formula.cpp b/sys/Formula.cpp
index a1b618b..94e1461 100644
--- a/sys/Formula.cpp
+++ b/sys/Formula.cpp
@@ -1,6 +1,6 @@
 /* Formula.cpp
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -29,14 +29,19 @@
 #include "Interpreter.h"
 #include "Ui.h"
 #include "praatP.h"
+#include "praat_script.h"
 #include "UnicodeData.h"
+#include "longchar.h"
 #include "UiPause.h"
 #include "DemoEditor.h"
 
 static Interpreter theInterpreter, theLocalInterpreter;
 static Data theSource;
 static const wchar_t *theExpression;
-static int theExpressionType, theOptimize;
+static int theLevel = 1;
+#define MAXIMUM_NUMBER_OF_LEVELS  20
+static int theExpressionType [1 + MAXIMUM_NUMBER_OF_LEVELS];
+static int theOptimize;
 
 static struct Formula_NumericArray theZeroNumericArray = { 0, 0, NULL };
 
@@ -62,7 +67,7 @@ enum { GEENSYMBOOL_,
 /* The list ends with "MINUS_" itself. */
 
 	/* Haakjes-openen. */
-	IF_, THEN_, ELSE_, HAAKJEOPENEN_, RECHTEHAAKOPENEN_, KOMMA_, FROM_, TO_,
+	IF_, THEN_, ELSE_, HAAKJEOPENEN_, RECHTEHAAKOPENEN_, KOMMA_, COLON_, FROM_, TO_,
 	/* Operatoren met boolean resultaat. */
 	OR_, AND_, NOT_, EQ_, NE_, LE_, LT_, GE_, GT_,
 	/* Operatoren met reeel resultaat. */
@@ -101,7 +106,7 @@ enum { GEENSYMBOOL_,
 
 	/* Functions of 2 variables; if you add, update the #defines. */
 	#define LOW_FUNCTION_2  ARCTAN2_
-		ARCTAN2_, RANDOM_UNIFORM_, RANDOM_INTEGER_, RANDOM_GAUSS_,
+		ARCTAN2_, RANDOM_UNIFORM_, RANDOM_INTEGER_, RANDOM_GAUSS_, RANDOM_BINOMIAL_,
 		CHI_SQUARE_P_, CHI_SQUARE_Q_, INCOMPLETE_GAMMAP_,
 		INV_CHI_SQUARE_Q_, STUDENT_P_, STUDENT_Q_, INV_STUDENT_Q_,
 		BETA_, BETA2_, BESSEL_I_, BESSEL_K_, LN_BETA_,
@@ -119,6 +124,7 @@ enum { GEENSYMBOOL_,
 		DO_, DOSTR_,
 		WRITE_INFO_, WRITE_INFO_LINE_, APPEND_INFO_, APPEND_INFO_LINE_,
 		WRITE_FILE_, WRITE_FILE_LINE_, APPEND_FILE_, APPEND_FILE_LINE_,
+		PAUSE_SCRIPT_, EXIT_SCRIPT_, RUN_SCRIPT_,
 		MIN_, MAX_, IMIN_, IMAX_,
 		LEFTSTR_, RIGHTSTR_, MIDSTR_,
 		SELECTED_, SELECTEDSTR_, NUMBER_OF_SELECTED_, SELECT_OBJECT_, PLUS_OBJECT_, MINUS_OBJECT_, REMOVE_OBJECT_,
@@ -131,17 +137,17 @@ enum { GEENSYMBOOL_,
 		DEMO_CLICKED_, DEMO_X_, DEMO_Y_, DEMO_KEY_PRESSED_, DEMO_KEY_,
 		DEMO_SHIFT_KEY_PRESSED_, DEMO_COMMAND_KEY_PRESSED_, DEMO_OPTION_KEY_PRESSED_, DEMO_EXTRA_CONTROL_KEY_PRESSED_,
 		ZERO_NUMAR_, LINEAR_NUMAR_, RANDOM_UNIFORM_NUMAR_, RANDOM_INTEGER_NUMAR_, RANDOM_GAUSS_NUMAR_,
-		NUMBER_OF_ROWS_, NUMBER_OF_COLUMNS_,
-	#define HIGH_FUNCTION_N  NUMBER_OF_COLUMNS_
+		NUMBER_OF_ROWS_, NUMBER_OF_COLUMNS_, EDITOR_,
+	#define HIGH_FUNCTION_N  EDITOR_
 
 	/* String functions. */
 	#define LOW_STRING_FUNCTION  LOW_FUNCTION_STR1
 	#define LOW_FUNCTION_STR1  LENGTH_
 		LENGTH_, STRING_TO_NUMBER_, FILE_READABLE_, DELETE_FILE_, CREATE_DIRECTORY_, VARIABLE_EXISTS_,
-		READ_FILE_, READ_FILESTR_,
-	#define HIGH_FUNCTION_STR1  READ_FILESTR_
+		READ_FILE_, READ_FILESTR_, UNICODE_TO_BACKSLASH_TRIGRAPHS_, BACKSLASH_TRIGRAPHS_TO_UNICODE_, ENVIRONMENTSTR_,
+	#define HIGH_FUNCTION_STR1  ENVIRONMENTSTR_
 		DATESTR_, INFOSTR_,
-		ENVIRONMENTSTR_, INDEX_, RINDEX_,
+		INDEX_, RINDEX_,
 		STARTS_WITH_, ENDS_WITH_, REPLACESTR_, INDEX_REGEX_, RINDEX_REGEX_, REPLACE_REGEXSTR_,
 		EXTRACT_NUMBER_, EXTRACT_WORDSTR_, EXTRACT_LINESTR_,
 		FIXEDSTR_, PERCENTSTR_,
@@ -188,7 +194,7 @@ enum { GEENSYMBOOL_,
 /* they are used in error messages and in debugging (see Formula_print). */
 
 static const wchar_t *Formula_instructionNames [1 + hoogsteSymbool] = { L"",
-	L"if", L"then", L"else", L"(", L"[", L",", L"from", L"to",
+	L"if", L"then", L"else", L"(", L"[", L",", L":", L"from", L"to",
 	L"or", L"and", L"not", L"=", L"<>", L"<=", L"<", L">=", L">",
 	L"+", L"-", L"*", L"/", L"div", L"mod", L"^", L"_call", L"_neg",
 	L"endif", L"fi", L")", L"]",
@@ -205,7 +211,7 @@ static const wchar_t *Formula_instructionNames [1 + hoogsteSymbool] = { L"",
 	L"hertzToMel", L"melToHertz", L"hertzToSemitones", L"semitonesToHertz",
 	L"erb", L"hertzToErb", L"erbToHertz",
 	L"string$",
-	L"arctan2", L"randomUniform", L"randomInteger", L"randomGauss",
+	L"arctan2", L"randomUniform", L"randomInteger", L"randomGauss", L"randomBinomial",
 	L"chiSquareP", L"chiSquareQ", L"incompleteGammaP", L"invChiSquareQ", L"studentP", L"studentQ", L"invStudentQ",
 	L"beta", L"beta2", L"besselI", L"besselK", L"lnBeta",
 	L"soundPressureToPhon", L"objectsAreIdentical",
@@ -215,6 +221,7 @@ static const wchar_t *Formula_instructionNames [1 + hoogsteSymbool] = { L"",
 	L"do", L"do$",
 	L"writeInfo", L"writeInfoLine", L"appendInfo", L"appendInfoLine",
 	L"writeFile", L"writeFileLine", L"appendFile", L"appendFileLine",
+	L"pauseScript", L"exitScript", L"runScript",
 	L"min", L"max", L"imin", L"imax",
 	L"left$", L"right$", L"mid$",
 	L"selected", L"selected$", L"numberOfSelected", L"selectObject", L"plusObject", L"minusObject", L"removeObject",
@@ -227,12 +234,12 @@ static const wchar_t *Formula_instructionNames [1 + hoogsteSymbool] = { L"",
 	L"demoClicked", L"demoX", L"demoY", L"demoKeyPressed", L"demoKey$",
 	L"demoShiftKeyPressed", L"demoCommandKeyPressed", L"demoOptionKeyPressed", L"demoExtraControlKeyPressed",
 	L"zero#", L"linear#", L"randomUniform#", L"randomInteger#", L"randomGauss#",
-	L"numberOfRows", L"numberOfColumns",
+	L"numberOfRows", L"numberOfColumns", L"editor",
 
 	L"length", L"number", L"fileReadable",	L"deleteFile", L"createDirectory", L"variableExists",
-	L"readFile", L"readFile$",
+	L"readFile", L"readFile$", L"unicodeToBackslashTrigraphs$", L"backslashTrigraphsToUnicode$", L"environment$",
 	L"date$", L"info$",
-	L"environment$", L"index", L"rindex",
+	L"index", L"rindex",
 	L"startsWith", L"endsWith", L"replace$", L"index_regex", L"rindex_regex", L"replace_regex$",
 	L"extractNumber", L"extractWord$", L"extractLine$",
 	L"fixed$", L"percent$",
@@ -333,7 +340,7 @@ static void Formula_lexan (void) {
 	do {
 		nieuwkar;
 		if (kar == ' ' || kar == '\t') {
-			;   /* Ignore spaces and tabs. */
+			;   // ignore spaces and tabs
 		} else if (kar == '\0') {
 			nieuwtok (END_)
 		} else if (kar >= '0' && kar <= '9') {
@@ -403,13 +410,13 @@ static void Formula_lexan (void) {
 				 */
 				} else if (tok >= LOW_FUNCTION && tok <= HIGH_FUNCTION) {
 					/*
-					 * Look ahead to find out whether the next token is a left parenthesis.
+					 * Look ahead to find out whether the next token is a left parenthesis (or a colon).
 					 */
 					int jkar;
 					jkar = ikar + 1;
 					while (theExpression [jkar] == ' ' || theExpression [jkar] == '\t') jkar ++;
-					if (theExpression [jkar] == '(') {
-						nieuwtok (tok)   /* This must be a function name. */
+					if (theExpression [jkar] == '(' || theExpression [jkar] == ':') {
+						nieuwtok (tok)   // this must be a function name
 					} else {
 						/*
 						 * This could be a variable with the same name as a function.
@@ -471,26 +478,38 @@ static void Formula_lexan (void) {
 						/*
 						 * This must be a variable, since there is no "current object" here.
 						 */
-						InterpreterVariable var = Interpreter_hasVariable (theInterpreter, token.string);
-						if (var == NULL) {
-							nieuwtok (VARIABLE_NAME_)
+						int jkar = ikar + 1;
+						while (theExpression [jkar] == ' ' || theExpression [jkar] == '\t') jkar ++;
+						if (theExpression [jkar] == '[' && ! isArray) {
+							if (isString) {
+								nieuwtok (INDEXED_STRING_VARIABLE_)
+							} else {
+								nieuwtok (INDEXED_NUMERIC_VARIABLE_)
+							}
 							lexan [itok]. content.string = Melder_wcsdup_f (token.string);
 							numberOfStringConstants ++;
 						} else {
-							if (isArray) {
-								if (isString) {
-									nieuwtok (STRING_ARRAY_VARIABLE_)
-								} else {
-									nieuwtok (NUMERIC_ARRAY_VARIABLE_)
-								}
+							InterpreterVariable var = Interpreter_hasVariable (theInterpreter, token.string);
+							if (var == NULL) {
+								nieuwtok (VARIABLE_NAME_)
+								lexan [itok]. content.string = Melder_wcsdup_f (token.string);
+								numberOfStringConstants ++;
 							} else {
-								if (isString) {
-									nieuwtok (STRING_VARIABLE_)
+								if (isArray) {
+									if (isString) {
+										nieuwtok (STRING_ARRAY_VARIABLE_)
+									} else {
+										nieuwtok (NUMERIC_ARRAY_VARIABLE_)
+									}
 								} else {
-									nieuwtok (NUMERIC_VARIABLE_)
+									if (isString) {
+										nieuwtok (STRING_VARIABLE_)
+									} else {
+										nieuwtok (NUMERIC_VARIABLE_)
+									}
 								}
+								lexan [itok]. content.variable = var;
 							}
-							lexan [itok]. content.variable = var;
 						}
 					}
 				} else {
@@ -502,7 +521,7 @@ static void Formula_lexan (void) {
 				 */
 				int jkar = ikar + 1;
 				while (theExpression [jkar] == ' ' || theExpression [jkar] == '\t') jkar ++;
-				if (theExpression [jkar] == '(') {
+				if (theExpression [jkar] == '(' || theExpression [jkar] == ':') {
 					Melder_throw (
 						"Unknown function " L_LEFT_GUILLEMET, token.string, L_RIGHT_GUILLEMET " in formula.");
 				} else if (theExpression [jkar] == '[' && ! isArray) {
@@ -646,6 +665,8 @@ static void Formula_lexan (void) {
 			}
 		} else if (kar == ',') {
 			nieuwtok (KOMMA_)
+		} else if (kar == ':') {
+			nieuwtok (COLON_)
 		} else if (kar == ';') {
 			nieuwtok (END_)
 		} else if (kar == '^') {
@@ -724,6 +745,20 @@ static void pas (int symbol) {
 	}
 }
 
+static bool pasArguments () {
+    int symbol = nieuwlees;
+    if (symbol == HAAKJEOPENEN_) return true;   // success: a function call like: myFunction (...)
+    if (symbol == COLON_) return false;   // success: a function call like: myFunction: ...
+    static MelderString melding = { 0 };
+    MelderString_empty (& melding);
+    const wchar_t *symbolName2 = Formula_instructionNames [lexan [ilexan]. symbol];
+    bool needQuotes2 = wcschr (symbolName2, ' ') == NULL;
+    MelderString_append (& melding,
+                         L"Expected \"(\" or \":\", but found ", needQuotes2 ? L"\"" : NULL, symbolName2, needQuotes2 ? L"\"" : NULL);
+    formulefout (melding.string, lexan [ilexan]. position);
+    return false;   // will never occur
+}
+
 #define nieuwontleed(s)  parse [++ iparse]. symbol = (s)
 #define parsenumber(g)  parse [iparse]. content.number = (g)
 #define ontleedlabel(l)  parse [iparse]. content.label = (l)
@@ -1169,38 +1204,38 @@ static void parsePowerFactor (void) {
 	}
 
 	if (symbol >= LOW_FUNCTION_1 && symbol <= HIGH_FUNCTION_1) {
-		pas (HAAKJEOPENEN_);
+		bool isParenthesis = pasArguments ();
 		parseExpression ();
-		pas (HAAKJESLUITEN_);
+		if (isParenthesis) pas (HAAKJESLUITEN_);
 		nieuwontleed (symbol);
 		return;
 	}
 
 	if (symbol >= LOW_FUNCTION_2 && symbol <= HIGH_FUNCTION_2) {
-		pas (HAAKJEOPENEN_);
+		bool isParenthesis = pasArguments ();
 		parseExpression ();
 		pas (KOMMA_);
 		parseExpression ();
-		pas (HAAKJESLUITEN_);
+		if (isParenthesis) pas (HAAKJESLUITEN_);
 		nieuwontleed (symbol);
 		return;
 	}
 
 	if (symbol >= LOW_FUNCTION_3 && symbol <= HIGH_FUNCTION_3) {
-		pas (HAAKJEOPENEN_);
+		bool isParenthesis = pasArguments ();
 		parseExpression ();
 		pas (KOMMA_);
 		parseExpression ();
 		pas (KOMMA_);
 		parseExpression ();
-		pas (HAAKJESLUITEN_);
+		if (isParenthesis) pas (HAAKJESLUITEN_);
 		nieuwontleed (symbol);
 		return;
 	}
 
 	if (symbol >= LOW_FUNCTION_N && symbol <= HIGH_FUNCTION_N) {
 		int n = 0;
-		pas (HAAKJEOPENEN_);
+		bool isParenthesis = pasArguments ();
 		if (nieuwlees != HAAKJESLUITEN_) {
 			oudlees;
 			parseExpression ();
@@ -1210,7 +1245,7 @@ static void parsePowerFactor (void) {
 				n ++;
 			}
 			oudlees;
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 		}
 		nieuwontleed (NUMBER_); parsenumber (n);
 		nieuwontleed (symbol);
@@ -1220,7 +1255,7 @@ static void parsePowerFactor (void) {
 	if (symbol == CALL_) {
 		wchar_t *procedureName = lexan [ilexan]. content.string;   // reference copy!
 		int n = 0;
-		pas (HAAKJEOPENEN_);
+		bool isParenthesis = pasArguments ();
 		if (nieuwlees != HAAKJESLUITEN_) {
 			oudlees;
 			parseExpression ();
@@ -1230,7 +1265,7 @@ static void parsePowerFactor (void) {
 				n ++;
 			}
 			oudlees;
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 		}
 		nieuwontleed (NUMBER_); parsenumber (n);
 		nieuwontleed (CALL_);
@@ -1240,39 +1275,35 @@ static void parsePowerFactor (void) {
 
 	if (symbol >= LOW_STRING_FUNCTION && symbol <= HIGH_STRING_FUNCTION) {
 		if (symbol >= LOW_FUNCTION_STR1 && symbol <= HIGH_FUNCTION_STR1) {
-			pas (HAAKJEOPENEN_);
+            bool isParenthesis = pasArguments ();
 			parseExpression ();
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 		} else if (symbol == INDEX_ || symbol == RINDEX_ ||
 			symbol == STARTS_WITH_ || symbol == ENDS_WITH_ ||
 			symbol == INDEX_REGEX_ || symbol == RINDEX_REGEX_ || symbol == EXTRACT_NUMBER_)
 		{
-			pas (HAAKJEOPENEN_);
+            bool isParenthesis = pasArguments ();
 			parseExpression ();
 			pas (KOMMA_);
 			parseExpression ();
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 		} else if (symbol == DATESTR_ || symbol == INFOSTR_) {
 			pas (HAAKJEOPENEN_);
 			pas (HAAKJESLUITEN_);
 		} else if (symbol == EXTRACT_WORDSTR_ || symbol == EXTRACT_LINESTR_) {
-			pas (HAAKJEOPENEN_);
+            bool isParenthesis = pasArguments ();
 			parseExpression ();
 			pas (KOMMA_);
 			parseExpression ();
-			pas (HAAKJESLUITEN_);
-		} else if (symbol == ENVIRONMENTSTR_) {
-			pas (HAAKJEOPENEN_);
-			parseExpression ();
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 		} else if (symbol == FIXEDSTR_ || symbol == PERCENTSTR_) {
-			pas (HAAKJEOPENEN_);
+            bool isParenthesis = pasArguments ();
 			parseExpression ();
 			pas (KOMMA_);
 			parseExpression ();
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 		} else if (symbol == REPLACESTR_ || symbol == REPLACE_REGEXSTR_) {
-			pas (HAAKJEOPENEN_);
+            bool isParenthesis = pasArguments ();
 			parseExpression ();
 			pas (KOMMA_);
 			parseExpression ();
@@ -1280,7 +1311,7 @@ static void parsePowerFactor (void) {
 			parseExpression ();
 			pas (KOMMA_);
 			parseExpression ();
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 		} else {
 			oudlees;   // needed for retry if we are going to be in a string comparison?
 			formulefout (L"Function expected", lexan [ilexan + 1]. position);
@@ -1293,7 +1324,7 @@ static void parsePowerFactor (void) {
 		if (symbol == SUM_) {
 			//theOptimize = 1;
 			nieuwontleed (NUMBER_); parsenumber (0.0);   // initialize the sum
-			pas (HAAKJEOPENEN_);
+            bool isParenthesis = pasArguments ();
 			int symbol2 = nieuwlees;
 			if (symbol2 == NUMERIC_VARIABLE_) {   // an existing variable
 				nieuwontleed (VARIABLE_REFERENCE_);
@@ -1324,7 +1355,7 @@ static void parsePowerFactor (void) {
 			nieuwontleed (INCREMENT_GREATER_GOTO_); ontleedlabel (endLabel);
 			pas (KOMMA_);
 			parseExpression ();
-			pas (HAAKJESLUITEN_);
+			if (isParenthesis) pas (HAAKJESLUITEN_);
 			// now on stack: sum, loop variable, end value, value to add
 			nieuwontleed (ADD_3DOWN_);
 			// now on stack: sum, loop variable, end value
@@ -1463,7 +1494,7 @@ static void parseExpression (void) {
 
 /*
 	Translate the infix expression "my lexan" into the postfix expression "my parse":
-	remove parentheses and brackets, commas, FROM_, TO_,
+	remove parentheses and brackets, commas, colons, FROM_, TO_,
 	IF_ THEN_ ELSE_ ENDIF_ OR_ AND_;
 	introduce LABEL_ GOTO_ IFTRUE_ IFFALSE_ TRUE_ FALSE_
 	SELF0_ SELF1_ SELF2_ MATRIKS0_ MATRIKS1_ MATRIKS2_
@@ -1810,7 +1841,7 @@ void Formula_compile (Any interpreter, Any data, const wchar_t *expression, int
 	}
 	theSource = (Data) data;
 	theExpression = expression;
-	theExpressionType = expressionType;
+	theExpressionType [theLevel] = expressionType;
 	theOptimize = optimize;
 	if (! lexan) {
 		lexan = Melder_calloc_f (struct structFormulaInstruction, 3000);
@@ -2335,6 +2366,17 @@ static void do_function_ll_l (long (*f) (long, long)) {
 			Stackel_whichText (x), " and ", Stackel_whichText (y), ".");
 	}
 }
+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, y->number));
+	} else {
+		Melder_throw ("The function ", Formula_instructionNames [parse [programPointer]. symbol],
+			" requires two numeric arguments, not ",
+			Stackel_whichText (x), " and ", Stackel_whichText (y), ".");
+	}
+}
 static void do_objects_are_identical (void) {
 	Stackel y = pop, x = pop;
 	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
@@ -2542,7 +2584,7 @@ static void do_writeFile () {
 	Melder_assert (narg->which == Stackel_NUMBER);
 	int numberOfArguments = narg->number;
 	w -= numberOfArguments;
-	Stackel fileName = & theStack [1];
+	Stackel fileName = & theStack [w + 1];
 	if (fileName -> which != Stackel_STRING) {
 		Melder_throw ("The first argument of \"writeFile\" has to be a string (a file name), not ", Stackel_whichText (fileName), ".");
 	}
@@ -2566,7 +2608,7 @@ static void do_writeFileLine () {
 	Melder_assert (narg->which == Stackel_NUMBER);
 	int numberOfArguments = narg->number;
 	w -= numberOfArguments;
-	Stackel fileName = & theStack [1];
+	Stackel fileName = & theStack [w + 1];
 	if (fileName -> which != Stackel_STRING) {
 		Melder_throw ("The first argument of \"writeFile\" has to be a string (a file name), not ", Stackel_whichText (fileName), ".");
 	}
@@ -2591,7 +2633,7 @@ static void do_appendFile () {
 	Melder_assert (narg->which == Stackel_NUMBER);
 	int numberOfArguments = narg->number;
 	w -= numberOfArguments;
-	Stackel fileName = & theStack [1];
+	Stackel fileName = & theStack [w + 1];
 	if (fileName -> which != Stackel_STRING) {
 		Melder_throw ("The first argument of \"writeFile\" has to be a string (a file name), not ", Stackel_whichText (fileName), ".");
 	}
@@ -2615,7 +2657,7 @@ static void do_appendFileLine () {
 	Melder_assert (narg->which == Stackel_NUMBER);
 	int numberOfArguments = narg->number;
 	w -= numberOfArguments;
-	Stackel fileName = & theStack [1];
+	Stackel fileName = & theStack [w + 1];
 	if (fileName -> which != Stackel_STRING) {
 		Melder_throw ("The first argument of \"writeFile\" has to be a string (a file name), not ", Stackel_whichText (fileName), ".");
 	}
@@ -2633,6 +2675,62 @@ static void do_appendFileLine () {
 	MelderFile_appendText (& file, text.transfer());
 	pushNumber (1);
 }
+static void do_pauseScript () {
+	if (theCurrentPraatObjects != & theForegroundPraatObjects)
+		Melder_throw ("The function \"pause\" is not available inside manuals.");
+	if (theCurrentPraatApplication -> batch) return;   // in batch we ignore pause statements
+	Stackel narg = pop;
+	Melder_assert (narg->which == Stackel_NUMBER);
+	int numberOfArguments = narg->number;
+	w -= numberOfArguments;
+	autoMelderString buffer;
+	for (int iarg = 1; iarg <= numberOfArguments; iarg ++) {
+		Stackel arg = & theStack [w + iarg];
+		if (arg->which == Stackel_NUMBER)
+			MelderString_append (& buffer, Melder_double (arg->number));
+		else if (arg->which == Stackel_STRING)
+			MelderString_append (& buffer, arg->string);
+	}
+	UiPause_begin (theCurrentPraatApplication -> topShell, L"stop or continue", theInterpreter);
+	UiPause_comment (numberOfArguments == 0 ? L"..." : buffer.string);
+	UiPause_end (1, 1, 0, L"Continue", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, theInterpreter);
+	pushNumber (1);
+}
+static void do_exitScript () {
+	Stackel narg = pop;
+	Melder_assert (narg->which == Stackel_NUMBER);
+	int numberOfArguments = narg->number;
+	w -= numberOfArguments;
+	for (int iarg = 1; iarg <= numberOfArguments; iarg ++) {
+		Stackel arg = & theStack [w + iarg];
+		if (arg->which == Stackel_NUMBER)
+			Melder_error_noLine (Melder_double (arg->number));
+		else if (arg->which == Stackel_STRING)
+			Melder_error_noLine (arg->string);
+	}
+	Melder_throw ("\nScript exited.");
+	pushNumber (1);
+}
+static void do_runScript () {
+	Stackel narg = pop;
+	Melder_assert (narg->which == Stackel_NUMBER);
+	int numberOfArguments = narg->number;
+	if (numberOfArguments < 1)
+		Melder_throw ("The function \"runScript\" requires at least one argument, namely the file name.");
+	w -= numberOfArguments;
+	Stackel fileName = & theStack [w + 1];
+	if (fileName->which != Stackel_STRING)
+		Melder_throw ("The first argument to \"runScript\" has to be a string (the file name), not ", Stackel_whichText (fileName));
+	theLevel += 1;
+	try {
+		praat_executeScriptFromFileName (fileName->string, numberOfArguments - 1, & theStack [w + 1]);
+		theLevel -= 1;
+	} catch (MelderError) {
+		theLevel -= 1;
+		throw;
+	}
+	pushNumber (1);
+}
 static void do_min (void) {
 	Stackel n = pop, last;
 	double result;
@@ -2819,6 +2917,30 @@ static void do_numberOfColumns (void) {
 			" requires a numeric argument, not ", Stackel_whichText (array), ".");
 	}
 }
+static void do_editor (void) {
+	Stackel n = pop;
+	Melder_assert (n->which == Stackel_NUMBER);
+	if (n->number == 0) {
+		if (theInterpreter && theInterpreter -> editorClass) {
+			praatP. editor = praat_findEditorFromString (theInterpreter -> environmentName);
+		} else {
+			Melder_throw ("The function \"editor\" requires an argument when called from outside an editor.");
+		}
+	} else if (n->number == 1) {
+		Stackel editor = pop;
+		if (editor->which == Stackel_STRING) {
+			praatP. editor = praat_findEditorFromString (editor->string);
+		} else if (editor->which == Stackel_NUMBER) {
+			praatP. editor = praat_findEditorById (round (editor->number));
+		} else {
+			Melder_throw ("The function \"editor\" requires a numeric or string argument, not ", Stackel_whichText (editor), ".");
+		}
+	} else {
+		Melder_throw ("The function \"editor\" requires 0 or 1 arguments, not ", n->number, ".");
+	}
+	pushNumber (1);
+}
+
 static void do_numericArrayElement (void) {
 	Stackel n = pop;
 	Melder_assert (n -> which == Stackel_NUMBER);
@@ -3007,6 +3129,28 @@ static void do_midStr (void) {
 		Melder_throw ("The function \"mid$\" requires two or three arguments.");
 	}
 }
+static void do_unicodeToBackslashTrigraphsStr () {
+	Stackel s = pop;
+	if (s->which == Stackel_STRING) {
+		long length = wcslen (s->string);
+		autostring trigraphs = Melder_calloc (wchar_t, 3 * length + 1);
+		Longchar_genericizeW (s->string, trigraphs.peek());
+		pushString (trigraphs.transfer());
+	} else {
+		Melder_throw ("The function \"unicodeToBackslashTrigraphs$\" requires a string, not ", Stackel_whichText (s), ".");
+	}
+}
+static void do_backslashTrigraphsToUnicodeStr () {
+	Stackel s = pop;
+	if (s->which == Stackel_STRING) {
+		long length = wcslen (s->string);
+		autostring unicode = Melder_calloc (wchar_t, length + 1);
+		Longchar_nativizeW (s->string, unicode.peek(), false);
+		pushString (unicode.transfer());
+	} else {
+		Melder_throw ("The function \"unicodeToBackslashTrigraphs$\" requires a string, not ", Stackel_whichText (s), ".");
+	}
+}
 static void do_environmentStr (void) {
 	Stackel s = pop;
 	if (s->which == Stackel_STRING) {
@@ -4580,6 +4724,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 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);
@@ -4614,6 +4759,9 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case WRITE_FILE_LINE_ : { do_writeFileLine  ();
 } break; case APPEND_FILE_     : { do_appendFile     ();
 } break; case APPEND_FILE_LINE_: { do_appendFileLine ();
+} break; case PAUSE_SCRIPT_: { do_pauseScript ();
+} break; case EXIT_SCRIPT_: { do_exitScript ();
+} break; case RUN_SCRIPT_: { do_runScript ();
 } break; case MIN_: { do_min ();
 } break; case MAX_: { do_max ();
 } break; case IMIN_: { do_imin ();
@@ -4625,6 +4773,7 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case RANDOM_GAUSS_NUMAR_: { do_function_dd_d_numar (NUMrandomGauss);
 } break; case NUMBER_OF_ROWS_: { do_numberOfRows ();
 } break; case NUMBER_OF_COLUMNS_: { do_numberOfColumns ();
+} break; case EDITOR_: { do_editor ();
 /********** String functions: **********/
 } break; case LENGTH_: { do_length ();
 } break; case STRING_TO_NUMBER_: { do_number ();
@@ -4634,6 +4783,8 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case LEFTSTR_: { do_leftStr ();
 } break; case RIGHTSTR_: { do_rightStr ();
 } break; case MIDSTR_: { do_midStr ();
+} break; case UNICODE_TO_BACKSLASH_TRIGRAPHS_: { do_unicodeToBackslashTrigraphsStr ();
+} break; case BACKSLASH_TRIGRAPHS_TO_UNICODE_: { do_backslashTrigraphsToUnicodeStr ();
 } break; case ENVIRONMENTSTR_: { do_environmentStr ();
 } break; case INDEX_: { do_index ();
 } break; case RINDEX_: { do_rindex ();
@@ -4805,54 +4956,38 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 			programPointer ++;
 		} // endwhile
 		if (w != 1) Melder_fatal ("Formula: stackpointer ends at %ld instead of 1.", w);
-		if (theExpressionType == kFormula_EXPRESSION_TYPE_NUMERIC) {
+		if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_NUMERIC) {
 			if (theStack [1]. which == Stackel_STRING) Melder_throw ("Found a string expression instead of a numeric expression.");
 			if (theStack [1]. which == Stackel_NUMERIC_ARRAY) Melder_throw ("Found a numeric array expression instead of a numeric expression.");
-			if (result) {
-				result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC;
-				result -> result.numericResult = theStack [1]. number;
-			} else {
-				Melder_information (Melder_double (theStack [1]. number));
-			}
-		} else if (theExpressionType == kFormula_EXPRESSION_TYPE_STRING) {
-			if (theStack [1]. which == Stackel_NUMBER) Melder_throw ("Found a numeric expression instead of a string expression.");
+			result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC;
+			result -> result.numericResult = theStack [1]. number;
+		} else if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_STRING) {
+			if (theStack [1]. which == Stackel_NUMBER)
+				Melder_throw ("Found a numeric expression (value ", theStack [1]. number, ") instead of a string expression.");
 			if (theStack [1]. which == Stackel_NUMERIC_ARRAY) Melder_throw ("Found a numeric array expression instead of a string expression.");
-			if (result) {
-				result -> expressionType = kFormula_EXPRESSION_TYPE_STRING;
-				result -> result.stringResult = theStack [1]. string; theStack [1]. string = NULL;   /* Undangle. */
-			} else {
-				Melder_information (theStack [1]. string);
-			}
-		} else if (theExpressionType == kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY) {
+			result -> expressionType = kFormula_EXPRESSION_TYPE_STRING;
+			result -> result.stringResult = theStack [1]. string;   // dangle...
+			theStack [1]. string = NULL;   // ...undangle (and disown)
+		} else if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY) {
 			if (theStack [1]. which == Stackel_NUMBER) Melder_throw ("Found a numeric expression instead of a numeric array expression.");
 			if (theStack [1]. which == Stackel_STRING) Melder_throw ("Found a string expression instead of a numeric array expression.");
-			if (result) {
-				result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY;
-				result -> result.numericArrayResult = theStack [1]. numericArray; theStack [1]. numericArray = theZeroNumericArray;   /* Undangle. */
-			} else {
-				//Melder_information (theStack [1]. string);  // TODO: implement
-			}
+			result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY;
+			result -> result.numericArrayResult = theStack [1]. numericArray;   // dangle
+			theStack [1]. numericArray = theZeroNumericArray;   // ...undangle (and disown)
 		} else {
-			Melder_assert (theExpressionType == kFormula_EXPRESSION_TYPE_UNKNOWN);
+			Melder_assert (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_UNKNOWN);
 			if (theStack [1]. which == Stackel_NUMBER) {
-				if (result) {
-					result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC;
-					result -> result.numericResult = theStack [1]. number;
-				} else {
-					Melder_information (Melder_double (theStack [1]. number));
-				}
+				result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC;
+				result -> result.numericResult = theStack [1]. number;
 			} else {
 				Melder_assert (theStack [1]. which == Stackel_STRING);
-				if (result) {
-					result -> expressionType = kFormula_EXPRESSION_TYPE_STRING;
-					result -> result.stringResult = theStack [1]. string; theStack [1]. string = NULL;   /* Undangle. */
-				} else {
-					Melder_information (theStack [1]. string);
-				}
+				result -> expressionType = kFormula_EXPRESSION_TYPE_STRING;
+				result -> result.stringResult = theStack [1]. string;   // dangle...
+				theStack [1]. string = NULL;   // ...undangle (and disown)
 			}
 		}
 		/*
-			Clean up the stack (theStack [1] may have been disowned).
+			Clean up the stack (theStack [1] has probably been disowned).
 		*/
 		for (w = wmax; w > 0; w --) {
 			Stackel stackel = & theStack [w];
@@ -4860,13 +4995,17 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 		}
 	} catch (MelderError) {
 		/*
-			Clean up the stack (theStack [1] may have been disowned).
+			Clean up the stack (theStack [1] has probably not been disowned).
 		*/
 		for (w = wmax; w > 0; w --) {
 			Stackel stackel = & theStack [w];
 			if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
 		}
-		Melder_throw ("Formula not run.");
+		if (Melder_hasError (L"Script exited.")) {
+			throw;
+		} else {
+			Melder_throw ("Formula not run.");
+		}
 	}
 }
 
diff --git a/sys/Formula.h b/sys/Formula.h
index 18e2e1d..6861061 100644
--- a/sys/Formula.h
+++ b/sys/Formula.h
@@ -2,7 +2,7 @@
 #define _Formula_h_
 /* Formula.h
  *
- * Copyright (C) 1990-2011,2013 Paul Boersma
+ * Copyright (C) 1990-2011,2013,2014 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
diff --git a/sys/Graphics.cpp b/sys/Graphics.cpp
index 4b2ac55..fbd27f6 100644
--- a/sys/Graphics.cpp
+++ b/sys/Graphics.cpp
@@ -1,6 +1,6 @@
 /* Graphics.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2014 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
@@ -26,11 +26,18 @@
 #include "Graphics_enums.h"
 
 #include "GuiP.h"
+#include "Preferences.h"
 
 /***** Methods *****/
 
 Thing_implement (Graphics, Thing, 0);
 
+enum kGraphics_cjkFontStyle theGraphicsCjkFontStyle;
+
+void Graphics_prefs () {
+	Preferences_addEnum (L"Graphics.cjkFontStyle", & theGraphicsCjkFontStyle, kGraphics_cjkFontStyle, kGraphics_cjkFontStyle_DEFAULT);
+}
+
 void structGraphics :: v_destroy () {
 	Melder_free (record);
 	Graphics_Parent :: v_destroy ();
@@ -42,32 +49,31 @@ void structGraphics :: v_destroy () {
  * Graphics_setWsViewport ().
  */
 static void widgetToWindowCoordinates (I) {
-#if motif && mac
-    iam (Graphics);
-    if (my screen) {
-        iam (GraphicsScreen);
-        if (my d_drawingArea) {
-            GuiObject widget = my d_drawingArea -> d_widget;
-            int shellX = 0, shellY = 0;
-            do {
-                int x = widget -> x, y = widget -> y;
-                shellX += x;
-                shellY += y;
-                widget = widget -> parent;
-            } while (! XtIsShell (widget));
-            my d_x1DC += shellX;
-            my d_x2DC += shellX;
-            my d_y1DC += shellY;
-            my d_y2DC += shellY;
-        }
-    }
+	#if motif && mac
+		iam (Graphics);
+		if (my screen) {
+			iam (GraphicsScreen);
+			if (my d_drawingArea) {
+				GuiObject widget = my d_drawingArea -> d_widget;
+				int shellX = 0, shellY = 0;
+				do {
+					int x = widget -> x, y = widget -> y;
+					shellX += x;
+					shellY += y;
+					widget = widget -> parent;
+				} while (! XtIsShell (widget));
+				my d_x1DC += shellX;
+				my d_x2DC += shellX;
+				my d_y1DC += shellY;
+				my d_y2DC += shellY;
+			}
+		}
 	#else
 		(void) void_me;
 	#endif
 }
 
-static void computeTrafo (I) {
-	iam (Graphics);
+static void computeTrafo (Graphics me) {
 	double worldScaleX, worldScaleY, workScaleX, workScaleY;
 	worldScaleX = (my d_x2NDC - my d_x1NDC) / (my d_x2WC - my d_x1WC);
 	worldScaleY = (my d_y2NDC - my d_y1NDC) / (my d_y2WC - my d_y1WC);
@@ -88,7 +94,21 @@ static void computeTrafo (I) {
 
 /***** WORKSTATION FUNCTIONS *****/
 
-void Graphics_init (Graphics me) {
+void Graphics_init (Graphics me, int resolution) {
+	my resolution = resolution;
+	if (resolution == 96) {
+		my resolutionNumber = kGraphics_resolution_96;
+	} else if (resolution == 100) {
+		my resolutionNumber = kGraphics_resolution_100;
+	} else if (resolution == 300) {
+		my resolutionNumber = kGraphics_resolution_300;
+	} else if (resolution == 360) {
+		my resolutionNumber = kGraphics_resolution_360;
+	} else if (resolution == 600) {
+		my resolutionNumber = kGraphics_resolution_600;
+	} else {
+		Melder_fatal ("Unsupported resolution %d dpi.", resolution);
+	}
 	my d_x1DC = my d_x1DCmin = 0;	my d_x2DC = my d_x2DCmax = 32767;
 	my d_y1DC = my d_y1DCmin = 0;	my d_y2DC = my d_y2DCmax = 32767;
 	my d_x1WC = my d_x1NDC = my d_x1wNDC = 0.0;
@@ -99,6 +119,7 @@ void Graphics_init (Graphics me) {
 	computeTrafo (me);
 	my lineWidth = 1.0;
 	my arrowSize = 1.0;
+	my speckleSize = 1.0;
 	my font = kGraphics_font_HELVETICA;
 	my fontSize = 10;
 	my fontStyle = Graphics_NORMAL;
@@ -114,8 +135,7 @@ void Graphics_init (Graphics me) {
 
 Graphics Graphics_create (int resolution) {
 	Graphics me = (Graphics) Thing_new (Graphics);
-	Graphics_init (me);
-	my resolution = resolution;
+	Graphics_init (me, resolution);
 	return me;
 }
 
@@ -216,6 +236,7 @@ void Graphics_WCtoDC (Graphics me, double xWC, double yWC, long *xDC, long *yDC)
 /***** OUTPUT PRIMITIVES, RECORDABLE *****/
 
 void Graphics_setViewport (Graphics me, double x1NDC, double x2NDC, double y1NDC, double y2NDC) {
+	trace ("enter %f %f %f %f", x1NDC, x2NDC, y1NDC, y2NDC);
 	my d_x1NDC = x1NDC;
 	my d_x2NDC = x2NDC;
 	my d_y1NDC = y1NDC;
@@ -243,6 +264,7 @@ void Graphics_setInner (Graphics me) {
 	my d_x2NDC = (1 - dx) * my outerViewport.x2NDC + dx * my outerViewport.x1NDC;
 	my d_y1NDC = (1 - dy) * my outerViewport.y1NDC + dy * my outerViewport.y2NDC;
 	my d_y2NDC = (1 - dy) * my outerViewport.y2NDC + dy * my outerViewport.y1NDC;
+	trace ("done %f %f %f %f", my d_x1NDC, my d_x2NDC, my d_y1NDC, my d_y2NDC);
 	computeTrafo (me);
 	if (my recording) { op (SET_INNER, 0); }
 }
@@ -252,6 +274,7 @@ void Graphics_unsetInner (Graphics me) {
 	my d_x2NDC = my outerViewport.x2NDC;
 	my d_y1NDC = my outerViewport.y1NDC;
 	my d_y2NDC = my outerViewport.y2NDC;
+	trace ("done %f %f %f %f", my d_x1NDC, my d_x2NDC, my d_y1NDC, my d_y2NDC);
 	computeTrafo (me);
 	if (my recording)
 		{ op (UNSET_INNER, 0); }
@@ -286,6 +309,7 @@ void Graphics_inqWindow (Graphics me, double *x1WC, double *x2WC, double *y1WC,
 Graphics_Viewport Graphics_insetViewport
 	(Graphics me, double x1rel, double x2rel, double y1rel, double y2rel)
 {
+	trace ("enter");
 	Graphics_Viewport previous;
 	previous.x1NDC = my d_x1NDC;
 	previous.x2NDC = my d_x2NDC;
@@ -300,6 +324,7 @@ Graphics_Viewport Graphics_insetViewport
 }
 
 void Graphics_resetViewport (Graphics me, Graphics_Viewport viewport) {
+	trace ("enter");
 	Graphics_setViewport (me, viewport.x1NDC, viewport.x2NDC, viewport.y1NDC, viewport.y2NDC);
 }
 
diff --git a/sys/Graphics.h b/sys/Graphics.h
index fd347f7..860be16 100644
--- a/sys/Graphics.h
+++ b/sys/Graphics.h
@@ -2,7 +2,7 @@
 #define _Graphics_h_
 /* Graphics.h
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -57,6 +57,8 @@ Thing_define (Graphics, Thing) {
 			/* Also used as a boolean. */
 		int resolution;
 			/* Dots per inch. */
+		enum kGraphics_resolution resolutionNumber;
+			/* 0 = 96 dpi, 1 = 100 dpi, 2 = 300 dpi, 3 = 360 dpi, 4 = 600 dpi */
 		long d_x1DCmin, d_x2DCmax, d_y1DCmin, d_y2DCmax;
 			/* Maximum dimensions of the output device. */
 			/* x1DCmin < x2DCmax; y1DCmin < y2DCmax; */
@@ -98,7 +100,8 @@ Thing_define (Graphics, Thing) {
 		/* Graphics state. */
 		int lineType;
 		Graphics_Colour colour;
-		double lineWidth, arrowSize;
+		double lineWidth, arrowSize, speckleSize;
+		enum kGraphics_colourScale colourScale;
 		int horizontalTextAlignment, verticalTextAlignment;
 		double textRotation, wrapWidth, secondIndent, textX, textY;
 		enum kGraphics_font font;
@@ -114,22 +117,22 @@ Thing_define (Graphics, Thing) {
 	// overridden methods:
 		virtual void v_destroy ();
 	// new methods:
-		virtual void v_polyline (long numberOfPoints, long *xyDC, bool close) { (void) numberOfPoints; (void) xyDC; (void) close; }
-		virtual void v_fillArea (long numberOfPoints, long *xyDC) { (void) numberOfPoints; (void) xyDC; }
-		virtual void v_rectangle (long a_x1DC, long a_x2DC, long a_y1DC, long a_y2DC) { (void) a_x1DC; (void) a_x2DC; (void) a_y1DC; (void) a_y2DC; }
-		virtual void v_fillRectangle (long a_x1DC, long a_x2DC, long a_y1DC, long a_y2DC) { (void) a_x1DC; (void) a_x2DC; (void) a_y1DC; (void) a_y2DC; }
+		virtual void v_polyline (long numberOfPoints, double *xyDC, bool close) { (void) numberOfPoints; (void) xyDC; (void) close; }
+		virtual void v_fillArea (long numberOfPoints, double *xyDC) { (void) numberOfPoints; (void) xyDC; }
+		virtual void v_rectangle (double x1DC, double x2DC, double y1DC, double y2DC) { (void) x1DC; (void) x2DC; (void) y1DC; (void) y2DC; }
+		virtual void v_fillRectangle (double a_x1DC, double a_x2DC, double a_y1DC, double a_y2DC) { (void) a_x1DC; (void) a_x2DC; (void) a_y1DC; (void) a_y2DC; }
 		virtual void v_circle (double xDC, double yDC, double rDC) { (void) xDC; (void) yDC; (void) rDC; }
-		virtual void v_ellipse (long a_x1DC, long a_x2DC, long a_y1DC, long a_y2DC) { (void) a_x1DC; (void) a_x2DC; (void) a_y1DC; (void) a_y2DC; }
-		virtual void v_arc (long xDC, long yDC, long rDC, double fromAngle, double toAngle) { (void) xDC; (void) yDC; (void) rDC; (void) fromAngle; (void) toAngle; }
-		virtual void v_fillCircle (long xDC, long yDC, long rDC) { (void) xDC; (void) yDC; (void) rDC; }
-		virtual void v_fillEllipse (long a_x1DC, long a_x2DC, long a_y1DC, long a_y2DC) { (void) a_x1DC; (void) a_x2DC; (void) a_y1DC; (void) a_y2DC; }
-		virtual void v_button (long a_x1DC, long a_x2DC, long a_y1DC, long a_y2DC)
+		virtual void v_ellipse (double a_x1DC, double a_x2DC, double a_y1DC, double a_y2DC) { (void) a_x1DC; (void) a_x2DC; (void) a_y1DC; (void) a_y2DC; }
+		virtual void v_arc (double xDC, double yDC, double rDC, double fromAngle, double toAngle) { (void) xDC; (void) yDC; (void) rDC; (void) fromAngle; (void) toAngle; }
+		virtual void v_fillCircle (double xDC, double yDC, double rDC) { (void) xDC; (void) yDC; (void) rDC; }
+		virtual void v_fillEllipse (double a_x1DC, double a_x2DC, double a_y1DC, double a_y2DC) { (void) a_x1DC; (void) a_x2DC; (void) a_y1DC; (void) a_y2DC; }
+		virtual void v_button (double a_x1DC, double a_x2DC, double a_y1DC, double a_y2DC)
 			{
 				v_rectangle (a_x1DC, a_x2DC, a_y1DC, a_y2DC);   // the simplest implementation
 			}
-		virtual void v_roundedRectangle (long x1DC, long x2DC, long y1DC, long y2DC, long r);
-		virtual void v_fillRoundedRectangle (long x1DC, long x2DC, long y1DC, long y2DC, long r);
-		virtual void v_arrowHead (long xDC, long yDC, double angle);
+		virtual void v_roundedRectangle (double x1DC, double x2DC, double y1DC, double y2DC, double r);
+		virtual void v_fillRoundedRectangle (double x1DC, double x2DC, double y1DC, double y2DC, double r);
+		virtual void v_arrowHead (double xDC, double yDC, double angle);
 		virtual bool v_mouseStillDown () { return false; }
 		virtual void v_getMouseLocation (double *xWC, double *yWC) { *xWC = *yWC = NUMundefined; }
 		virtual void v_flushWs () { }
@@ -146,6 +149,8 @@ Graphics Graphics_create_pdffile (MelderFile file, int resolution,
 	double x1inches, double x2inches, double y1inches, double y2inches);
 Graphics Graphics_create_pdf (void *context, int resolution,
 	double x1inches, double x2inches, double y1inches, double y2inches);
+Graphics Graphics_create_pngfile (MelderFile file, int resolution,
+	double x1inches, double x2inches, double y1inches, double y2inches);
 Graphics Graphics_create_postscriptprinter (void);
 Graphics Graphics_create_screenPrinter (void *display, void *window);
 Graphics Graphics_create_screen (void *display, void *window, int resolution);
@@ -211,6 +216,7 @@ void Graphics_circle (Graphics me, double x, double y, double r);
 void Graphics_fillCircle (Graphics me, double x, double y, double r);
 void Graphics_circle_mm (Graphics me, double x, double y, double d);
 void Graphics_fillCircle_mm (Graphics me, double x, double y, double d);
+void Graphics_speckle (Graphics me, double x, double y);
 void Graphics_rectangle_mm (Graphics me, double x, double y, double horizontalSide_mm, double verticalSide_mm);
 void Graphics_fillRectangle_mm (Graphics me, double x, double y, double horizontalSide_mm, double verticalSide_mm);
 void Graphics_arc (Graphics me, double x, double y, double r, double fromAngle, double toAngle);
@@ -226,7 +232,7 @@ void Graphics_innerRectangle (Graphics me, double x1, double x2, double y1, doub
 
 extern Graphics_Colour Graphics_BLACK, Graphics_WHITE, Graphics_RED, Graphics_GREEN, Graphics_BLUE,
 	Graphics_CYAN, Graphics_MAGENTA, Graphics_YELLOW, Graphics_MAROON, Graphics_LIME, Graphics_NAVY, Graphics_TEAL,
-	Graphics_PURPLE, Graphics_OLIVE, Graphics_PINK, Graphics_SILVER, Graphics_GREY;
+	Graphics_PURPLE, Graphics_OLIVE, Graphics_PINK, Graphics_SILVER, Graphics_GREY, Graphics_WINDOW_BACKGROUND_COLOUR;
 const wchar_t * Graphics_Colour_name (Graphics_Colour colour);
 static inline bool Graphics_Colour_equal (Graphics_Colour colour1, Graphics_Colour colour2) {
 	return colour1. red == colour2. red && colour1. green == colour2. green && colour1. blue == colour2. blue;
@@ -252,17 +258,20 @@ void Graphics_unhighlight2 (Graphics me, double x1, double x2, double y1, double
 #define Graphics_TOP  2
 #define Graphics_BASELINE  3
 void Graphics_setTextAlignment (Graphics me, int horizontal, int vertical);
+
 void Graphics_setFont (Graphics me, enum kGraphics_font font);
 void Graphics_setFontSize (Graphics me, int height);
-void Graphics_setFontStyle (Graphics me, int style);
-void Graphics_setItalic (Graphics me, bool onoff);
-void Graphics_setBold (Graphics me, bool onoff);
-void Graphics_setCode (Graphics me, bool onoff);
+
 #define Graphics_NORMAL  0
 #define Graphics_BOLD  1
 #define Graphics_ITALIC  2
 #define Graphics_BOLD_ITALIC  3
 #define Graphics_CODE  4
+void Graphics_setFontStyle (Graphics me, int style);
+
+void Graphics_setItalic (Graphics me, bool onoff);
+void Graphics_setBold (Graphics me, bool onoff);
+void Graphics_setCode (Graphics me, bool onoff);
 void Graphics_setTextRotation (Graphics me, double angle);
 void Graphics_setTextRotation_vector (Graphics me, double dx, double dy);
 void Graphics_setWrapWidth (Graphics me, double wrapWidth);
@@ -278,13 +287,17 @@ void Graphics_setUnderscoreIsSubscript (Graphics me, bool isSubscript);
 void Graphics_setDollarSignIsCode (Graphics me, bool isCode);
 void Graphics_setAtSignIsLink (Graphics me, bool isLink);
 
-void Graphics_setLineType (Graphics me, int lineType);
 #define Graphics_DRAWN  0
 #define Graphics_DOTTED  1
 #define Graphics_DASHED  2
 #define Graphics_DASHED_DOTTED  3
+void Graphics_setLineType (Graphics me, int lineType);
+
 void Graphics_setLineWidth (Graphics me, double lineWidth);
-void Graphics_setArrowSize (Graphics me, double arrorSize);
+void Graphics_setArrowSize (Graphics me, double arrowSize);
+void Graphics_setSpeckleSize (Graphics me, double speckleSize);
+
+void Graphics_setColourScale (Graphics me, enum kGraphics_colourScale colourScale);
 
 void Graphics_inqViewport (Graphics me, double *x1NDC, double *x2NDC, double *y1NDC, double *y2NDC);
 void Graphics_inqWindow (Graphics me, double *x1WC, double *x2WC, double *y1WC, double *y2WC);
@@ -294,7 +307,9 @@ int Graphics_inqFontStyle (Graphics me);
 int Graphics_inqLineType (Graphics me);
 double Graphics_inqLineWidth (Graphics me);
 double Graphics_inqArrowSize (Graphics me);
+double Graphics_inqSpeckleSize (Graphics me);
 Graphics_Colour Graphics_inqColour (Graphics me);
+enum kGraphics_colourScale Graphics_inqColourScale (Graphics me);
 
 void Graphics_contour (Graphics me, double **z,
 	long ix1, long ix2, double x1WC, double x2WC, long iy1, long iy2, double y1WC, double y2WC, double height);
@@ -359,5 +374,7 @@ void Graphics_getMouseLocation (Graphics me, double *xWC, double *yWC);
 
 void Graphics_nextSheetOfPaper (Graphics me);
 
+void Graphics_prefs ();
+
 /* End of file Graphics.h */
 #endif
diff --git a/sys/GraphicsP.h b/sys/GraphicsP.h
index 8c293bd..1a23c06 100644
--- a/sys/GraphicsP.h
+++ b/sys/GraphicsP.h
@@ -2,7 +2,7 @@
 #define _GraphicsP_h_
 /* GraphicsP.h
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -24,6 +24,7 @@
 
 #if defined (_WIN32)
 	#include <windowsx.h>
+	#include <gdiplus.h>
 #elif defined (macintosh)
 	#include "macport_on.h"
     #if useCarbon
@@ -32,7 +33,7 @@
 	#include "macport_off.h"
 #endif
 
-void Graphics_init (Graphics me);
+void Graphics_init (Graphics me, int resolution);
 /*
 	Postconditions:
 		my font == Graphics_FONT_HELVETICA;
@@ -44,14 +45,24 @@ void Graphics_init (Graphics me);
 #define kGraphics_font_IPATIMES  (kGraphics_font_MAX + 2)
 #define kGraphics_font_IPAPALATINO  (kGraphics_font_MAX + 3)
 #define kGraphics_font_DINGBATS  (kGraphics_font_MAX + 4)
+#define kGraphics_font_CHINESE  (kGraphics_font_MAX + 5)
+#define kGraphics_font_JAPANESE  (kGraphics_font_MAX + 6)
 
 Thing_define (GraphicsScreen, Graphics) {
 	// new data:
 	public:
-		#if defined (UNIX)
+		bool d_isPng;
+		structMelderFile d_file;
+		#if defined (NO_GRAPHICS)
+		#elif defined (UNIX)
 			GdkDisplay *d_display;
-			GdkDrawable *d_window;
-			GdkGC *d_gdkGraphicsContext;
+			#if ALLOW_GDK_DRAWING
+				GdkDrawable *d_window;
+				GdkGC *d_gdkGraphicsContext;
+			#else
+				GdkWindow *d_window;
+			#endif
+			cairo_surface_t *d_cairoSurface;
 			cairo_t *d_cairoGraphicsContext;
 		#elif defined (_WIN32)
 			HWND d_winWindow;
@@ -61,6 +72,8 @@ Thing_define (GraphicsScreen, Graphics) {
 			HBRUSH d_winBrush;
 			bool d_fatNonSolid;
 			bool d_useGdiplus;
+			HBITMAP d_gdiBitmap;
+			Gdiplus::Bitmap *d_gdiplusBitmap;
 		#elif defined (macintosh)
             #if useCarbon
                 GrafPtr d_macPort;
@@ -70,23 +83,24 @@ Thing_define (GraphicsScreen, Graphics) {
 			int d_macFont, d_macStyle;
 			int d_depth;
 			RGBColor d_macColour;
+			uint8_t *d_bits;
 			CGContextRef d_macGraphicsContext;
 		#endif
 	// overridden methods:
 	protected:
 		virtual void v_destroy ();
-		virtual void v_polyline (long numberOfPoints, long *xyDC, bool close);
-		virtual void v_fillArea (long numberOfPoints, long *xyDC);
-		virtual void v_rectangle (long x1DC, long x2DC, long y1DC, long y2DC);
-		virtual void v_fillRectangle (long x1DC, long x2DC, long y1DC, long y2DC);
+		virtual void v_polyline (long numberOfPoints, double *xyDC, bool close);
+		virtual void v_fillArea (long numberOfPoints, double *xyDC);
+		virtual void v_rectangle (double x1DC, double x2DC, double y1DC, double y2DC);
+		virtual void v_fillRectangle (double x1DC, double x2DC, double y1DC, double y2DC);
 		virtual void v_circle (double xDC, double yDC, double rDC);
-		virtual void v_ellipse (long x1DC, long x2DC, long y1DC, long y2DC);
-		virtual void v_arc (long xDC, long yDC, long rDC, double fromAngle, double toAngle);
-		virtual void v_fillCircle (long xDC, long yDC, long rDC);
-		virtual void v_fillEllipse (long x1DC, long x2DC, long y1DC, long y2DC);
-		virtual void v_button (long x1DC, long x2DC, long y1DC, long y2DC);
-		virtual void v_roundedRectangle (long x1DC, long x2DC, long y1DC, long y2DC, long r);
-		virtual void v_arrowHead (long xDC, long yDC, double angle);
+		virtual void v_ellipse (double x1DC, double x2DC, double y1DC, double y2DC);
+		virtual void v_arc (double xDC, double yDC, double rDC, double fromAngle, double toAngle);
+		virtual void v_fillCircle (double xDC, double yDC, double rDC);
+		virtual void v_fillEllipse (double x1DC, double x2DC, double y1DC, double y2DC);
+		virtual void v_button (double x1DC, double x2DC, double y1DC, double y2DC);
+		virtual void v_roundedRectangle (double x1DC, double x2DC, double y1DC, double y2DC, double r);
+		virtual void v_arrowHead (double xDC, double yDC, double angle);
 		virtual bool v_mouseStillDown ();
 		virtual void v_getMouseLocation (double *xWC, double *yWC);
 		virtual void v_flushWs ();
@@ -94,7 +108,8 @@ Thing_define (GraphicsScreen, Graphics) {
 		virtual void v_updateWs ();
 };
 
-#if defined (UNIX)
+#if defined (NO_GRAPHICS)
+#elif defined (UNIX)
 	#define mac 0
 	#define win 0
 	#define cairo 1
@@ -124,16 +139,16 @@ Thing_define (GraphicsPostscript, Graphics) {
 		int job, eps, pageNumber, lastSize;
 	// overridden methods:
 		virtual void v_destroy ();
-		virtual void v_polyline (long numberOfPoints, long *xyDC, bool close);
-		virtual void v_fillArea (long numberOfPoints, long *xyDC);
-		virtual void v_rectangle (long x1DC, long x2DC, long y1DC, long y2DC);
-		virtual void v_fillRectangle (long x1DC, long x2DC, long y1DC, long y2DC);
+		virtual void v_polyline (long numberOfPoints, double *xyDC, bool close);
+		virtual void v_fillArea (long numberOfPoints, double *xyDC);
+		virtual void v_rectangle (double x1DC, double x2DC, double y1DC, double y2DC);
+		virtual void v_fillRectangle (double x1DC, double x2DC, double y1DC, double y2DC);
 		virtual void v_circle (double xDC, double yDC, double rDC);
-		virtual void v_ellipse (long x1DC, long x2DC, long y1DC, long y2DC);
-		virtual void v_arc (long xDC, long yDC, long rDC, double fromAngle, double toAngle);
-		virtual void v_fillCircle (long xDC, long yDC, long rDC);
-		virtual void v_fillEllipse (long x1DC, long x2DC, long y1DC, long y2DC);
-		virtual void v_arrowHead (long xDC, long yDC, double angle);
+		virtual void v_ellipse (double x1DC, double x2DC, double y1DC, double y2DC);
+		virtual void v_arc (double xDC, double yDC, double rDC, double fromAngle, double toAngle);
+		virtual void v_fillCircle (double xDC, double yDC, double rDC);
+		virtual void v_fillEllipse (double x1DC, double x2DC, double y1DC, double y2DC);
+		virtual void v_arrowHead (double xDC, double yDC, double angle);
 };
 
 /* Opcodes for recording. */
@@ -162,7 +177,8 @@ 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, CELL_ARRAY_COLOUR, IMAGE_COLOUR, SET_COLOUR_SCALE
+	/* 161 */ POLYLINE_CLOSED, CELL_ARRAY_COLOUR, IMAGE_COLOUR, SET_COLOUR_SCALE,
+	/* 165 */ SET_SPECKLE_SIZE, SPECKLE
 };
 
 void _GraphicsScreen_text_init (GraphicsScreen me);
@@ -170,12 +186,14 @@ void _Graphics_fillRectangle (Graphics me, long x1DC, long x2DC, long y1DC, long
 void _Graphics_setColour (Graphics me, Graphics_Colour colour);
 void _Graphics_setGrey (Graphics me, double grey);
 void _Graphics_colour_init (Graphics me);
-bool _GraphicsMac_tryToInitializeAtsuiFonts (void);
+bool _GraphicsMac_tryToInitializeFonts (void);
 
 #ifdef macintosh
 	void GraphicsQuartz_initDraw (GraphicsScreen me);
 	void GraphicsQuartz_exitDraw (GraphicsScreen me);
 #endif
 
+extern enum kGraphics_cjkFontStyle theGraphicsCjkFontStyle;
+
 /* End of file GraphicsP.h */
 #endif
diff --git a/sys/GraphicsPostscript.cpp b/sys/GraphicsPostscript.cpp
index 92994f4..b07bdc0 100644
--- a/sys/GraphicsPostscript.cpp
+++ b/sys/GraphicsPostscript.cpp
@@ -1,6 +1,6 @@
 /* GraphicsPostscript.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -136,9 +136,7 @@ Graphics Graphics_create_postscriptjob (MelderFile file, int resolution, enum kG
 	my postScript = true, my yIsZeroAtTheTop = false, my languageLevel = 2;
 	my job = TRUE, my eps = FALSE, my printer = FALSE;
 	my d_printf = (int (*)(void *, const char*, ...)) fprintf;
-	Graphics_init (me.peek());
-	my resolution = resolution;   /* Virtual resolution; may not be equal to that of the printer; */
-					/* there is no problem if this always equals 600 dpi. */
+	Graphics_init (me.peek(), resolution);   // virtual resolution; may differ from that of the printer; OK if always 600 dpi
 	my photocopyable = spots == kGraphicsPostscript_spots_PHOTOCOPYABLE;
 	if (my photocopyable) { my spotsDensity = 85; my spotsAngle = 35; }
 	else { my spotsDensity = 106; my spotsAngle = 46; }
@@ -206,9 +204,7 @@ Graphics Graphics_create_epsfile (MelderFile file, int resolution, enum kGraphic
 	#else
 		my d_printf = (int (*)(void *, const char*, ...)) fprintf;
 	#endif
-	Graphics_init (me.peek());
-	my resolution = resolution;   /* Virtual resolution; may not be equal to that of the printer; */
-					/* there is no problem if this always equals 600 dpi. */
+	Graphics_init (me.peek(), resolution);   // virtual resolution; may differ from that of the printer; OK if always 600 dpi
 	my photocopyable = spots == kGraphicsPostscript_spots_PHOTOCOPYABLE;
 	if (my photocopyable) { my spotsDensity = 85; my spotsAngle = 35; }
 	else { my spotsDensity = 106; my spotsAngle = 46; }
@@ -253,8 +249,7 @@ Graphics Graphics_create_postscriptprinter (void) {
 	my postScript = TRUE, my languageLevel = 2;
 	my job = FALSE, my eps = FALSE, my printer = TRUE;
 	my d_printf = Printer_postScript_printf;
-	Graphics_init (me);
-	my resolution = thePrinter. resolution;   /* Virtual resolution. */
+	Graphics_init (me, thePrinter. resolution);   // virtual resolution
 	my photocopyable = thePrinter. spots == kGraphicsPostscript_spots_PHOTOCOPYABLE;
 	if (my photocopyable) { my spotsDensity = 85; my spotsAngle = 35; }
 	else { my spotsDensity = 106; my spotsAngle = 46; }
diff --git a/sys/GraphicsScreen.cpp b/sys/GraphicsScreen.cpp
index b3668b4..867f8e2 100644
--- a/sys/GraphicsScreen.cpp
+++ b/sys/GraphicsScreen.cpp
@@ -1,6 +1,6 @@
 /* GraphicsScreen.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-2012,2014 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
@@ -34,7 +34,9 @@
 #include "Printer.h"
 #include "GuiP.h"
 
-#if win
+#if gtk
+	#include <cairo/cairo-pdf.h>
+#elif win
 	//#include "winport_on.h"
 	#include <gdiplus.h>
 	//#include "winport_off.h"
@@ -49,11 +51,7 @@
 	#include "macport_on.h"
 	static RGBColor theBlackColour = { 0, 0, 0 };
 	static bool _GraphicsMacintosh_tryToInitializeQuartz (void) {
-		#if cocoa
-			return true;
-		#else
-			return _GraphicsMac_tryToInitializeAtsuiFonts ();
-		#endif
+		return _GraphicsMac_tryToInitializeFonts ();
 	}
 #endif
 
@@ -61,14 +59,29 @@ Thing_implement (GraphicsScreen, Graphics, 0);
 
 void structGraphicsScreen :: v_destroy () {
 	#if cairo
-		if (d_gdkGraphicsContext != NULL) {
-			g_object_unref (d_gdkGraphicsContext);			
-			d_gdkGraphicsContext = NULL;
-		}
+		#if ALLOW_GDK_DRAWING
+			if (d_gdkGraphicsContext != NULL) {
+				g_object_unref (d_gdkGraphicsContext);			
+				d_gdkGraphicsContext = NULL;
+			}
+		#endif
 		if (d_cairoGraphicsContext != NULL) {
 			cairo_destroy (d_cairoGraphicsContext);
 			d_cairoGraphicsContext = NULL;
 		}
+		if (d_cairoSurface != NULL) {
+			cairo_surface_flush (d_cairoSurface);
+			if (d_isPng) {
+				#if 1
+					cairo_surface_write_to_png (d_cairoSurface, Melder_peekWcsToUtf8 (d_file. path));
+				#else
+					unsigned char *bitmap = cairo_image_surface_get_data (my d_cairoSurface);   // peeking into the internal bits
+					// copy bitmap to PNG structure created with the PNG library
+					// save the PNG tructure to a file
+				#endif
+			}
+			cairo_surface_destroy (d_cairoSurface);
+		}
 	#elif win
 		if (d_gdiGraphicsContext != NULL) {
 			SelectPen (d_gdiGraphicsContext, GetStockPen (BLACK_PEN));
@@ -83,25 +96,132 @@ void structGraphicsScreen :: v_destroy () {
 			DeleteObject (d_winBrush);
 			d_winBrush = NULL;
 		}
+		if (d_isPng && d_gdiBitmap) {
+			trace ("saving the filled bitmap to a PNG file");
+			/*
+			 * Deselect the bitmap from the device context (otherwise GetDIBits won't work).
+			 */
+			//SelectBitmap (d_gdiGraphicsContext, NULL);
+			//SelectBitmap (d_gdiGraphicsContext, CreateCompatibleBitmap (NULL, 1, 1));
+
+			BITMAP bitmap;
+			GetObject (d_gdiBitmap, sizeof (BITMAP), & bitmap);
+			int width = bitmap. bmWidth, height = bitmap. bmHeight;
+			trace ("width %d, height %d", width, height);
+
+			/*
+			 * Get the bits from the HBITMAP;
+			 */
+			struct { BITMAPINFOHEADER header; } bitmapInfo;
+			bitmapInfo. header.biSize = sizeof (BITMAPINFOHEADER);
+			bitmapInfo. header.biWidth = width;
+			bitmapInfo. header.biHeight = height;
+			bitmapInfo. header.biPlanes = 1;
+			bitmapInfo. header.biBitCount = 32;
+			bitmapInfo. header.biCompression = BI_RGB;
+			bitmapInfo. header.biSizeImage = 0;
+			bitmapInfo. header.biXPelsPerMeter = 0;
+			bitmapInfo. header.biYPelsPerMeter = 0;
+			bitmapInfo. header.biClrUsed = 0;
+			bitmapInfo. header.biClrImportant = 0;
+			unsigned char *bits = Melder_calloc (unsigned char, 4 * width * height);
+			//HANDLE handle = GlobalAlloc (GHND, 4 * width * height);
+			//unsigned char *bits = (unsigned char *) GlobalLock (handle);
+			int numberOfLinesScanned = GetDIBits (GetDC (NULL), d_gdiBitmap, 0, height, bits, (BITMAPINFO *) & bitmapInfo, DIB_RGB_COLORS);
+			trace ("%d lines scanned", numberOfLinesScanned);
+
+			trace ("creating a savable bitmap");
+
+			//Gdiplus::Bitmap gdiplusBitmap (width, height, PixelFormat32bppARGB);
+			Gdiplus::Bitmap gdiplusBitmap ((BITMAPINFO *) & bitmapInfo, bits);
+			gdiplusBitmap. SetResolution (resolution, resolution);
+
+			trace ("copying the device-independent bits to the savable bitmap.");
+
+			/*
+			for (long irow = 1; irow <= height; irow ++) {
+				for (long icol = 1; icol <= width; icol ++) {
+					unsigned char blue = *bits ++, green = *bits ++, red = *bits ++, alpha = 255 - *bits ++;
+					Gdiplus::Color gdiplusColour (alpha, red, green, blue);
+					gdiplusBitmap. SetPixel (icol - 1, height - irow, gdiplusColour);
+				}
+			}
+			*/
+
+			trace ("saving");
+
+			UINT numberOfImageEncoders, sizeOfImageEncoderArray;
+			Gdiplus::GetImageEncodersSize (& numberOfImageEncoders, & sizeOfImageEncoderArray);
+			Gdiplus::ImageCodecInfo *imageEncoderInfos = Melder_malloc (Gdiplus::ImageCodecInfo, sizeOfImageEncoderArray);
+			Gdiplus::GetImageEncoders (numberOfImageEncoders, sizeOfImageEncoderArray, imageEncoderInfos);
+			for (int iencoder = 0; iencoder < numberOfImageEncoders; iencoder ++) {
+				if (Melder_wcsequ (imageEncoderInfos [iencoder]. MimeType, L"image/png")) {
+					gdiplusBitmap. Save (d_file. path, & imageEncoderInfos [iencoder]. Clsid, NULL);
+				}
+			}
+
+			trace ("cleaning up");
+
+			Melder_free (imageEncoderInfos);
+			//bits -= 4 * width * height;
+			Melder_free (bits);
+			//GlobalUnlock (handle);
+			//GlobalFree (handle);
+			DeleteObject (d_gdiBitmap);
+		}
 		/*
 		 * No ReleaseDC here, because we have not created it ourselves,
 		 * not even with GetDC.
 		 */
 	#elif mac
         #if useCarbon
-            if (d_macPort == NULL) {
+            if (d_macPort == NULL && ! d_isPng) {
                 CGContextEndPage (d_macGraphicsContext);
                 CGContextRelease (d_macGraphicsContext);
             }
-
         #else
-            if (d_macView == NULL) {
+            if (d_macView == NULL && ! d_isPng) {
                 CGContextEndPage (d_macGraphicsContext);
                 CGContextRelease (d_macGraphicsContext);
             }
         #endif
+		if (d_isPng && d_macGraphicsContext) {
+			/*
+			 * Turn the offscreen bitmap into an image.
+			 */
+			CGImageRef image = CGBitmapContextCreateImage (d_macGraphicsContext);
+			Melder_assert (image != NULL);
+			//CGContextRelease (d_macGraphicsContext);
+			/*
+			 * Create a dictionary with resolution properties.
+			 */
+			CFTypeRef keys [2], values [2];
+			keys [0] = kCGImagePropertyDPIWidth;
+			keys [1] = kCGImagePropertyDPIHeight;
+			float resolution_float = resolution;
+			values [1] = values [0] = CFNumberCreate (NULL, kCFNumberFloatType, & resolution_float);
+			CFDictionaryRef properties = CFDictionaryCreate (NULL,
+				(const void **) keys, (const void **) values, 2,
+				& kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
+			Melder_assert (properties != NULL);
+			/*
+			 */
+			CFURLRef url = CFURLCreateWithFileSystemPath (NULL,
+				(CFStringRef) Melder_peekWcsToCfstring (d_file. path), kCFURLPOSIXPathStyle, false);
+			CGImageDestinationRef imageDestination = CGImageDestinationCreateWithURL (url, kUTTypePNG, 1, NULL);
+			Melder_assert (imageDestination != NULL);
+			CGImageDestinationAddImage (imageDestination, image, properties);
+			CGImageRelease (image);
+			CFRelease (properties);
+			CGImageDestinationFinalize (imageDestination);
+			CFRelease (imageDestination);
+			CFRelease (url);
+		}
+		Melder_free (d_bits);
 	#endif
+	trace ("destroying parent");
 	GraphicsScreen_Parent :: v_destroy ();
+	trace ("exit");
 }
 
 void structGraphicsScreen :: v_flushWs () {
@@ -254,7 +374,9 @@ void structGraphicsScreen :: v_updateWs () {
 			cairo_rectangle (d_cairoGraphicsContext, rect.x, rect.y, rect.width, rect.height);
 			cairo_clip (d_cairoGraphicsContext);
 		}
-		gdk_window_clear (d_window);
+		#if ALLOW_GDK_DRAWING
+			gdk_window_clear (d_window);
+		#endif
 		gdk_window_invalidate_rect (d_window, & rect, true);
 		//gdk_window_process_updates (d_window, true);
 	#elif cocoa
@@ -299,18 +421,21 @@ void Graphics_updateWs (Graphics me) {
 		my v_updateWs ();
 }
 
-static int GraphicsScreen_init (GraphicsScreen me, void *voidDisplay, void *voidWindow, int resolution) {
+static int GraphicsScreen_init (GraphicsScreen me, void *voidDisplay, void *voidWindow) {
 
 	/* Fill in new members. */
 
 	#if cairo
 		my d_display = (GdkDisplay *) gdk_display_get_default ();
 		_GraphicsScreen_text_init (me);
-		my resolution = 100;
-		trace ("retrieving window");
-		my d_window = GDK_DRAWABLE (GTK_WIDGET (voidDisplay) -> window);
-		trace ("retrieved window");
-		my d_gdkGraphicsContext = gdk_gc_new (my d_window);
+		#if ALLOW_GDK_DRAWING
+			trace ("retrieving window");
+			my d_window = GDK_DRAWABLE (GTK_WIDGET (voidDisplay) -> window);
+			trace ("retrieved window");
+			my d_gdkGraphicsContext = gdk_gc_new (my d_window);
+		#else
+			my d_window = gtk_widget_get_window (GTK_WIDGET (voidDisplay));
+		#endif
 		my d_cairoGraphicsContext = gdk_cairo_create (my d_window);
 	#elif win
 		if (my printer) {
@@ -323,7 +448,6 @@ static int GraphicsScreen_init (GraphicsScreen me, void *voidDisplay, void *void
 			my d_gdiGraphicsContext = GetDC (my d_winWindow);   // window must have a constant display context; see XtInitialize ()
 		}
 		Melder_assert (my d_gdiGraphicsContext != NULL);
-		my resolution = resolution;
 		SetBkMode (my d_gdiGraphicsContext, TRANSPARENT);   // not the default!
 		/*
 		 * Create pens and brushes.
@@ -337,13 +461,18 @@ static int GraphicsScreen_init (GraphicsScreen me, void *voidDisplay, void *void
 		(void) voidDisplay;
         #if useCarbon
             my d_macPort = (GrafPtr) voidWindow;
+			(void) my d_macGraphicsContext;   // will be retrieved from QuickDraw with every drawing command!
         #else
-            my d_macView = (NSView*) voidWindow;
+			if (my printer) {
+				my d_macView = (NSView *) voidWindow;   // in case we do view-based printing
+				//my d_macGraphicsContext = (CGContextRef) voidWindow;   // in case we do context-based printing
+			} else {
+				my d_macView = (NSView *) voidWindow;
+				(void) my d_macGraphicsContext;   // will be retrieved from Core Graphics with every drawing command!
+			}
         #endif
 		my d_macColour = theBlackColour;
-		my resolution = resolution;
 		my d_depth = my resolution > 150 ? 1 : 8;   /* BUG: replace by true depth (1=black/white) */
-		(void) my d_macGraphicsContext;   // will be retreived from QuickDraw with every drawing command!
 		_GraphicsScreen_text_init (me);
 	#endif
 	return 1;
@@ -353,12 +482,12 @@ Graphics Graphics_create_screen (void *display, void *window, int resolution) {
 	GraphicsScreen me = Thing_new (GraphicsScreen);
 	my screen = true;
 	my yIsZeroAtTheTop = true;
-	Graphics_init (me);
+	Graphics_init (me, resolution);
 	Graphics_setWsViewport ((Graphics) me, 0, 100, 0, 100);
 	#if mac && useCarbon
-		GraphicsScreen_init (me, display, GetWindowPort ((WindowRef) window), resolution);
+		GraphicsScreen_init (me, display, GetWindowPort ((WindowRef) window));
 	#else
-		GraphicsScreen_init (me, display, window, resolution);
+		GraphicsScreen_init (me, display, window);
 	#endif
 	return (Graphics) me;
 }
@@ -371,9 +500,10 @@ Graphics Graphics_create_screenPrinter (void *display, void *window) {
 	#ifdef macintosh
 		_GraphicsMacintosh_tryToInitializeQuartz ();
 	#endif
-	Graphics_init (me);
+	Graphics_init (me, thePrinter. resolution);
 	my paperWidth = (double) thePrinter. paperWidth / thePrinter. resolution;
 	my paperHeight = (double) thePrinter. paperHeight / thePrinter. resolution;
+	//NSLog (@"Graphics_create_screenPrinter: %f %f", my paperWidth, my paperHeight);
 	my d_x1DC = my d_x1DCmin = thePrinter. resolution / 2;
 	my d_x2DC = my d_x2DCmax = (my paperWidth - 0.5) * thePrinter. resolution;
 	my d_y1DC = my d_y1DCmin = thePrinter. resolution / 2;
@@ -388,7 +518,7 @@ Graphics Graphics_create_screenPrinter (void *display, void *window) {
 		my d_y2DC -= GetDeviceCaps ((HDC) window, PHYSICALOFFSETY);
 	#endif
 	Graphics_setWsWindow ((Graphics) me, 0, my paperWidth - 1.0, 13.0 - my paperHeight, 12.0);
-	GraphicsScreen_init (me, display, window, thePrinter. resolution);
+	GraphicsScreen_init (me, display, window);
 	return (Graphics) me;
 }
 
@@ -434,25 +564,24 @@ Graphics Graphics_create_xmdrawingarea (GuiDrawingArea w) {
 	#elif mac
 		_GraphicsMacintosh_tryToInitializeQuartz ();
 	#endif
-	Graphics_init (me);
-	#if mac 
-    #if useCarbon
+	#if mac
+		Graphics_init (me, Gui_getResolution (NULL));
+		#if useCarbon
             GraphicsScreen_init (me,
                 XtDisplay (my d_drawingArea -> d_widget),
-                GetWindowPort ((WindowRef) XtWindow (my d_drawingArea -> d_widget)),
-                Gui_getResolution (NULL));
-    #else
+                GetWindowPort ((WindowRef) XtWindow (my d_drawingArea -> d_widget)));
+		#else
             GraphicsScreen_init (me,
                                  my d_drawingArea -> d_widget,
-                                 my d_drawingArea -> d_widget,
-                                 Gui_getResolution (NULL));
-    #endif
-    
+                                 my d_drawingArea -> d_widget);
+		#endif
 	#else
 		#if gtk
-			GraphicsScreen_init (me, GTK_WIDGET (my d_drawingArea -> d_widget), GTK_WIDGET (my d_drawingArea -> d_widget), Gui_getResolution (my d_drawingArea -> d_widget));
+			Graphics_init (me, Gui_getResolution (my d_drawingArea -> d_widget));
+			GraphicsScreen_init (me, GTK_WIDGET (my d_drawingArea -> d_widget), GTK_WIDGET (my d_drawingArea -> d_widget));
 		#elif motif
-			GraphicsScreen_init (me, XtDisplay (my d_drawingArea -> d_widget), XtWindow (my d_drawingArea -> d_widget), Gui_getResolution (my d_drawingArea -> d_widget));
+			Graphics_init (me, Gui_getResolution (my d_drawingArea -> d_widget));
+			GraphicsScreen_init (me, XtDisplay (my d_drawingArea -> d_widget), XtWindow (my d_drawingArea -> d_widget));
 		#endif
 	#endif
 
@@ -477,20 +606,110 @@ Graphics Graphics_create_xmdrawingarea (GuiDrawingArea w) {
 	return (Graphics) me;
 }
 
-Graphics Graphics_create_pdffile (MelderFile file, int resolution,
+Graphics Graphics_create_pngfile (MelderFile file, int resolution,
 	double x1inches, double x2inches, double y1inches, double y2inches)
 {
-	GraphicsScreen me = Thing_new (GraphicsScreen);
+	autoGraphicsScreen me = Thing_new (GraphicsScreen);
 	my screen = true;
 	my yIsZeroAtTheTop = true;
 	#ifdef macintosh
 		_GraphicsMacintosh_tryToInitializeQuartz ();
 	#endif
-	Graphics_init (me);
-	my resolution = resolution;
+	Graphics_init (me.peek(), resolution);
+	my d_isPng = true;
+	my d_file = *file;
+	my d_x1DC = my d_x1DCmin = 0;
+	my d_x2DC = my d_x2DCmax = (x2inches - x1inches) * resolution;
+	my d_y1DC = my d_y1DCmin = 0;
+	my d_y2DC = my d_y2DCmax = (y2inches - y1inches) * resolution;
+	Graphics_setWsWindow ((Graphics) me.peek(), x1inches, x2inches, y1inches, y2inches);
+	#if gtk
+		my d_cairoSurface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+			(x2inches - x1inches) * resolution, (y2inches - y1inches) * resolution);
+		my d_cairoGraphicsContext = cairo_create (my d_cairoSurface);
+		//cairo_scale (my d_cairoGraphicsContext, 72.0 / resolution, 72.0 / resolution);
+		/*
+		 * Fill in the whole area with a white background.
+		 */
+		cairo_set_source_rgb (my d_cairoGraphicsContext, 1.0, 1.0, 1.0);
+		cairo_rectangle (my d_cairoGraphicsContext, 0, 0, my d_x2DC, my d_y2DC);
+		cairo_fill (my d_cairoGraphicsContext);
+		cairo_set_source_rgb (my d_cairoGraphicsContext, 0.0, 0.0, 0.0);
+	#elif mac
+		long width = (x2inches - x1inches) * resolution, height = (y2inches - y1inches) * resolution;
+		long stride = width * 4;
+		stride = (stride + 15) & ~15;   // CommonCode/AppDrawing.c: "a multiple of 16 bytes, for best performance"
+		my d_bits = Melder_malloc (uint8_t, stride * height);
+		static CGColorSpaceRef colourSpace = NULL;
+		if (colourSpace == NULL) {
+			colourSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);
+			Melder_assert (colourSpace != NULL);
+		}
+		my d_macGraphicsContext = CGBitmapContextCreate (my d_bits,
+			width, height,
+			8,   // bits per component
+			stride,
+			colourSpace,
+			kCGImageAlphaPremultipliedLast);
+    	if (my d_macGraphicsContext == NULL)
+			Melder_throw ("Could not create PNG file ", file, ".");
+		CGRect rect = CGRectMake (0, 0, width, height);
+		CGContextSetAlpha (my d_macGraphicsContext, 1.0);
+		CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeNormal);
+		CGContextSetRGBFillColor (my d_macGraphicsContext, 1.0, 1.0, 1.0, 1.0);
+		CGContextFillRect (my d_macGraphicsContext, rect);
+		CGContextTranslateCTM (my d_macGraphicsContext, 0, height);
+		CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
+	#elif win
+		my metafile = TRUE;
+		HDC screenDC = GetDC (NULL);
+		my d_gdiGraphicsContext = CreateCompatibleDC (screenDC);
+		trace ("d_gdiGraphicsContext %p", my d_gdiGraphicsContext);
+		Melder_assert (my d_gdiGraphicsContext != NULL);
+		my d_gdiBitmap = CreateCompatibleBitmap (screenDC, (x2inches - x1inches) * resolution, (y2inches - y1inches) * resolution);
+		trace ("d_gdiBitmap %p", my d_gdiBitmap);
+		SelectObject (my d_gdiGraphicsContext, my d_gdiBitmap);
+		trace ("bitmap selected into device context");
+		my resolution = resolution;
+		SetBkMode (my d_gdiGraphicsContext, TRANSPARENT);
+		my d_winPen = CreatePen (PS_SOLID, 0, RGB (0, 0, 0));
+		my d_winBrush = CreateSolidBrush (RGB (0, 0, 0));
+		SetTextAlign (my d_gdiGraphicsContext, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);
+		/*
+		 * Fill in the whole area with a white background.
+		 */
+		SelectPen (my d_gdiGraphicsContext, GetStockPen (NULL_PEN));
+		SelectBrush (my d_gdiGraphicsContext, GetStockBrush (WHITE_BRUSH));
+		Rectangle (my d_gdiGraphicsContext, 0, 0, my d_x2DC + 1, my d_y2DC + 1);   // plus 1, in order to prevent two black edges
+		SelectPen (my d_gdiGraphicsContext, GetStockPen (BLACK_PEN));
+		SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
+	#endif
+	return (Graphics) me.transfer();
+}
+
+Graphics Graphics_create_pdffile (MelderFile file, int resolution,
+	double x1inches, double x2inches, double y1inches, double y2inches)
+{
+	autoGraphicsScreen me = Thing_new (GraphicsScreen);
+	my screen = true;
+	my yIsZeroAtTheTop = true;
 	#ifdef macintosh
+		_GraphicsMacintosh_tryToInitializeQuartz ();
+	#endif
+	Graphics_init (me.peek(), resolution);
+	#if gtk
+		my d_cairoSurface = cairo_pdf_surface_create (Melder_peekWcsToUtf8 (file -> path),
+			(x2inches - x1inches) * 72.0, (y2inches - y1inches) * 72.0);
+		my d_cairoGraphicsContext = cairo_create (my d_cairoSurface);
+		my d_x1DC = my d_x1DCmin = 0;
+		my d_x2DC = my d_x2DCmax = 7.5 * resolution;
+		my d_y1DC = my d_y1DCmin = 0;
+		my d_y2DC = my d_y2DCmax = 11.0 * resolution;
+		Graphics_setWsWindow ((Graphics) me.peek(), 0, 7.5, 1.0, 12.0);
+		cairo_scale (my d_cairoGraphicsContext, 72.0 / resolution, 72.0 / resolution);
+	#elif mac
 		CFURLRef url = CFURLCreateWithFileSystemPath (NULL, (CFStringRef) Melder_peekWcsToCfstring (file -> path), kCFURLPOSIXPathStyle, false);
-		CGRect rect = CGRectMake (0, 0, (x2inches - x1inches) * 72, (y2inches - y1inches) * 72);   // don't tire PDF viewers with funny origins
+		CGRect rect = CGRectMake (0, 0, (x2inches - x1inches) * 72.0, (y2inches - y1inches) * 72.0);   // don't tire PDF viewers with funny origins
 		CFStringRef key = (CFStringRef) Melder_peekWcsToCfstring (L"Creator");
 		CFStringRef value = (CFStringRef) Melder_peekWcsToCfstring (L"Praat");
 		CFIndex numberOfValues = 1;
@@ -499,19 +718,19 @@ Graphics Graphics_create_pdffile (MelderFile file, int resolution,
 		my d_macGraphicsContext = CGPDFContextCreateWithURL (url, & rect, dictionary);
 		CFRelease (url);
 		CFRelease (dictionary);
+    	if (my d_macGraphicsContext == NULL)
+			Melder_throw ("Could not create PDF file ", file, ".");
 		my d_x1DC = my d_x1DCmin = 0;
 		my d_x2DC = my d_x2DCmax = 7.5 * resolution;
 		my d_y1DC = my d_y1DCmin = 0;
 		my d_y2DC = my d_y2DCmax = 11.0 * resolution;
-		Graphics_setWsWindow ((Graphics) me, 0, 7.5, 1.0, 12.0);
-    NSCAssert(my d_macGraphicsContext, @"nil context");
-
+		Graphics_setWsWindow ((Graphics) me.peek(), 0, 7.5, 1.0, 12.0);
 		CGContextBeginPage (my d_macGraphicsContext, & rect);
-		CGContextScaleCTM (my d_macGraphicsContext, 72.0/resolution, 72.0/resolution);
+		CGContextScaleCTM (my d_macGraphicsContext, 72.0 / resolution, 72.0 / resolution);
 		CGContextTranslateCTM (my d_macGraphicsContext, - x1inches * resolution, (12.0 - y1inches) * resolution);
 		CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
 	#endif
-	return (Graphics) me;
+	return (Graphics) me.transfer();
 }
 Graphics Graphics_create_pdf (void *context, int resolution,
 	double x1inches, double x2inches, double y1inches, double y2inches)
@@ -522,8 +741,7 @@ Graphics Graphics_create_pdf (void *context, int resolution,
 	#ifdef macintosh
 		_GraphicsMacintosh_tryToInitializeQuartz ();
 	#endif
-	Graphics_init (me);
-	my resolution = resolution;
+	Graphics_init (me, resolution);
 	#ifdef macintosh
 		my d_macGraphicsContext = static_cast <CGContext *> (context);
 		CGRect rect = CGRectMake (0, 0, (x2inches - x1inches) * 72, (y2inches - y1inches) * 72);   // don't tire PDF viewers with funny origins
@@ -532,10 +750,9 @@ Graphics Graphics_create_pdf (void *context, int resolution,
 		my d_y1DC = my d_y1DCmin = 0;
 		my d_y2DC = my d_y2DCmax = 11.0 * resolution;
 		Graphics_setWsWindow ((Graphics) me, 0, 7.5, 1.0, 12.0);
-    NSCAssert(my d_macGraphicsContext, @"nil context");
-
+    	Melder_assert (my d_macGraphicsContext != NULL);
 		CGContextBeginPage (my d_macGraphicsContext, & rect);
-		CGContextScaleCTM (my d_macGraphicsContext, 72.0/resolution, 72.0/resolution);
+		CGContextScaleCTM (my d_macGraphicsContext, 72.0 / resolution, 72.0 / resolution);
 		CGContextTranslateCTM (my d_macGraphicsContext, - x1inches * resolution, (12.0 - y1inches) * resolution);
 		CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
 	#endif
@@ -555,7 +772,7 @@ Graphics Graphics_create_pdf (void *context, int resolution,
 	void GraphicsQuartz_initDraw (GraphicsScreen me) {
 		#if useCarbon
 			if (my d_macPort) {
-					QDBeginCGContext (my d_macPort, & my d_macGraphicsContext);
+				QDBeginCGContext (my d_macPort, & my d_macGraphicsContext);
 				//CGContextSetAlpha (my macGraphicsContext, 1.0);
 				//CGContextSetAllowsAntialiasing (my macGraphicsContext, false);
 				if (my d_drawingArea != NULL) {
@@ -569,11 +786,14 @@ Graphics Graphics_create_pdf (void *context, int resolution,
         #else
             if (my d_macView) {            
                 [my d_macView lockFocus];
-                my d_macGraphicsContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
+				//if (! my printer) {
+					my d_macGraphicsContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
+				//}
                 Melder_assert (my d_macGraphicsContext != NULL);
-                GuiCocoaDrawingArea *cocoaDrawingArea = (GuiCocoaDrawingArea *) my d_drawingArea -> d_widget;
-                CGContextTranslateCTM (my d_macGraphicsContext, 0, cocoaDrawingArea.bounds.size.height);
-                CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
+				if (my printer) {
+					//CGContextTranslateCTM (my d_macGraphicsContext, 0, [my d_macView bounds]. size. height);
+					//CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
+				}
 			}
 		#endif
 	}
@@ -585,7 +805,7 @@ Graphics Graphics_create_pdf (void *context, int resolution,
 			}
         #else
             if (my d_macView) {
-                CGContextSynchronize (my d_macGraphicsContext);   // BUG: should not be needed
+                //CGContextSynchronize (my d_macGraphicsContext);   // BUG: should not be needed
                 [my d_macView unlockFocus];
             }
 		#endif
diff --git a/sys/Graphics_colour.cpp b/sys/Graphics_colour.cpp
index 2f91c9b..1c31ede 100644
--- a/sys/Graphics_colour.cpp
+++ b/sys/Graphics_colour.cpp
@@ -1,6 +1,6 @@
 /* Graphics_colour.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -33,6 +33,7 @@
  * pb 2011/01/15 Windows: inverted the colour in XOR mode
  * pb 2011/03/17 C++
  * pb 2013/08/27 Cocoa: trick: triple kCGBlendModeDifference approximates coloured XOR
+ * pb 2014/02/10 Cocoa: trick: use window cache for unhighlighting
  */
 
 #include "GraphicsP.h"
@@ -55,7 +56,8 @@ Graphics_Colour
 	Graphics_OLIVE = { 0.5, 0.5, 0.0 },
 	Graphics_PINK = { 1.0, 0.75, 0.75 },
 	Graphics_SILVER = { 0.75, 0.75, 0.75 },
-	Graphics_GREY = { 0.5, 0.5, 0.5 };
+	Graphics_GREY = { 0.5, 0.5, 0.5 },
+	Graphics_WINDOW_BACKGROUND_COLOUR = { 0.90, 0.90, 0.85 };
 
 const wchar_t * Graphics_Colour_name (Graphics_Colour colour) {
 	return
@@ -122,6 +124,11 @@ void Graphics_setColour (Graphics me, Graphics_Colour colour) {
 	if (my recording) { op (SET_RGB_COLOUR, 3); put (colour. red); put (colour. green); put (colour. blue); }
 }
 
+void Graphics_setColourScale (Graphics me, enum kGraphics_colourScale colourScale) {
+	my colourScale = colourScale;
+	if (my recording) { op (SET_COLOUR_SCALE, 1); put (colourScale); }
+}
+
 void _Graphics_setGrey (Graphics graphics, double fgrey) {
 	if (graphics -> screen) {
 		GraphicsScreen me = static_cast <GraphicsScreen> (graphics);
@@ -142,7 +149,7 @@ void _Graphics_setGrey (Graphics graphics, double fgrey) {
 			if (fgrey < 0.0) fgrey = 0.0; else if (fgrey > 1.0) fgrey = 1.0;
 			my d_macColour. red = my d_macColour. green = my d_macColour. blue = fgrey * 65535;
 		#endif
-	} else if (graphics ->  postScript) {
+	} else if (graphics -> postScript) {
 		GraphicsPostscript me = static_cast <GraphicsPostscript> (graphics);
 		if (fgrey < 0.0) fgrey = 0.0; else if (fgrey > 1.0) fgrey = 1.0;
 		my d_printf (my d_file, "%.6g setgray\n", fgrey);
@@ -162,41 +169,43 @@ static void highlight (Graphics graphics, long x1DC, long x2DC, long y1DC, long
 			if (my d_cairoGraphicsContext == NULL) return;
 			int width = x2DC - x1DC, height = y1DC - y2DC;
 			if (width <= 0 || height <= 0) return;
-			gdk_gc_set_function (my d_gdkGraphicsContext, GDK_XOR);
-			GdkColor pinkXorWhite = { 0, 0x0000, 0x4000, 0x4000 }, black = { 0, 0x0000, 0x0000, 0x0000 };
-			gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & pinkXorWhite);
-			gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y2DC, width, height);
-			gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & black);
-			gdk_gc_set_function (my d_gdkGraphicsContext, GDK_COPY);
-			gdk_flush ();
+			#if ALLOW_GDK_DRAWING
+				gdk_gc_set_function (my d_gdkGraphicsContext, GDK_XOR);
+				GdkColor pinkXorWhite = { 0, 0x0000, 0x4000, 0x4000 }, black = { 0, 0x0000, 0x0000, 0x0000 };
+				gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & pinkXorWhite);
+				gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y2DC, width, height);
+				gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & black);
+				gdk_gc_set_function (my d_gdkGraphicsContext, GDK_COPY);
+				gdk_flush ();
+			#endif
 		#elif cocoa
 			int width = x2DC - x1DC, height = y1DC - y2DC;
 			if (width <= 0 || height <= 0) return;
 			GuiCocoaDrawingArea *drawingArea = (GuiCocoaDrawingArea *) my d_drawingArea -> d_widget;
 			if (drawingArea) {
-				[drawingArea lockFocus];
-				CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
-				CGContextSaveGState (context);
-				NSCAssert (context, @"nil context");
-				CGContextTranslateCTM (context, 0, drawingArea. bounds. size. height);
-				CGContextScaleCTM (context, 1.0, -1.0);
-				NSRect rect = NSMakeRect (x1DC,  y2DC, width, height);
-				CGContextSetBlendMode (context, kCGBlendModeDifference);
-				CGContextSetShouldAntialias (context, false);
-				NSColor *colour = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName: NSCalibratedRGBColorSpace];
-				double red = 0.5 + 0.5 * colour.redComponent, green = 0.5 + 0.5 * colour.greenComponent, blue = 0.5 + 0.5 * colour.blueComponent;
+				NSView *nsView = my d_macView;
 				if (direction == 1) {   // forward
+					NSRect rect = NSMakeRect (x1DC, y2DC, width, height);
+					NSRect windowRect = [nsView convertRect: rect toView: nil];
+					//windowRect.origin.x += 1;
+					//windowRect.size.width -= 2;
+					[[nsView window] cacheImageInRect: windowRect];
+					[drawingArea lockFocus];
+					CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
+					CGContextSaveGState (context);
+					CGContextSetBlendMode (context, kCGBlendModeDifference);
+					//CGContextSetBlendMode (context, kCGBlendModeDarken);
+					CGContextSetShouldAntialias (context, false);
+					NSColor *colour = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName: NSDeviceRGBColorSpace];
+					double red = 0.5 + 0.5 * colour.redComponent, green = 0.5 + 0.5 * colour.greenComponent, blue = 0.5 + 0.5 * colour.blueComponent;
 					CGContextSetRGBFillColor (context, 1.0 - red, 1.0 - green, 1.0 - blue, 1.0);
+					//CGContextSetRGBFillColor (context, red, green, blue, 1.0);
 					CGContextFillRect (context, rect);
+					CGContextRestoreGState (context);
+					[drawingArea unlockFocus];
 				} else {   // backward
-					CGContextSetRGBFillColor (context, red, green, blue, 1.0);
-					CGContextFillRect (context, rect);
-					CGContextSetRGBFillColor (context, 1.0, 1.0, 1.0, 1.0);
-					CGContextFillRect (context, rect);
+					[[nsView window] restoreCachedImage];
 				}
-				CGContextRestoreGState (context);
-				//CGContextSynchronize (context);
-				[drawingArea unlockFocus];
 			}
         #elif mac
 			Rect rect;
@@ -244,56 +253,48 @@ static void highlight2 (Graphics graphics, long x1DC, long x2DC, long y1DC, long
 			if (my d_cairoGraphicsContext == NULL) return;
 			int width = x2DC - x1DC, height = y1DC - y2DC;
 			if (width <= 0 || height <= 0) return;
-			gdk_gc_set_function (my d_gdkGraphicsContext, GDK_XOR);
-			GdkColor pinkXorWhite = { 0, 0x0000, 0x4000, 0x4000 }, black = { 0, 0x0000, 0x0000, 0x0000 };
-			gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & pinkXorWhite);
-			gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y2DC, x2DC - x1DC, y2DC_inner - y2DC); // upper
-			gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y2DC_inner, x1DC_inner - x1DC, y1DC_inner - y2DC_inner); // left part
-			gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x2DC_inner, y2DC_inner, x2DC - x2DC_inner, y1DC_inner - y2DC_inner); // right part
-			gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y1DC_inner, x2DC - x1DC, y1DC - y1DC_inner); // lower
-			gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & black);
-			gdk_gc_set_function (my d_gdkGraphicsContext, GDK_COPY);
-			gdk_flush ();
+			#if ALLOW_GDK_DRAWING
+				gdk_gc_set_function (my d_gdkGraphicsContext, GDK_XOR);
+				GdkColor pinkXorWhite = { 0, 0x0000, 0x4000, 0x4000 }, black = { 0, 0x0000, 0x0000, 0x0000 };
+				gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & pinkXorWhite);
+				gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y2DC, x2DC - x1DC, y2DC_inner - y2DC); // upper
+				gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y2DC_inner, x1DC_inner - x1DC, y1DC_inner - y2DC_inner); // left part
+				gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x2DC_inner, y2DC_inner, x2DC - x2DC_inner, y1DC_inner - y2DC_inner); // right part
+				gdk_draw_rectangle (my d_window, my d_gdkGraphicsContext, TRUE, x1DC, y1DC_inner, x2DC - x1DC, y1DC - y1DC_inner); // lower
+				gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & black);
+				gdk_gc_set_function (my d_gdkGraphicsContext, GDK_COPY);
+				gdk_flush ();
+			#endif
 		#elif cocoa
 			GuiCocoaDrawingArea *drawingArea = (GuiCocoaDrawingArea*) my d_drawingArea -> d_widget;
 			if (drawingArea) {
+				NSRect rect = NSMakeRect (x1DC, y2DC, x2DC - x1DC, y1DC - y2DC);
+				if (direction == 1) {
+					NSView *nsView = my d_macView;
+					NSRect windowRect = [nsView convertRect: rect toView: nil];
+					[[nsView window] cacheImageInRect: windowRect];
+				} else {
+					[[my d_macView window] restoreCachedImage];
+					return;
+				}
 				[drawingArea lockFocus];
-
-				my d_macGraphicsContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+				my d_macGraphicsContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
 				CGContextSaveGState (my d_macGraphicsContext);
-
-				NSCAssert (my d_macGraphicsContext, @"nil context");
-				CGContextTranslateCTM (my d_macGraphicsContext, 0, drawingArea. bounds. size. height);
-				CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
-
 				NSRect upperRect = NSMakeRect (x1DC, y2DC, x2DC - x1DC, y2DC_inner - y2DC);
 				NSRect leftRect  = NSMakeRect (x1DC, y2DC_inner, x1DC_inner - x1DC, y1DC_inner - y2DC_inner);
 				NSRect rightRect = NSMakeRect (x2DC_inner, y2DC_inner, x2DC - x2DC_inner, y1DC_inner - y2DC_inner);
 				NSRect lowerRect = NSMakeRect (x1DC, y1DC_inner, x2DC - x1DC, y1DC - y1DC_inner);
-				CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeDifference);
+				//CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeDifference);
+				CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeDarken);
 				NSColor *colour = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName: NSCalibratedRGBColorSpace];
 				double red = 0.5 + 0.5 * colour.redComponent, green = 0.5 + 0.5 * colour.greenComponent, blue = 0.5 + 0.5 * colour.blueComponent;
-				if (direction == 1) {
-					CGContextSetRGBFillColor (my d_macGraphicsContext, 1.0 - red, 1.0 - green, 1.0 - blue, 1.0);
-					CGContextFillRect (my d_macGraphicsContext, upperRect);
-					CGContextFillRect (my d_macGraphicsContext, leftRect);
-					CGContextFillRect (my d_macGraphicsContext, rightRect);
-					CGContextFillRect (my d_macGraphicsContext, lowerRect);
-				} else {
-					CGContextSetRGBFillColor (my d_macGraphicsContext, red, green, blue, 1.0);
-					CGContextFillRect (my d_macGraphicsContext, upperRect);
-					CGContextFillRect (my d_macGraphicsContext, leftRect);
-					CGContextFillRect (my d_macGraphicsContext, rightRect);
-					CGContextFillRect (my d_macGraphicsContext, lowerRect);
-					CGContextSetRGBFillColor (my d_macGraphicsContext, 1.0, 1.0, 1.0, 1.0);
-					CGContextFillRect (my d_macGraphicsContext, upperRect);
-					CGContextFillRect (my d_macGraphicsContext, leftRect);
-					CGContextFillRect (my d_macGraphicsContext, rightRect);
-					CGContextFillRect (my d_macGraphicsContext, lowerRect);
-				}
-
+				//CGContextSetRGBFillColor (my d_macGraphicsContext, 1.0 - red, 1.0 - green, 1.0 - blue, 1.0);
+				CGContextSetRGBFillColor (my d_macGraphicsContext, red, green, blue, 1.0);
+				CGContextFillRect (my d_macGraphicsContext, upperRect);
+				CGContextFillRect (my d_macGraphicsContext, leftRect);
+				CGContextFillRect (my d_macGraphicsContext, rightRect);
+				CGContextFillRect (my d_macGraphicsContext, lowerRect);
 				CGContextRestoreGState (my d_macGraphicsContext);
-				//CGContextSynchronize ( my d_macGraphicsContext);   // needed?
 				[drawingArea unlockFocus];
 			}
         #elif mac
@@ -352,14 +353,17 @@ void Graphics_xorOn (Graphics graphics, Graphics_Colour colour) {
 		GraphicsScreen me = static_cast <GraphicsScreen> (graphics);
 		#if cairo
 			GdkColor colourXorWhite = { 0,
-				(uint16_t) (colour. red * 65535.0) ^ 0xFFFF,
-				(uint16_t) (colour. green * 65535.0) ^ 0xFFFF,
-				(uint16_t) (colour. blue * 65535.0) ^ 0xFFFF };
-			gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & colourXorWhite);
-			gdk_gc_set_function (my d_gdkGraphicsContext, GDK_XOR);
-			//cairo_set_source_rgba (my d_cairoGraphicsContext, 1.0, 0.8, 0.8, 0.5);
-			//cairo_set_operator (my d_cairoGraphicsContext, CAIRO_OPERATOR_XOR);
-			gdk_flush ();
+				(uint16_t) ((uint16_t) (colour. red * 65535.0) ^ (uint16_t) 0xFFFF),
+				(uint16_t) ((uint16_t) (colour. green * 65535.0) ^ (uint16_t) 0xFFFF),
+				(uint16_t) ((uint16_t) (colour. blue * 65535.0) ^ (uint16_t) 0xFFFF) };
+			#if ALLOW_GDK_DRAWING
+				gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & colourXorWhite);
+				gdk_gc_set_function (my d_gdkGraphicsContext, GDK_XOR);
+				gdk_flush ();
+			#else
+				cairo_set_source_rgba (my d_cairoGraphicsContext, 1.0, 0.8, 0.8, 0.5);
+				cairo_set_operator (my d_cairoGraphicsContext, CAIRO_OPERATOR_XOR);
+			#endif
 		#elif win
 			SetROP2 (my d_gdiGraphicsContext, R2_XORPEN);
 			colour. red = ((uint16_t) (colour. red * 65535.0) ^ 0xFFFF) / 65535.0;
@@ -380,16 +384,19 @@ void Graphics_xorOff (Graphics graphics) {
 		GraphicsScreen me = static_cast <GraphicsScreen> (graphics);
 		#if cairo
 			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);
-			//cairo_set_source_rgba (my d_cairoGraphicsContext, 0.0, 0.0, 0.0, 1.0);
-			//cairo_set_operator (my d_cairoGraphicsContext, CAIRO_OPERATOR_OVER);
-			gdk_flush ();   // to undraw the last drawing
+			#if ALLOW_GDK_DRAWING
+				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
+			#else
+				cairo_set_source_rgba (my d_cairoGraphicsContext, 0.0, 0.0, 0.0, 1.0);
+				cairo_set_operator (my d_cairoGraphicsContext, CAIRO_OPERATOR_OVER);
+			#endif
 		#elif win
 			SetROP2 (my d_gdiGraphicsContext, R2_COPYPEN);
 			_Graphics_setColour (me, my colour);
 		#elif cocoa
-			Graphics_flushWs (graphics);   // to undraw the last drawing
+			//Graphics_flushWs (graphics);   // to undraw the last drawing
 		#elif mac
 			//CGContextSetBlendMode (my macGraphicsContext, kCGBlendModeNormal);
 		#endif
@@ -402,4 +409,8 @@ Graphics_Colour Graphics_inqColour (Graphics me) {
 	return my colour;
 }
 
+enum kGraphics_colourScale Graphics_inqColourScale (Graphics me) {
+	return my colourScale;
+}
+
 /* End of file Graphics_colour.cpp */
diff --git a/sys/Graphics_enums.h b/sys/Graphics_enums.h
index eec9401..7b9a0a8 100644
--- a/sys/Graphics_enums.h
+++ b/sys/Graphics_enums.h
@@ -1,6 +1,6 @@
 /* Graphics_enums.h
  *
- * Copyright (C) 1992-2007,2013 Paul Boersma
+ * Copyright (C) 1992-2007,2013,2014 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
@@ -31,6 +31,19 @@ enums_begin (kGraphics_horizontalAlignment, 0)
 	enums_add (kGraphics_horizontalAlignment, 2, RIGHT, L"right")
 enums_end (kGraphics_horizontalAlignment, 2, CENTRE)
 
+enums_begin (kGraphics_resolution, 0)
+	enums_add (kGraphics_resolution, 0,  96, L"96 dpi")
+	enums_add (kGraphics_resolution, 1, 100, L"100 dpi")
+	enums_add (kGraphics_resolution, 2, 300, L"300 dpi")
+	enums_add (kGraphics_resolution, 3, 360, L"360 dpi")
+	enums_add (kGraphics_resolution, 4, 600, L"600 dpi")
+enums_end (kGraphics_resolution, 4, 100)
+
+enums_begin (kGraphics_colourScale, 0)
+	enums_add (kGraphics_colourScale, 0, GREY, L"grey")
+	enums_add (kGraphics_colourScale, 1, BLUE_TO_RED, L"blue to red")
+enums_end (kGraphics_colourScale, 1, GREY)
+
 enums_begin (kGraphicsPostscript_spots, 0)
 	enums_add (kGraphicsPostscript_spots, 0, FINE, L"finest")
 	enums_add (kGraphicsPostscript_spots, 1, PHOTOCOPYABLE, L"photocopyable")
@@ -54,4 +67,9 @@ enums_begin (kGraphicsPostscript_fontChoiceStrategy, 0)
 	enums_add (kGraphicsPostscript_fontChoiceStrategy, 3, PS_MONOTYPE, L"PS Monotype")
 enums_end (kGraphicsPostscript_fontChoiceStrategy, 3, AUTOMATIC)
 
+enums_begin (kGraphics_cjkFontStyle, 0)
+	enums_add (kGraphics_cjkFontStyle, 0, CHINESE, L"Chinese")
+	enums_add (kGraphics_cjkFontStyle, 1, JAPANESE, L"Japanese")
+enums_end (kGraphics_cjkFontStyle, 1, CHINESE)
+
 /* End of file Graphics_enums.h */
diff --git a/sys/Graphics_image.cpp b/sys/Graphics_image.cpp
index b9c4902..046fecf 100644
--- a/sys/Graphics_image.cpp
+++ b/sys/Graphics_image.cpp
@@ -1,6 +1,6 @@
 /* Graphics_image.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -32,6 +32,8 @@
 
 #include "GraphicsP.h"
 
+#include "../fon/Photo.h"
+
 #if win
 	#include <GdiPlus.h>
 #elif mac
@@ -150,7 +152,6 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 			#if cairo
 				for (int igrey = 0; igrey < sizeof (grey) / sizeof (*grey); igrey ++)
 					cairo_pattern_destroy (grey [igrey]);
-				cairo_paint (my d_cairoGraphicsContext);
 			#elif mac
 				CGContextSetRGBFillColor (my d_macGraphicsContext, 0.0, 0.0, 0.0, 1.0);
 				GraphicsQuartz_exitDraw (me);
@@ -166,9 +167,7 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 			long arrayWidth = clipx2 - clipx1;
 			long arrayHeight = clipy1 - clipy2;
 			trace ("arrayWidth %f, arrayHeight %f", (double) arrayWidth, (double) arrayHeight);
-			// We're creating an alpha-only surface here (size is 1/4 compared to RGB24 and ARGB)
-			// The grey values are reversed as we let the foreground colour shine through instead of blackening
-			cairo_surface_t *sfc = cairo_image_surface_create (/*CAIRO_FORMAT_A8*/ CAIRO_FORMAT_RGB24, arrayWidth, arrayHeight);
+			cairo_surface_t *sfc = cairo_image_surface_create (CAIRO_FORMAT_RGB24, arrayWidth, arrayHeight);
 			unsigned char *bits = cairo_image_surface_get_data (sfc);
 			int scanLineLength = cairo_image_surface_get_stride (sfc);
 			unsigned char grey [256];
@@ -179,28 +178,23 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 			long bitmapWidth = clipx2 - clipx1, bitmapHeight = clipy1 - clipy2;
 			int igrey;
 			/*
-			 * Create a device-independent bitmap, 8 pixels deep, for 256 greys.
+			 * Create a device-independent bitmap, 32 bits deep.
 			 */
-			struct { BITMAPINFOHEADER header; RGBQUAD colours [256]; } bitmapInfo;
-			long scanLineLength = (bitmapWidth + 3) & ~3L;
+			struct { BITMAPINFOHEADER header; } bitmapInfo;
+			long scanLineLength = bitmapWidth * 4;   // for 24 bits: (bitmapWidth * 3 + 3) & ~3L;
 			HBITMAP bitmap;
-			unsigned char *bits;
+			unsigned char *bits;   // a pointer to memory allocated by VirtualAlloc or by CreateDIBSection ()
 			bitmapInfo. header.biSize = sizeof (BITMAPINFOHEADER);
-			bitmapInfo. header.biWidth = scanLineLength;
+			bitmapInfo. header.biWidth = bitmapWidth;   // scanLineLength;
 			bitmapInfo. header.biHeight = bitmapHeight;
 			bitmapInfo. header.biPlanes = 1;
-			bitmapInfo. header.biBitCount = 8;
+			bitmapInfo. header.biBitCount = 32;
 			bitmapInfo. header.biCompression = 0;
 			bitmapInfo. header.biSizeImage = 0;
 			bitmapInfo. header.biXPelsPerMeter = 0;
 			bitmapInfo. header.biYPelsPerMeter = 0;
 			bitmapInfo. header.biClrUsed = 0;
 			bitmapInfo. header.biClrImportant = 0;
-			for (igrey = 0; igrey <= 255; igrey ++) {
-				bitmapInfo. colours [igrey]. rgbRed = igrey;
-				bitmapInfo. colours [igrey]. rgbGreen = igrey;
-				bitmapInfo. colours [igrey]. rgbBlue = igrey;
-			}
 			bitmap = CreateDIBSection (my d_gdiGraphicsContext /* ignored */, (CONST BITMAPINFO *) & bitmapInfo,
 				DIB_RGB_COLORS, (VOID **) & bits, NULL, 0);
 		#elif mac
@@ -225,17 +219,59 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 				}
 		#elif win
 			#define ROW_START_ADDRESS  (bits + (clipy1 - 1 - yDC) * scanLineLength)
-			#define PUT_PIXEL  *pixelAddress ++ = value <= 0 ? 0 : value >= 255 ? 255 : (int) value;
+			#define PUT_PIXEL \
+				if (1) { \
+					unsigned char kar = value <= 0 ? 0 : value >= 255 ? 255 : (int) value; \
+					*pixelAddress ++ = kar; \
+					*pixelAddress ++ = kar; \
+					*pixelAddress ++ = kar; \
+					*pixelAddress ++ = 0; \
+				}
 		#elif mac
 			#define ROW_START_ADDRESS  (imageData + (clipy1 - 1 - yDC) * bytesPerRow)
 			#define PUT_PIXEL \
-				if (1) { \
+				if (my colourScale == kGraphics_colourScale_GREY) { \
 					unsigned char kar = value <= 0 ? 0 : value >= 255 ? 255 : (int) value; \
 					*pixelAddress ++ = kar; \
 					*pixelAddress ++ = kar; \
 					*pixelAddress ++ = kar; \
 					*pixelAddress ++ = 0; \
+				} else if (my colourScale == kGraphics_colourScale_BLUE_TO_RED) { \
+					if (value < 0.0) { \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = 63; \
+						*pixelAddress ++ = 0; \
+					} else if (value < 64.0) { \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = (int) (value * 3 + 63.999); \
+						*pixelAddress ++ = 0; \
+					} else if (value < 128.0) { \
+						*pixelAddress ++ = (int) (value * 4 - 256.0); \
+						*pixelAddress ++ = (int) (value * 4 - 256.0); \
+						*pixelAddress ++ = 255; \
+						*pixelAddress ++ = 0; \
+					} else if (value < 192.0) { \
+						*pixelAddress ++ = 255; \
+						*pixelAddress ++ = (int) ((256.0 - value) * 4 - 256.0); \
+						*pixelAddress ++ = (int) ((256.0 - value) * 4 - 256.0); \
+						*pixelAddress ++ = 0; \
+					} else if (value < 256.0) { \
+						*pixelAddress ++ = (int) ((256.0 - value) * 3 + 63.999); \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = 0; \
+					} else { \
+						*pixelAddress ++ = 63; \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = 0; \
+						*pixelAddress ++ = 0; \
+					} \
 				}
+		#else
+			#define ROW_START_ADDRESS  NULL
+			#define PUT_PIXEL
 		#endif
 		if (interpolate) {
 			try {
@@ -287,10 +323,22 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 							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;
+							#if win
+								*pixelAddress ++ = blue         * 255.0;
+								*pixelAddress ++ = green        * 255.0;
+								*pixelAddress ++ = red          * 255.0;
+								*pixelAddress ++ = 0;
+							#elif mac
+								*pixelAddress ++ = red          * 255.0;
+								*pixelAddress ++ = green        * 255.0;
+								*pixelAddress ++ = blue         * 255.0;
+								*pixelAddress ++ = transparency * 255.0;
+							#elif cairo
+								*pixelAddress ++ = blue         * 255.0;
+								*pixelAddress ++ = green        * 255.0;
+								*pixelAddress ++ = red          * 255.0;
+								*pixelAddress ++ = transparency * 255.0;
+							#endif
 						}
 					} else {
 						unsigned char *ztop = z_byte [itop], *zbottom = z_byte [ibottom];
@@ -354,10 +402,15 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 		#elif win
 			SetDIBitsToDevice (my d_gdiGraphicsContext, clipx1, clipy2, bitmapWidth, bitmapHeight, 0, 0, 0, bitmapHeight,
 				bits, (CONST BITMAPINFO *) & bitmapInfo, DIB_RGB_COLORS);
+			//StretchDIBits (my d_gdiGraphicsContext, clipx1, clipy2, bitmapWidth, bitmapHeight, 0, 0, 0, bitmapHeight,
+			//	bits, (CONST BITMAPINFO *) & bitmapInfo, DIB_RGB_COLORS, SRCCOPY);
 		#elif mac
 			CGImageRef image;
-			CGColorSpaceRef colourSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);   // used to be kCGColorSpaceUserRGB
-			Melder_assert (colourSpace != NULL);
+			static CGColorSpaceRef colourSpace = NULL;
+			if (colourSpace == NULL) {
+				colourSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);   // used to be kCGColorSpaceUserRGB
+				Melder_assert (colourSpace != NULL);
+			}
 			if (1) {
 				CGDataProviderRef dataProvider = CGDataProviderCreateWithData (NULL,
 					imageData,
@@ -384,7 +437,7 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 			GraphicsQuartz_initDraw (me);
 			CGContextDrawImage (my d_macGraphicsContext, CGRectMake (clipx1, clipy2, clipx2 - clipx1, clipy1 - clipy2), image);
 			GraphicsQuartz_exitDraw (me);
-			CGColorSpaceRelease (colourSpace);
+			//CGColorSpaceRelease (colourSpace);
 			CGImageRelease (image);
 		#endif
 		/*
@@ -441,7 +494,7 @@ static void _GraphicsPostscript_cellArrayOrImage (GraphicsPostscript me, double
 		my d_printf (my d_file, "/lorow %ld string def /hirow %ld string def\n", nx, nx);
 		/* New rows (scanlines) are longer: */
 		my d_printf (my d_file, "/scanline %ld string def\n", nx_new);
-		/* The first four arguments to the 'image' command,
+		/* The first four arguments to the 'image' command, */
 		/* namely the new number of columns, the new number of rows, the bit depth, and the matrix: */
 		my d_printf (my d_file, "%ld %ld 8 [%ld 0 0 %ld 0 0]\n", nx_new, ny_new, nx_new, ny_new);
 		/* Since our imageproc is going to output only one scanline at a time, */
@@ -493,7 +546,7 @@ static void _GraphicsPostscript_cellArrayOrImage (GraphicsPostscript me, double
 		my d_printf (my d_file, "/row %ld string def\n", nx, nx);
 		/* New rows (scanlines) are longer: */
 		my d_printf (my d_file, "/scanline %ld string def\n", nx_new);
-		/* The first four arguments to the 'image' command,
+		/* The first four arguments to the 'image' command, */
 		/* namely the new number of columns, the number of rows, the bit depth, and the matrix: */
 		my d_printf (my d_file, "%ld %ld 8 [%ld 0 0 %ld 0 0]\n", nx_new, ny, nx_new, ny);
 		/* The imageproc starts here. We fill one original row. */
@@ -525,7 +578,7 @@ static void _GraphicsPostscript_cellArrayOrImage (GraphicsPostscript me, double
 		my d_printf (my d_file, "/lorow %ld string def /hirow %ld string def\n", nx, nx);
 		/* New rows (scanlines) are equally long: */
 		my d_printf (my d_file, "/scanline %ld string def\n", nx);
-		/* The first four arguments to the 'image' command,
+		/* The first four arguments to the 'image' command, */
 		/* namely the number of columns, the new number of rows, the bit depth, and the matrix: */
 		my d_printf (my d_file, "%ld %ld 8 [%ld 0 0 %ld 0 0]\n", nx, ny_new, nx, ny_new);
 		/* Since our imageproc is going to output only one scanline at a time, */
@@ -586,12 +639,11 @@ static void _cellArrayOrImage (Graphics me, double **z_float, double_rgbt **z_rg
 	_Graphics_setColour (me, my colour);
 }
 
-static void cellArrayOrImage (I, double **z_float, double_rgbt **z_rgbt, unsigned char **z_byte,
+static void cellArrayOrImage (Graphics me, 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_rgbt, z_byte,
 		ix1, ix2, wdx (x1WC), wdx (x2WC),
@@ -656,12 +708,44 @@ void Graphics_image8 (Graphics me, unsigned char **z, long ix1, long ix2, double
 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);
 	long width = x2DC - x1DC, height = my yIsZeroAtTheTop ? y1DC - y2DC : y2DC - y1DC;
-	#if win
+	#if 0
+		structMelderFile file;
+		Melder_relativePathToFile (relativeFileName, & file);
+		try {
+			autoPhoto photo = Photo_readFromImageFile (& file);
+			if (x1 == x2 && y1 == y2) {
+				width = photo -> nx, x1DC -= width / 2, x2DC = x1DC + width;
+				height = photo -> ny, y2DC -= height / 2, y1DC = y2DC + height;
+			} else if (x1 == x2) {
+				width = height * (double) photo -> nx / (double) photo -> ny;
+				x1DC -= width / 2, x2DC = x1DC + width;
+			} else if (y1 == y2) {
+				height = width * (double) photo -> ny / (double) photo -> nx;
+				y2DC -= height / 2, y1DC = y2DC + height;
+			}
+			autoNUMmatrix <double_rgbt> z (1, photo -> ny, 1, photo -> nx);
+			for (long iy = 1; iy <= photo -> ny; iy ++) {
+				for (long ix = 1; ix <= photo -> nx; ix ++) {
+					z [iy] [ix]. red          = photo -> d_red          -> z [iy] [ix];
+					z [iy] [ix]. green        = photo -> d_green        -> z [iy] [ix];
+					z [iy] [ix]. blue         = photo -> d_blue         -> z [iy] [ix];
+					z [iy] [ix]. transparency = photo -> d_transparency -> z [iy] [ix];
+				}
+			}
+			_cellArrayOrImage (me, NULL, z.peek(), NULL,
+				1, photo -> nx, x1DC, x2DC, 1, photo -> ny, y1DC, y2DC,
+				0.0, 1.0,
+				//wdx (my d_x1WC), wdx (my d_x2WC), wdy (my d_y1WC), wdy (my d_y2WC),   // in case of clipping
+				LONG_MIN, LONG_MAX, LONG_MAX, LONG_MIN,   // in case of no clipping
+				true);
+		} catch (MelderError) {
+			Melder_clearError ();
+		}
+	#elif win
 		if (my d_useGdiplus) {
 			structMelderFile file;
 			Melder_relativePathToFile (relativeFileName, & file);
-			Gdiplus::Image image (file. path);
-			Gdiplus::Graphics dcplus (my d_gdiGraphicsContext);
+			Gdiplus::Bitmap image (file. path);
 			if (x1 == x2 && y1 == y2) {
 				width = image. GetWidth (), x1DC -= width / 2, x2DC = x1DC + width;
 				height = image. GetHeight (), y2DC -= height / 2, y1DC = y2DC + height;
@@ -672,6 +756,7 @@ static void _GraphicsScreen_imageFromFile (GraphicsScreen me, const wchar_t *rel
 				height = width * (double) image. GetHeight () / (double) image. GetWidth ();
 				y2DC -= height / 2, y1DC = y2DC + height;
 			}
+			Gdiplus::Graphics dcplus (my d_gdiGraphicsContext);
 			Gdiplus::Rect rect (x1DC, y2DC, width, height);
 			dcplus. DrawImage (& image, rect);
 		}
diff --git a/sys/Graphics_linesAndAreas.cpp b/sys/Graphics_linesAndAreas.cpp
index 89cbfec..650d519 100644
--- a/sys/Graphics_linesAndAreas.cpp
+++ b/sys/Graphics_linesAndAreas.cpp
@@ -1,6 +1,6 @@
 /* Graphics_linesAndAreas.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -22,7 +22,6 @@
 /* Normally on, because e.g. the intensity contour in the Sound window should not run through the play buttons: */
 #define FUNCTIONS_ARE_CLIPPED  1
 
-#define POSTSCRIPT_MAXPATH  1000
 #define LINE_WIDTH_IN_PIXELS(me)  ( my resolution > 192 ? my lineWidth * (my resolution / 192.0) : my lineWidth )
 #define ORDER_DC  { long temp; if (x1DC > x2DC) temp = x1DC, x1DC = x2DC, x2DC = temp; \
 	if (yIsZeroAtTheTop == (y2DC > y1DC)) temp = y1DC, y1DC = y2DC, y2DC = temp; }
@@ -48,15 +47,17 @@ static void psRevertLine (GraphicsPostscript me) {
 }
 
 #if cairo
-	static void gdkPrepareLine (GraphicsScreen me) {
-		gdk_gc_set_line_attributes (my d_gdkGraphicsContext, my lineWidth,
-			my lineType >= Graphics_DOTTED ? GDK_LINE_ON_OFF_DASH : GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
-	}
-	static void gdkRevertLine (GraphicsScreen me) {
-		if (my lineType >= Graphics_DOTTED) {
-			gdk_gc_set_line_attributes (my d_gdkGraphicsContext, my lineWidth, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
+	#if ALLOW_GDK_DRAWING
+		static void gdkPrepareLine (GraphicsScreen me) {
+			gdk_gc_set_line_attributes (my d_gdkGraphicsContext, my lineWidth,
+				my lineType >= Graphics_DOTTED ? GDK_LINE_ON_OFF_DASH : GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
 		}
-	}
+		static void gdkRevertLine (GraphicsScreen me) {
+			if (my lineType >= Graphics_DOTTED) {
+				gdk_gc_set_line_attributes (my d_gdkGraphicsContext, my lineWidth, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
+			}
+		}
+	#endif
 	static void cairoPrepareLine (GraphicsScreen me) {
 		if (my d_cairoGraphicsContext == NULL) return;
 		double dotted_line [] = { 2, 2 };
@@ -114,6 +115,7 @@ static void psRevertLine (GraphicsPostscript me) {
 	}
 #elif mac
 	static void quartzPrepareLine (GraphicsScreen me) {
+		CGContextSetLineJoin (my d_macGraphicsContext, kCGLineJoinRound);
 		if (my duringXor) {
 			CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeDifference);
 			CGContextSetAllowsAntialiasing (my d_macGraphicsContext, false);
@@ -152,21 +154,25 @@ static void psRevertLine (GraphicsPostscript me) {
 
 /* First level. */
 
-void structGraphicsScreen :: v_polyline (long numberOfPoints, long *xyDC, bool close) {
+void structGraphicsScreen :: v_polyline (long numberOfPoints, double *xyDC, bool close) {
 	#if cairo
 		if (duringXor) {
-			gdkPrepareLine (this);
-			for (long i = 0; i < numberOfPoints - 1; i ++) {
-				gdk_draw_line (d_window, d_gdkGraphicsContext, xyDC [i + i], xyDC [i + i + 1], xyDC [i + i + 2], xyDC [i + i + 3]);
-			}
-			gdkRevertLine (this);
+			#if ALLOW_GDK_DRAWING
+				gdkPrepareLine (this);
+				for (long i = 0; i < numberOfPoints - 1; i ++) {
+					gdk_draw_line (d_window, d_gdkGraphicsContext,
+						xyDC [i + i], xyDC [i + i + 1], xyDC [i + i + 2], xyDC [i + i + 3]);
+				}
+				gdkRevertLine (this);
+				gdk_flush ();
+			#endif
 		} else {
 			if (d_cairoGraphicsContext == NULL) return;
 			cairoPrepareLine (this);
 			// cairo_new_path (d_cairoGraphicsContext); // move_to() automatically creates a new path
-			cairo_move_to (d_cairoGraphicsContext, (double) xyDC [0], (double) xyDC [1]);
+			cairo_move_to (d_cairoGraphicsContext, xyDC [0], xyDC [1]);
 			for (long i = 1; i < numberOfPoints; i ++) {
-				cairo_line_to (d_cairoGraphicsContext, (double) xyDC [i + i], (double) xyDC [i + i + 1]);
+				cairo_line_to (d_cairoGraphicsContext, xyDC [i + i], xyDC [i + i + 1]);
 			}
 			if (close) cairo_close_path (d_cairoGraphicsContext);
 			cairo_stroke (d_cairoGraphicsContext);
@@ -215,33 +221,13 @@ void structGraphicsScreen :: v_polyline (long numberOfPoints, long *xyDC, bool c
 	#endif
 }
 
-void structGraphicsPostscript :: v_polyline (long numberOfPoints, long *xyDC, bool close) {
-	long nn = numberOfPoints + numberOfPoints;
+void structGraphicsPostscript :: v_polyline (long numberOfPoints, double *xyDC, bool close) {
+	long nn = 2 * numberOfPoints;
 	psPrepareLine (this);
-	d_printf (d_file, "N %ld %ld moveto\n", xyDC [0], xyDC [1]);
-	for (long i = 2, ipath = 1; i < nn; i += 2) {
-		long dx = xyDC [i] - xyDC [i - 2], dy = xyDC [i + 1] - xyDC [i - 1];
-		if (dx == 1 && i < nn - 20 && xyDC [i + 2] - xyDC [i] == 1 &&
-			 xyDC [i + 4] - xyDC [i + 2] == 1 && xyDC [i + 6] - xyDC [i + 4] == 1 &&
-			 xyDC [i + 8] - xyDC [i + 6] == 1 && xyDC [i + 10] - xyDC [i + 8] == 1 &&
-			 xyDC [i + 12] - xyDC [i + 10] == 1 && xyDC [i + 14] - xyDC [i + 12] == 1 &&
-			 xyDC [i + 16] - xyDC [i + 14] == 1 && xyDC [i + 18] - xyDC [i + 16] == 1)
-		{
-			d_printf (d_file, "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld F\n",
-				xyDC [i + 19] - xyDC [i + 17], xyDC [i + 17] - xyDC [i + 15],
-				xyDC [i + 15] - xyDC [i + 13], xyDC [i + 13] - xyDC [i + 11],
-				xyDC [i + 11] - xyDC [i + 9], xyDC [i + 9] - xyDC [i + 7],
-				xyDC [i + 7] - xyDC [i + 5], xyDC [i + 5] - xyDC [i + 3],
-				xyDC [i + 3] - xyDC [i + 1], dy);
-			ipath += 9;
-			i += 18;
-		} else if (dx != 0 || dy != 0 || i < 4) {
-			d_printf (d_file, "%ld %ld L\n", dx, dy);
-		}
-		if (++ ipath >= POSTSCRIPT_MAXPATH && i != nn - 2) {
-			d_printf (d_file, "currentpoint stroke moveto\n");
-			ipath = 1;
-		}
+	d_printf (d_file, "N %.7g %.7g moveto\n", xyDC [0], xyDC [1]);
+	for (long i = 2; i < nn; i += 2) {
+		double dx = xyDC [i] - xyDC [i - 2], dy = xyDC [i + 1] - xyDC [i - 1];
+		d_printf (d_file, "%.7g %.7g L\n", dx, dy);
 	}
 	if (close)
 		d_printf (d_file, "closepath ");
@@ -249,7 +235,7 @@ void structGraphicsPostscript :: v_polyline (long numberOfPoints, long *xyDC, bo
 	psRevertLine (this);
 }
 
-void structGraphicsScreen :: v_fillArea (long numberOfPoints, long *xyDC) {
+void structGraphicsScreen :: v_fillArea (long numberOfPoints, double *xyDC) {
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;
 		// cairo_new_path (d_cairoGraphicsContext); // move_to() automatically creates a new path
@@ -280,33 +266,22 @@ void structGraphicsScreen :: v_fillArea (long numberOfPoints, long *xyDC) {
 	#endif
 }
 
-void structGraphicsPostscript :: v_fillArea (long numberOfPoints, long *xyDC) {
+void structGraphicsPostscript :: v_fillArea (long numberOfPoints, double *xyDC) {
 	long nn = numberOfPoints + numberOfPoints;
-	d_printf (d_file, "N %ld %ld M\n", xyDC [0], xyDC [1]);
-	/*
-	 * Very old (?) printers have path size restrictions.
-	 * That's no reason to truncate the path on newer printers.
-	 */
-	#if 0
-		if (numberOfPoints > POSTSCRIPT_MAXPATH) {
-			Melder_warning (L"GraphicsPostscript::fillArea: path truncated.");
-			numberOfPoints = POSTSCRIPT_MAXPATH, nn = numberOfPoints + numberOfPoints;
-		}
-	#endif
+	d_printf (d_file, "N %.7g %.7g M\n", xyDC [0], xyDC [1]);
 	for (long i = 2; i < nn; i += 2) {
-		d_printf (d_file, "%ld %ld L\n",
-			xyDC [i] - xyDC [i - 2], xyDC [i + 1] - xyDC [i - 1]);
+		d_printf (d_file, "%.7g %.7g L\n", xyDC [i] - xyDC [i - 2], xyDC [i + 1] - xyDC [i - 1]);
 	}
 	d_printf (d_file, "closepath fill\n");
 }
 
 /* Second level. */
 
-void structGraphicsScreen :: v_rectangle (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsScreen :: v_rectangle (double x1DC, double x2DC, double y1DC, double y2DC) {
 	ORDER_DC
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;
-		int width = x2DC - x1DC, height = y1DC - y2DC;
+		double width = x2DC - x1DC, height = y1DC - y2DC;
 		if (width <= 0 || height <= 0) return;
 		cairoPrepareLine (this);
 		cairo_rectangle (d_cairoGraphicsContext, x1DC, y2DC, width, height);
@@ -319,11 +294,11 @@ void structGraphicsScreen :: v_rectangle (long x1DC, long x2DC, long y1DC, long
 	#elif mac
 		GraphicsQuartz_initDraw (this);
 		quartzPrepareLine (this);
-		CGContextStrokeRect (d_macGraphicsContext, CGRectMake (x1DC - 0.5, y2DC - 0.5, x2DC - x1DC, y1DC - y2DC));
+		CGContextStrokeRect (d_macGraphicsContext, CGRectMake (x1DC, y2DC, x2DC - x1DC, y1DC - y2DC));
 		quartzRevertLine (this);
 		GraphicsQuartz_exitDraw (this);
 	#else
-		long xyDC [8];
+		double xyDC [8];
 		xyDC [0] = x1DC;	xyDC [1] = y1DC;
 		xyDC [2] = x2DC;	xyDC [3] = y1DC;
 		xyDC [4] = x2DC;	xyDC [5] = y2DC;
@@ -332,14 +307,14 @@ void structGraphicsScreen :: v_rectangle (long x1DC, long x2DC, long y1DC, long
 	#endif
 }
 
-void structGraphicsPostscript :: v_rectangle (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsPostscript :: v_rectangle (double x1DC, double x2DC, double y1DC, double y2DC) {
 	psPrepareLine (this);
-	d_printf (d_file, "N %ld %ld M %ld %ld lineto %ld %ld lineto %ld %ld lineto closepath stroke\n",
+	d_printf (d_file, "N %.7g %.7g M %.7g %.7g lineto %.7g %.7g lineto %.7g %.7g lineto closepath stroke\n",
 		x1DC, y1DC, x2DC, y1DC, x2DC, y2DC, x1DC, y2DC);
 	psRevertLine (this);
 }
 
-void structGraphicsScreen :: v_fillRectangle (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsScreen :: v_fillRectangle (double x1DC, double x2DC, double y1DC, double y2DC) {
 	ORDER_DC
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;	
@@ -359,7 +334,7 @@ void structGraphicsScreen :: v_fillRectangle (long x1DC, long x2DC, long y1DC, l
 		CGContextFillRect (d_macGraphicsContext, CGRectMake (x1DC, y2DC, x2DC - x1DC, y1DC - y2DC));
 		GraphicsQuartz_exitDraw (this);
 	#else
-		long xyDC [10];
+		double xyDC [10];
 		xyDC [0] = x1DC;	xyDC [1] = y1DC;
 		xyDC [2] = x2DC;	xyDC [3] = y1DC;
 		xyDC [4] = x2DC;	xyDC [5] = y2DC;
@@ -369,18 +344,21 @@ void structGraphicsScreen :: v_fillRectangle (long x1DC, long x2DC, long y1DC, l
 	#endif
 }
 
-void structGraphicsPostscript :: v_fillRectangle (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsPostscript :: v_fillRectangle (double x1DC, double x2DC, double y1DC, double y2DC) {
 	d_printf (d_file,
-		"N %ld %ld M %ld %ld lineto %ld %ld lineto %ld %ld lineto closepath fill\n",
+		"N %.7g %.7g M %.7g %.7g lineto %.7g %.7g lineto %.7g %.7g lineto closepath fill\n",
 		x1DC, y1DC, x2DC, y1DC, x2DC, y2DC, x1DC, y2DC);
 }
 
 void structGraphicsScreen :: v_circle (double xDC, double yDC, double rDC) {
 	#if cairo
 		if (duringXor) {
-			gdkPrepareLine (this);
-			gdk_draw_arc (d_window, d_gdkGraphicsContext, FALSE, xDC - rDC, yDC - rDC, rDC + rDC, rDC + rDC, 0, 360 * 64);
-			gdkRevertLine (this);
+			#if ALLOW_GDK_DRAWING
+				gdkPrepareLine (this);
+				gdk_draw_arc (d_window, d_gdkGraphicsContext, FALSE, xDC - rDC, yDC - rDC, rDC + rDC, rDC + rDC, 0, 360 * 64);
+				gdkRevertLine (this);
+				gdk_flush ();
+			#endif
 		} else {
 			if (d_cairoGraphicsContext == NULL) return;
 			cairoPrepareLine (this);
@@ -410,7 +388,7 @@ void structGraphicsPostscript :: v_circle (double xDC, double yDC, double rDC) {
 	psRevertLine (this);
 }
 
-void structGraphicsScreen :: v_ellipse (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsScreen :: v_ellipse (double x1DC, double x2DC, double y1DC, double y2DC) {
 	ORDER_DC
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;
@@ -430,8 +408,7 @@ void structGraphicsScreen :: v_ellipse (long x1DC, long x2DC, long y1DC, long y2
 	#elif mac
 		GraphicsQuartz_initDraw (this);
 		quartzPrepareLine (this);
-        NSCAssert( d_macGraphicsContext, @"nil context");
-
+        NSCAssert (d_macGraphicsContext, @"nil context");
 		CGContextBeginPath (d_macGraphicsContext);
 		CGContextSaveGState (d_macGraphicsContext);
 		CGContextTranslateCTM (d_macGraphicsContext, 0.5 * (x2DC + x1DC), 0.5 * (y2DC + y1DC));
@@ -445,22 +422,22 @@ void structGraphicsScreen :: v_ellipse (long x1DC, long x2DC, long y1DC, long y2
 	#endif
 }
 
-void structGraphicsPostscript :: v_ellipse (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsPostscript :: v_ellipse (double x1DC, double x2DC, double y1DC, double y2DC) {
 	if (x1DC != x2DC && y1DC != y2DC) {   // prevent division by zero
 		psPrepareLine (this);
 		/* To draw an ellipse, we will have to 'translate' and 'scale' and draw a circle. */
 		/* However, we have to scale back before the actual 'stroke', */
 		/* because we want the normal line thickness; */
 		/* So we cannot use 'gsave' and 'grestore', which clear the path (Cookbook 3). */
-		d_printf (d_file, "gsave %f %f translate %f %f scale N 0 0 1 0 360 arc\n"
-			" %f %f scale stroke grestore\n",
+		d_printf (d_file, "gsave %.7g %.7g translate %.7g %.7g scale N 0 0 1 0 360 arc\n"
+			" %.7g %.7g scale stroke grestore\n",
 			0.5 * (x2DC + x1DC), 0.5 * (y2DC + y1DC), 0.5 * (x2DC - x1DC), 0.5 * (y2DC - y1DC),
 			2.0 / (x2DC - x1DC), 2.0 / (y2DC - y1DC));
 		psRevertLine (this);
 	}
 }
 
-void structGraphicsScreen :: v_arc (long xDC, long yDC, long rDC, double fromAngle, double toAngle) {
+void structGraphicsScreen :: v_arc (double xDC, double yDC, double rDC, double fromAngle, double toAngle) {
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;
 		cairoPrepareLine (this);
@@ -487,15 +464,15 @@ void structGraphicsScreen :: v_arc (long xDC, long yDC, long rDC, double fromAng
 	#endif
 }
 
-void structGraphicsPostscript :: v_arc (long xDC, long yDC, long rDC, double fromAngle, double toAngle) {
+void structGraphicsPostscript :: v_arc (double xDC, double yDC, double rDC, double fromAngle, double toAngle) {
 	psPrepareLine (this);
-	d_printf (d_file, "N %ld %ld %ld %f %f arc stroke\n", xDC, yDC, rDC, fromAngle, toAngle);
+	d_printf (d_file, "N %.7g %.7g %.7g %.7g %.7g arc stroke\n", xDC, yDC, rDC, fromAngle, toAngle);
 	psRevertLine (this);
 }
 
 /* Third level. */
 
-void structGraphicsScreen :: v_fillCircle (long xDC, long yDC, long rDC) {
+void structGraphicsScreen :: v_fillCircle (double xDC, double yDC, double rDC) {
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;
 		cairo_new_path (d_cairoGraphicsContext);
@@ -520,11 +497,11 @@ void structGraphicsScreen :: v_fillCircle (long xDC, long yDC, long rDC) {
 	#endif
 }
 
-void structGraphicsPostscript :: v_fillCircle (long xDC, long yDC, long rDC) {
-	d_printf (d_file, "N %ld %ld %ld FC\n", xDC, yDC, rDC);
+void structGraphicsPostscript :: v_fillCircle (double xDC, double yDC, double rDC) {
+	d_printf (d_file, "N %.7g %.7g %.7g FC\n", xDC, yDC, rDC);
 }
 
-void structGraphicsScreen :: v_fillEllipse (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsScreen :: v_fillEllipse (double x1DC, double x2DC, double y1DC, double y2DC) {
 	ORDER_DC
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;	
@@ -542,10 +519,9 @@ void structGraphicsScreen :: v_fillEllipse (long x1DC, long x2DC, long y1DC, lon
 	#elif mac
 		GraphicsQuartz_initDraw (this);
 		quartzPrepareFill (this);
+        NSCAssert (d_macGraphicsContext, @"nil context");
 		CGContextBeginPath (d_macGraphicsContext);
 		CGContextSaveGState (d_macGraphicsContext);
-        NSCAssert(d_macGraphicsContext, @"nil context");
-
 		CGContextTranslateCTM (d_macGraphicsContext, 0.5 * (x2DC + x1DC), 0.5 * (y2DC + y1DC));
 		CGContextScaleCTM (d_macGraphicsContext, 0.5 * (x2DC - x1DC), 0.5 * (y2DC - y1DC));
 		CGContextAddArc (d_macGraphicsContext, 0.0, 0.0, 1.0, 0.0, NUM2pi, 0);
@@ -558,12 +534,12 @@ void structGraphicsScreen :: v_fillEllipse (long x1DC, long x2DC, long y1DC, lon
 	#endif
 }
 
-void structGraphicsPostscript :: v_fillEllipse (long x1DC, long x2DC, long y1DC, long y2DC) {
-	d_printf (d_file, "gsave %ld %ld translate %ld %ld scale N 0 0 1 FC grestore\n",
+void structGraphicsPostscript :: v_fillEllipse (double x1DC, double x2DC, double y1DC, double y2DC) {
+	d_printf (d_file, "gsave %.7g %.7g translate %.7g %.7g scale N 0 0 1 FC grestore\n",
 		(x2DC + x1DC) / 2, (y2DC + y1DC) / 2, (x2DC - x1DC) / 2, (y2DC - y1DC) / 2);
 }
 
-void structGraphicsScreen :: v_button (long x1DC, long x2DC, long y1DC, long y2DC) {
+void structGraphicsScreen :: v_button (double x1DC, double x2DC, double y1DC, double y2DC) {
 	ORDER_DC
 	#if cairo
 		if (x2DC <= x1DC || y1DC <= y2DC) return;
@@ -572,7 +548,12 @@ void structGraphicsScreen :: v_button (long x1DC, long x2DC, long y1DC, long y2D
 		if (d_drawingArea && 0) {
 			// clip to drawing area
 			int w, h;
-			gdk_drawable_get_size (d_window, & 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);
 		}
@@ -605,39 +586,49 @@ void structGraphicsScreen :: v_button (long x1DC, long x2DC, long y1DC, long y2D
 		}
 		cairo_restore (d_cairoGraphicsContext);
 	#elif mac
-        int width = x2DC - x1DC, height = y1DC - y2DC;
+        double width = x2DC - x1DC, height = y1DC - y2DC;
 		if (width <= 0 || height <= 0) return;
+		/*
+		 * This is pixel-precise drawing, and may therefore by different on retina displays than on 100 dpi displays.
+		 */
+		#if cocoa
+			bool isRetinaDisplay = [[d_macView window] backingScaleFactor] == 2.0;
+		#else
+			bool isRetinaDisplay = false;
+		#endif
 
-		#define SetRect(r, left, top, right, bottom) r.origin.x = left; r.origin.y = top; r.size.width = right - left; r.size.height = bottom - top;
-    
 		GraphicsQuartz_initDraw (this);
 		CGContextSetLineWidth (d_macGraphicsContext, 1.0);
 		CGContextSetAllowsAntialiasing (d_macGraphicsContext, false);   // because we want to draw by pixel
-        CGFloat gray = 0.1;
-        CGContextSetRGBStrokeColor (d_macGraphicsContext, gray, gray, gray, 1.0);
-        CGRect rect;
-        SetRect (rect, x1DC - 1, y2DC, x2DC + 1, y1DC);
+        CGFloat red = 0.3, green = 0.3, blue = 0.2;
+        CGContextSetRGBStrokeColor (d_macGraphicsContext, red, green, blue, 1.0);
+		if (! isRetinaDisplay)
+			x1DC --;
+		x1DC += 0.5, x2DC -= 0.5, y1DC -= 0.5, y2DC += 0.5, width = x2DC - x1DC, height = y1DC - y2DC;
+        CGRect rect = CGRectMake (x1DC, y2DC, width, height);
         CGContextAddRect (d_macGraphicsContext, rect);
         CGContextStrokePath (d_macGraphicsContext);
-		if (width > 2 && height > 2) {
-			gray = 0.3;
-			CGContextSetRGBStrokeColor (d_macGraphicsContext, gray, gray, gray, 1.0);
-			CGContextMoveToPoint (d_macGraphicsContext, x1DC, y1DC - 1);
-			CGContextAddLineToPoint (d_macGraphicsContext, x2DC - 2, y1DC - 1);
-			CGContextMoveToPoint (d_macGraphicsContext, x2DC - 2, y1DC - 1);
-			CGContextAddLineToPoint (d_macGraphicsContext, x2DC - 2, y2DC);
+		x1DC ++, x2DC --, y1DC --, y2DC ++, width = x2DC - x1DC, height = y1DC - y2DC;
+		if (width > 0 && height > 0) {
+			red = 0.5, green = 0.5, blue = 0.4;
+			CGContextSetRGBStrokeColor (d_macGraphicsContext, red, green, blue, 1.0);
+			CGContextMoveToPoint (d_macGraphicsContext, x1DC, y1DC);
+			CGContextAddLineToPoint (d_macGraphicsContext, x2DC, y1DC);
+			CGContextMoveToPoint (d_macGraphicsContext, x2DC, y1DC);
+			CGContextAddLineToPoint (d_macGraphicsContext, x2DC, y2DC);
 			CGContextStrokePath (d_macGraphicsContext);
-			gray = 1.0;
-			CGContextSetRGBStrokeColor (d_macGraphicsContext, gray, gray, gray, 1.0);
-			CGContextMoveToPoint (d_macGraphicsContext, x1DC, y1DC - 1);
-			CGContextAddLineToPoint (d_macGraphicsContext, x1DC, y2DC + 1);
-			CGContextMoveToPoint (d_macGraphicsContext, x1DC, y2DC + 1);
-            CGContextAddLineToPoint (d_macGraphicsContext, x2DC - 2, y2DC + 1);
+			red = 1.0, green = 1.0, blue = 0.9;
+			CGContextSetRGBStrokeColor (d_macGraphicsContext, red, green, blue, 1.0);
+			CGContextMoveToPoint (d_macGraphicsContext, x1DC, y1DC);
+			CGContextAddLineToPoint (d_macGraphicsContext, x1DC, y2DC);
+			CGContextMoveToPoint (d_macGraphicsContext, x1DC, y2DC);
+            CGContextAddLineToPoint (d_macGraphicsContext, x2DC, y2DC);
             CGContextStrokePath (d_macGraphicsContext);
-			if (width > 4 && height > 4) {
-				gray = 0.65;
-				CGContextSetRGBFillColor (d_macGraphicsContext, gray, gray, gray, 1.0);
-				SetRect (rect, x1DC + 1, y2DC + 1, x2DC - 4, y1DC - 4);
+			if (width > 2 && height > 2) {
+				if (! isRetinaDisplay) x1DC ++, width = x2DC - x1DC, height = y1DC - y2DC;
+				red = 0.75, green = 0.75, blue = 0.65;
+				CGContextSetRGBFillColor (d_macGraphicsContext, red, green, blue, 1.0);
+				rect = CGRectMake (x1DC, y2DC, width, height);
 				CGContextFillRect (d_macGraphicsContext, rect);
             }
         }
@@ -656,8 +647,9 @@ void structGraphicsScreen :: v_button (long x1DC, long x2DC, long y1DC, long y2D
 	#endif
 }
 
-void structGraphics :: v_roundedRectangle (long x1DC, long x2DC, long y1DC, long y2DC, long r) {
-	long dy = yIsZeroAtTheTop ? - r : r, xyDC [4];
+void structGraphics :: v_roundedRectangle (double x1DC, double x2DC, double y1DC, double y2DC, double r) {
+	double dy = yIsZeroAtTheTop ? - r : r;
+	double xyDC [4];
 	ORDER_DC
 	xyDC [0] = x1DC + r;
 	xyDC [1] = y1DC;
@@ -685,9 +677,9 @@ void structGraphics :: v_roundedRectangle (long x1DC, long x2DC, long y1DC, long
 	v_arc (x1DC + r, y1DC + dy, r, 180, 270);
 }
 
-void structGraphicsScreen :: v_roundedRectangle (long x1DC, long x2DC, long y1DC, long y2DC, long r) {
+void structGraphicsScreen :: v_roundedRectangle (double x1DC, double x2DC, double y1DC, double y2DC, double r) {
 	#if win
-		long dy = yIsZeroAtTheTop ? - r : r, xyDC [4];
+		double dy = yIsZeroAtTheTop ? - r : r, xyDC [4];
 		ORDER_DC
 		winPrepareLine (this);
 		RoundRect (d_gdiGraphicsContext, x1DC, y2DC, x2DC + 1, y1DC + 1, r + r, r + r);
@@ -700,8 +692,8 @@ void structGraphicsScreen :: v_roundedRectangle (long x1DC, long x2DC, long y1DC
 
 /* Fourth level. */
 
-void structGraphics :: v_fillRoundedRectangle (long x1DC, long x2DC, long y1DC, long y2DC, long r) {
-	long dy = yIsZeroAtTheTop ? - r : r;
+void structGraphics :: v_fillRoundedRectangle (double x1DC, double x2DC, double y1DC, double y2DC, double r) {
+	double dy = yIsZeroAtTheTop ? - r : r;
 	ORDER_DC
 	v_fillCircle (x2DC - r, y1DC + dy, r);
 	v_fillCircle (x2DC - r, y2DC - dy, r);
@@ -718,9 +710,9 @@ void structGraphics :: v_fillRoundedRectangle (long x1DC, long x2DC, long y1DC,
 
 void Graphics_polyline (Graphics me, long numberOfPoints, double *xWC, double *yWC) {   // base 0
 	if (numberOfPoints == 0) return;
-	long *xyDC;
+	double *xyDC;
 	try {
-		xyDC = Melder_malloc (long, 2 * numberOfPoints);
+		xyDC = Melder_malloc (double, 2 * numberOfPoints);
 	} catch (MelderError) {
 		/*
 		 * Out of memory: silently refuse to draw.
@@ -744,9 +736,9 @@ void Graphics_polyline (Graphics me, long numberOfPoints, double *xWC, double *y
 
 void Graphics_polyline_closed (Graphics me, long numberOfPoints, double *xWC, double *yWC) {   // base 0
 	if (numberOfPoints == 0) return;
-	long *xyDC;
+	double *xyDC;
 	try {
-		xyDC = Melder_malloc (long, 2 * numberOfPoints);
+		xyDC = Melder_malloc (double, 2 * numberOfPoints);
 	} catch (MelderError) {
 		/*
 		 * Out of memory: silently refuse to draw.
@@ -769,7 +761,7 @@ void Graphics_polyline_closed (Graphics me, long numberOfPoints, double *xWC, do
 }
 
 void Graphics_line (Graphics me, double x1WC, double y1WC, double x2WC, double y2WC) {
-	long xyDC [4];
+	double xyDC [4];
 	xyDC [0] = wdx (x1WC);
 	xyDC [1] = wdy (y1WC);
 	xyDC [2] = wdx (x2WC);
@@ -779,9 +771,9 @@ void Graphics_line (Graphics me, double x1WC, double y1WC, double x2WC, double y
 }
 
 void Graphics_fillArea (Graphics me, long numberOfPoints, double *xWC, double *yWC) {
-	long *xyDC;
+	double *xyDC;
 	try {
-		xyDC = Melder_malloc (long, 2 * numberOfPoints);
+		xyDC = Melder_malloc (double, 2 * numberOfPoints);
 	} catch (MelderError) {
 		/*
 		 * Out of memory: silently refuse to draw.
@@ -821,10 +813,10 @@ void Graphics_fillArea (Graphics me, long numberOfPoints, double *xWC, double *y
 	if (n > (x2DC - x1DC + 1) * 2) {  /* Optimize: draw one vertical line for each device x coordinate. */ \
 		long numberOfPixels = x2DC - x1DC + 1, k = 0; \
 		long numberOfPointsActuallyDrawn = numberOfPixels * 2; \
-		long *xyDC; \
+		double *xyDC; \
 		TYPE lastMini; \
 		if (numberOfPointsActuallyDrawn < 1) return; \
-		xyDC = Melder_malloc_f (long, 2 * numberOfPointsActuallyDrawn); \
+		xyDC = Melder_malloc_f (double, 2 * numberOfPointsActuallyDrawn); \
 		for (i = 0; i < numberOfPixels; i ++) { \
 			long j, jmin = ix1 + i / scale, jmax = ix1 + (i + 1) / scale; \
 			TYPE mini, maxi; \
@@ -884,7 +876,7 @@ void Graphics_fillArea (Graphics me, long numberOfPoints, double *xWC, double *y
 		if (k > 1) my v_polyline (k / 2, xyDC, false); \
 		Melder_free (xyDC); \
 	} else {  /* Normal. */  \
-		long *xyDC = Melder_malloc_f (long, 2 * n); \
+		double *xyDC = Melder_malloc_f (double, 2 * n); \
 		for (i = 0; i < n; i ++) { \
 			long ix = ix1 + i; \
 			long value = wdy (yWC [STAGGER (ix)]); \
@@ -976,6 +968,11 @@ void Graphics_fillCircle_mm (Graphics me, double xWC, double yWC, double diamete
 	if (my recording) { op (FILL_CIRCLE_MM, 3); put (xWC); put (yWC); put (diameter); }
 }
 
+void Graphics_speckle (Graphics me, double xWC, double yWC) {
+	my v_fillCircle (wdx (xWC), wdy (yWC), ceil (0.5 * my speckleSize * my resolution / 25.4));
+	if (my recording) { op (SPECKLE, 2); put (xWC); put (yWC); }
+}
+
 void Graphics_rectangle_mm (Graphics me, double xWC, double yWC, double horSide, double vertSide) {
 	long xDC = wdx (xWC), yDC = wdy (yWC);
 	long halfHorSide = ceil (0.5 * horSide * my resolution / 25.4);
@@ -1022,13 +1019,13 @@ void Graphics_fillArc (Graphics me, double xWC, double yWC, double rWC, double f
 
 /* Arrows. */
 
-void structGraphics :: v_arrowHead (long xDC, long yDC, double angle) {
+void structGraphics :: v_arrowHead (double xDC, double yDC, double angle) {
 	(void) xDC;
 	(void) yDC;
 	(void) angle;
 }
 
-void structGraphicsScreen :: v_arrowHead (long xDC, long yDC, double angle) {
+void structGraphicsScreen :: v_arrowHead (double xDC, double yDC, double angle) {
 	#if cairo
 		if (d_cairoGraphicsContext == NULL) return;
 		double size = 10.0 * resolution * arrowSize / 75.0; // TODO: die 75 zou dat niet de scherm resolutie moeten worden?
@@ -1051,10 +1048,9 @@ void structGraphicsScreen :: v_arrowHead (long xDC, long yDC, double angle) {
 	#elif mac
 		GraphicsQuartz_initDraw (this);
 		quartzPrepareFill (this);
+		NSCAssert( d_macGraphicsContext, @"nil context");
 		CGContextSaveGState (d_macGraphicsContext);
 		CGContextBeginPath (d_macGraphicsContext);
-    NSCAssert( d_macGraphicsContext, @"nil context");
-
 		CGContextTranslateCTM (d_macGraphicsContext, xDC, yDC);
 		CGContextRotateCTM (d_macGraphicsContext, - angle * NUMpi / 180);
 		CGContextMoveToPoint (d_macGraphicsContext, 0.0, 0.0);
@@ -1068,16 +1064,16 @@ void structGraphicsScreen :: v_arrowHead (long xDC, long yDC, double angle) {
 	#endif
 }
 
-void structGraphicsPostscript :: v_arrowHead (long xDC, long yDC, double angle) {
-	long length = resolution * arrowSize / 10, radius = resolution * arrowSize / 30;
-	d_printf (d_file, "gsave %ld %ld translate %f rotate\n"
-		"N 0 0 M -%ld 0 %ld -60 60 arc closepath fill grestore\n", xDC, yDC, angle, length, radius);
+void structGraphicsPostscript :: v_arrowHead (double xDC, double yDC, double angle) {
+	double length = resolution * arrowSize / 10, radius = resolution * arrowSize / 30;
+	d_printf (d_file, "gsave %.7g %.7g translate %.7g rotate\n"
+		"N 0 0 M %.7g 0 %.7g -60 60 arc closepath fill grestore\n", xDC, yDC, angle, - length, radius);
 }
 
 void Graphics_arrow (Graphics me, double x1WC, double y1WC, double x2WC, double y2WC) {
 	double angle = (180.0 / NUMpi) * atan2 ((wdy (y2WC) - wdy (y1WC)) * (my yIsZeroAtTheTop ? -1 : 1), wdx (x2WC) - wdx (x1WC));
 	double size = my screen ? 10.0 * my resolution * my arrowSize / 72.0 : my resolution * my arrowSize / 10;
-	long xyDC [4];
+	double xyDC [4];
 	xyDC [0] = wdx (x1WC);
 	xyDC [1] = wdy (y1WC);
 	xyDC [2] = wdx (x2WC) + (my screen ? 0.7 : 0.6) * cos ((angle - 180) * NUMpi / 180) * size;
@@ -1090,7 +1086,7 @@ void Graphics_arrow (Graphics me, double x1WC, double y1WC, double x2WC, double
 void Graphics_doubleArrow (Graphics me, double x1WC, double y1WC, double x2WC, double y2WC) {
 	double angle = (180.0 / NUMpi) * atan2 ((wdy (y2WC) - wdy (y1WC)) * (my yIsZeroAtTheTop ? -1 : 1), wdx (x2WC) - wdx (x1WC));
 	double size = my screen ? 10.0 * my resolution * my arrowSize / 72.0 : my resolution * my arrowSize / 10;
-	long xyDC [4];
+	double xyDC [4];
 	xyDC [0] = wdx (x1WC) + (my screen ? 0.7 : 0.6) * cos (angle * NUMpi / 180) * size;
 	xyDC [1] = wdy (y1WC) + (my yIsZeroAtTheTop ? -1.0 : 1.0) * (my screen ? 0.7 : 0.6) * sin (angle * NUMpi / 180) * size;
 	xyDC [2] = wdx (x2WC) + (my screen ? 0.7 : 0.6) * cos ((angle - 180) * NUMpi / 180) * size;
@@ -1134,10 +1130,16 @@ void Graphics_setArrowSize (Graphics me, double arrowSize) {
 	if (my recording) { op (SET_ARROW_SIZE, 1); put (arrowSize); }
 }
 
+void Graphics_setSpeckleSize (Graphics me, double speckleSize) {
+	my speckleSize = speckleSize;
+	if (my recording) { op (SET_SPECKLE_SIZE, 1); put (speckleSize); }
+}
+
 /* Inquiries. */
 
 int Graphics_inqLineType (Graphics me) { return my lineType; }
 double Graphics_inqLineWidth (Graphics me) { return my lineWidth; }
 double Graphics_inqArrowSize (Graphics me) { return my arrowSize; }
+double Graphics_inqSpeckleSize (Graphics me) { return my speckleSize; }
 
 /* End of file Graphics_linesAndAreas.cpp */
diff --git a/sys/Graphics_mouse.cpp b/sys/Graphics_mouse.cpp
index 2223f04..a91ba74 100644
--- a/sys/Graphics_mouse.cpp
+++ b/sys/Graphics_mouse.cpp
@@ -64,7 +64,7 @@ void structGraphicsScreen :: v_getMouseLocation (double *xWC, double *yWC) {
 	#elif cocoa
         NSPoint mouseLoc = [[d_macView window]  mouseLocationOutsideOfEventStream];
         mouseLoc = [d_macView   convertPoint: mouseLoc   fromView: nil];
-        mouseLoc. y = d_macView. bounds. size. height - mouseLoc. y;
+        //mouseLoc. y = d_macView. bounds. size. height - mouseLoc. y;
         Graphics_DCtoWC (this, mouseLoc. x, mouseLoc. y, xWC, yWC);
 	#elif win
 		POINT pos;
diff --git a/sys/Graphics_record.cpp b/sys/Graphics_record.cpp
index 2d2e1b3..831c0d5 100644
--- a/sys/Graphics_record.cpp
+++ b/sys/Graphics_record.cpp
@@ -1,6 +1,6 @@
 /* Graphics_record.cpp
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -98,36 +98,30 @@ void Graphics_clearRecording (Graphics me) {
 	}
 }
 
-// TODO: Paul, ik zou er een enorme fan van zijn als bij deze functie
-// ook een bounding box van events kan worden meegeven.
 void Graphics_play (Graphics me, Graphics thee) {
 	double *p = my record, *endp = p + my irecord;
 	bool wasRecording = my recording;
 	if (! p) return;
-	my recording = false;   /* Temporarily, in case me == thee. */
+	my recording = false;   // temporarily, in case me == thee
 	while (p < endp) {
 		#define get  (* ++ p)
 		#define mget(n)  (p += n, p - n)
 		#define sget(n)  ((char *) (p += n, p - n + 1))
-//		#define skip(x1, x2, y1, y2) if ((((x1 >= thy x1DC && x1 <= thy x2DC) || (x2 >= thy x1DC && x2 <= thy x2DC)) && ((y1 >= thy y1DC && y1 <= thy y2DC) || (y2 >= thy y1DC && y2 <= thy y2DC))) == FALSE) { g_debug("SKIP!"); break; }
 		int opcode = (int) get;
 		(void) (long) get;   // ignore number of arguments
 		switch (opcode) {
 			case SET_VIEWPORT:
 			{  double x1NDC = get, x2NDC = get, y1NDC = get, y2NDC = get;
-//				skip(x1NDC, x2NDC, y1NDC, y2NDC)
 				Graphics_setViewport (thee, x1NDC, x2NDC, y1NDC, y2NDC);
 			}  break;
 			case SET_INNER: Graphics_setInner (thee); break;
 			case UNSET_INNER: Graphics_unsetInner (thee); break;
 			case SET_WINDOW:
 			{  double x1 = get, x2 = get, y1 = get, y2 = get;
-//				skip(x1, x2, y1, y2)
 				Graphics_setWindow (thee, x1, x2, y1, y2);
 			}  break;
 			case TEXT:
 			{  double x = get, y = get; long length = get; char *text_utf8 = sget (length);
-//				skip(x, x, y, y)
 				Graphics_text (thee, x, y, Melder_peekUtf8ToWcs (text_utf8));
 			}  break;
 			case POLYLINE:
@@ -136,7 +130,6 @@ void Graphics_play (Graphics me, Graphics thee) {
 			} break;
 			case LINE:
 			{  double x1 = get, y1 = get, x2 = get, y2 = get;
-//				skip(x1, x2, y1, y2)
 				Graphics_line (thee, x1, y1, x2, y2);
 			}  break;
 			case ARROW:
@@ -157,7 +150,6 @@ void Graphics_play (Graphics me, Graphics thee) {
 			}  break;
 			case FILL_RECTANGLE:
 			{  double x1 = get, x2 = get, y1 = get, y2 = get;
-//				skip(x1, x2, y1, y2)
 				Graphics_fillRectangle (thee, x1, x2, y1, y2);
 			}  break;
 			case CIRCLE:
@@ -391,6 +383,12 @@ void Graphics_play (Graphics me, Graphics thee) {
 								0, nrow - 1, y1, y2, minimum, maximum);
 				Melder_free (z);
 			}  break;
+			case SET_COLOUR_SCALE: Graphics_setColourScale (thee, (enum kGraphics_colourScale) get); break;
+			case SET_SPECKLE_SIZE: Graphics_setSpeckleSize (thee, get); break;
+			case SPECKLE:
+			{  double x = get, y = get;
+				Graphics_speckle (thee, x, y);
+			}  break;
 			default:
 				my recording = wasRecording;
 				Melder_flushError ("Graphics_play: unknown opcode (%d).\n%f %f", opcode, p [-1], p [1]);
@@ -422,13 +420,23 @@ void Graphics_writeRecordings (Graphics me, FILE *f) {
 			binputr4 ((float) numberOfArguments, f);
 		}
 		if (opcode == TEXT) {
-			binputr4 (get, f);   /* x */
-			binputr4 (get, f);   /* y */
-			binputr4 (get, f);   /* length */
+			binputr4 (get, f);   // x
+			binputr4 (get, f);   // y
+			binputr4 (get, f);   // length
 			Melder_assert (sizeof (double) == 8);
 			if ((long) fwrite (++ p, 8, numberOfArguments - 3, f) < numberOfArguments - 3)   // text
 				Melder_throw ("Error writing graphics recordings.");
 			p += numberOfArguments - 4;
+		} else if (opcode == IMAGE_FROM_FILE) {
+			binputr4 (get, f);   // x1
+			binputr4 (get, f);   // x2
+			binputr4 (get, f);   // y1
+			binputr4 (get, f);   // y2
+			binputr4 (get, f);   // length
+			Melder_assert (sizeof (double) == 8);
+			if ((long) fwrite (++ p, 8, numberOfArguments - 5, f) < numberOfArguments - 5)   // text
+				Melder_throw ("Error writing graphics recordings.");
+			p += numberOfArguments - 6;
 		} else {
 			for (long i = numberOfArguments; i > 0; i --) binputr4 (get, f);
 		}
@@ -462,6 +470,15 @@ void Graphics_readRecordings (Graphics me, FILE *f) {
 				if ((long) fread (++ p, 8, numberOfArguments - 3, f) < numberOfArguments - 3)   // text
 					Melder_throw ("Error reading graphics recordings.");
 				p += numberOfArguments - 4;
+			} else if (opcode == IMAGE_FROM_FILE) {
+				put (bingetr4 (f));   // x1
+				put (bingetr4 (f));   // x2
+				put (bingetr4 (f));   // y1
+				put (bingetr4 (f));   // y2
+				put (bingetr4 (f));   // length
+				if ((long) fread (++ p, 8, numberOfArguments - 5, f) < numberOfArguments - 5)   // text
+					Melder_throw ("Error reading graphics recordings.");
+				p += numberOfArguments - 6;
 			} else {
 				for (long i = numberOfArguments; i > 0; i --) put (bingetr4 (f));
 			}
diff --git a/sys/Graphics_text.cpp b/sys/Graphics_text.cpp
index 59173b9..9a1b03a 100644
--- a/sys/Graphics_text.cpp
+++ b/sys/Graphics_text.cpp
@@ -1,6 +1,6 @@
 /* Graphics_text.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -77,8 +77,7 @@ extern const char * ipaSerifRegular24 [1 + 255-33+1 + 1] [24 + 1];
 
 #if win
 	#define win_MAXIMUM_FONT_SIZE  500
-	static HFONT screenFonts [1 + kGraphics_font_DINGBATS] [1+win_MAXIMUM_FONT_SIZE] [1 + Graphics_BOLD_ITALIC];
-	static HFONT printerFonts [1 + kGraphics_font_DINGBATS] [1+win_MAXIMUM_FONT_SIZE] [1 + Graphics_BOLD_ITALIC];
+	static HFONT fonts [1 + kGraphics_resolution_MAX] [1 + kGraphics_font_JAPANESE] [1+win_MAXIMUM_FONT_SIZE] [1 + Graphics_BOLD_ITALIC];
 	static int ipaAvailable = FALSE;
 	static int win_size2isize (int size) { return size > win_MAXIMUM_FONT_SIZE ? win_MAXIMUM_FONT_SIZE : size; }
 	static int win_isize2size (int isize) { return isize; }
@@ -88,8 +87,12 @@ extern const char * ipaSerifRegular24 [1 + 255-33+1 + 1] [24 + 1];
         #include <Carbon/Carbon.h>
     #endif
 	#include "macport_off.h"
-	static ATSFontRef theTimesAtsuiFont, theHelveticaAtsuiFont, theCourierAtsuiFont, theSymbolAtsuiFont,
-		thePalatinoAtsuiFont, theIpaTimesAtsuiFont, theIpaPalatinoAtsuiFont, theZapfDingbatsAtsuiFont, theArabicAtsuiFont;
+	#if useCarbon
+		static ATSFontRef theTimesAtsuiFont, theHelveticaAtsuiFont, theCourierAtsuiFont, theSymbolAtsuiFont,
+			thePalatinoAtsuiFont, theIpaTimesAtsuiFont, theIpaPalatinoAtsuiFont, theZapfDingbatsAtsuiFont, theArabicAtsuiFont;
+	#else
+		static bool hasTimes, hasHelvetica, hasCourier, hasSymbol, hasPalatino, hasDoulos, hasCharis, hasIpaSerif;
+	#endif
 	static CTFontRef theScreenFonts [1 + kGraphics_font_DINGBATS] [1 + Graphics_BOLD_ITALIC];
 	static RGBColor theWhiteColour = { 0xFFFF, 0xFFFF, 0xFFFF }, theBlueColour = { 0, 0, 0xFFFF };
 #endif
@@ -119,11 +122,18 @@ static HFONT loadFont (GraphicsScreen me, int font, int size, int style) {
 	spec. lfWeight = style & Graphics_BOLD ? FW_BOLD : 0;
 	spec. lfItalic = style & Graphics_ITALIC ? 1 : 0;
 	spec. lfUnderline = spec. lfStrikeOut = 0;
-	spec. lfCharSet = font == kGraphics_font_SYMBOL ? SYMBOL_CHARSET : font >= kGraphics_font_IPATIMES ? DEFAULT_CHARSET : ANSI_CHARSET;
+	spec. lfCharSet =
+		font == kGraphics_font_SYMBOL ? SYMBOL_CHARSET :
+		font == kGraphics_font_CHINESE ? DEFAULT_CHARSET :
+		font == kGraphics_font_JAPANESE ? DEFAULT_CHARSET :
+		font >= kGraphics_font_IPATIMES ? DEFAULT_CHARSET :
+		ANSI_CHARSET;
 	spec. lfOutPrecision = spec. lfClipPrecision = spec. lfQuality = 0;
 	spec. lfPitchAndFamily =
 		( font == kGraphics_font_COURIER ? FIXED_PITCH : font == kGraphics_font_IPATIMES ? DEFAULT_PITCH : VARIABLE_PITCH ) |
 		( font == kGraphics_font_HELVETICA ? FF_SWISS : font == kGraphics_font_COURIER ? FF_MODERN :
+		  font == kGraphics_font_CHINESE ? FF_DONTCARE :
+		  font == kGraphics_font_JAPANESE ? FF_DONTCARE :
 		  font >= kGraphics_font_IPATIMES ? FF_DONTCARE : FF_ROMAN );
 	if (font == kGraphics_font_IPATIMES && ! ipaInited && Melder_debug != 15) {
 		LOGFONTW logFont;
@@ -149,6 +159,8 @@ static HFONT loadFont (GraphicsScreen me, int font, int size, int style) {
 		font == kGraphics_font_SYMBOL    ? L"Symbol" :
 		font == kGraphics_font_IPATIMES  ? ( doulosAvailable && style == 0 ? L"Doulos SIL" : charisAvailable ? L"Charis SIL" : L"Times New Roman" ) :
 		font == kGraphics_font_DINGBATS  ? L"Wingdings" :
+		font == kGraphics_font_CHINESE   ? L"SimSun" :
+		font == kGraphics_font_JAPANESE  ? L"MS UI Gothic" :
 		L"");
 	return CreateFontIndirectW (& spec);
 }
@@ -216,17 +228,15 @@ static void charSize (I, _Graphics_widechar *lc) {
 			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;
+			if ((unsigned int) lc -> kar >= 0x2E80 && (unsigned int) lc -> kar <= 0x9FFF)
+				font = ( theGraphicsCjkFontStyle == kGraphics_cjkFontStyle_CHINESE ? kGraphics_font_CHINESE : kGraphics_font_JAPANESE );
 			size = lc -> size < 100 ? smallSize : normalSize;
 			style = lc -> style & (Graphics_ITALIC | Graphics_BOLD);   // take out Graphics_CODE
-			fontInfo = my printer || my metafile ? printerFonts [font] [size] [style] : screenFonts [font] [size] [style];
+			fontInfo = fonts [my resolutionNumber] [font] [size] [style];
 			if (! fontInfo) {
 				fontInfo = loadFont (me, font, size, style);
 				if (! fontInfo) return;
-				if (my printer || my metafile) {
-					printerFonts [font] [size] [style] = fontInfo;
-				} else {
-					screenFonts [font] [size] [style] = fontInfo;
-				}
+				fonts [my resolutionNumber] [font] [size] [style] = fontInfo;
 			}
 			if (font == kGraphics_font_IPATIMES && ! ipaAvailable) {
 				int overstrike = ipaSerifRegular24 [info -> psEncoding - 32] [0] [0] == 'o';
@@ -238,7 +248,14 @@ static void charSize (I, _Graphics_widechar *lc) {
 			} else {
 				SIZE extent;
 				wchar_t code;
-				lc -> code = font == kGraphics_font_IPATIMES || font == kGraphics_font_TIMES || font == kGraphics_font_HELVETICA || font == kGraphics_font_COURIER ? (unsigned short) lc -> kar : info -> winEncoding;
+				lc -> code =
+					font == kGraphics_font_IPATIMES ||
+					font == kGraphics_font_TIMES ||
+					font == kGraphics_font_HELVETICA ||
+					font == kGraphics_font_CHINESE ||
+					font == kGraphics_font_JAPANESE ||
+					font == kGraphics_font_COURIER ? (unsigned short) lc -> kar :
+					info -> winEncoding;
 				if (lc -> code == 0) {
 					_Graphics_widechar *lc2;
 					if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_SCHWA_WITH_HOOK) {
@@ -268,24 +285,66 @@ static void charSize (I, _Graphics_widechar *lc) {
 			lc -> font.integer = font;   // kGraphics_font_HELVETICA .. kGraphics_font_DINGBATS
 			lc -> size = size;   // 0..4 instead of 10..24
 			lc -> style = style;   // without Graphics_CODE
-        #elif cocoa
+		#elif cocoa
 			/*
 			 * Determine the font family.
 			 */
 			Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
 			int font =
-				info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
+				info -> alphabet == Longchar_SYMBOL || // ? kGraphics_font_SYMBOL :
 				info -> alphabet == Longchar_PHONETIC ?
-					( my font == kGraphics_font_TIMES && lc -> style == 0 ? kGraphics_font_IPATIMES : kGraphics_font_IPAPALATINO ) :
-				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 ? ( lc -> style == 0 ? kGraphics_font_IPATIMES : kGraphics_font_TIMES ) :
-				my font == kGraphics_font_HELVETICA ? kGraphics_font_HELVETICA :
-				my font == kGraphics_font_PALATINO ? kGraphics_font_IPAPALATINO :
+					( 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 ?
+					( hasDoulos ?
+						( lc -> style == 0 ?
+							kGraphics_font_IPATIMES :
+						  lc -> style == Graphics_ITALIC ?
+							kGraphics_font_TIMES :
+						  hasCharis ?
+							kGraphics_font_IPAPALATINO :
+							kGraphics_font_TIMES 
+						) :
+						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_IPAPALATINO :
+						kGraphics_font_PALATINO
+					) :
 				my font;   // why not lc -> font.integer?
 			Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
-            lc -> font.string = NULL;   // this erases font.integer!
+			lc -> font.string = NULL;   // this erases font.integer!
 
 			/*
 			 * Determine the style.
@@ -307,7 +366,11 @@ static void charSize (I, _Graphics_widechar *lc) {
 					case kGraphics_font_TIMES:       { [attributes   setObject: @"Times"           forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_HELVETICA:   { [attributes   setObject: @"Arial"           forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_COURIER:     { [attributes   setObject: @"Courier New"     forKey: (id) kCTFontNameAttribute]; } break;
-					case kGraphics_font_PALATINO:    { [attributes   setObject: @"Palatino"        forKey: (id) kCTFontNameAttribute]; } break;
+					case kGraphics_font_PALATINO:    { if (Melder_debug == 900)
+															[attributes   setObject: @"DG Meta Serif Science" forKey: (id) kCTFontNameAttribute];
+													   else
+														    [attributes   setObject: @"Palatino"              forKey: (id) kCTFontNameAttribute];
+													 } break;
 					case kGraphics_font_SYMBOL:      { [attributes   setObject: @"Symbol"          forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_IPATIMES:    { [attributes   setObject: @"Doulos SIL"      forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_IPAPALATINO: { [attributes   setObject: @"Charis SIL"      forKey: (id) kCTFontNameAttribute]; } break;
@@ -321,8 +384,8 @@ static void charSize (I, _Graphics_widechar *lc) {
  				theScreenFonts [font] [style] = ctFont;
 			}
 
-            int normalSize = my fontSize * my resolution / 72.0;
-            lc -> size = lc -> size < 100 ? (3 * normalSize + 2) / 4 : normalSize;
+			int normalSize = my fontSize * my resolution / 72.0;
+			lc -> size = lc -> size < 100 ? (3 * normalSize + 2) / 4 : normalSize;
         
 			uint16_t codes16 [2];
 			int nchars = 1;
@@ -339,44 +402,35 @@ static void charSize (I, _Graphics_widechar *lc) {
 				length: nchars * 2
 				encoding: NSUTF16LittleEndianStringEncoding   // BUG: should be NSUTF16NativeStringEncoding, except that that doesn't exist
 				];
-			//CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
-            //NSCAssert (context, @"nil context");
-
-			//[NSGraphicsContext setCurrentContext: my d_macGraphicsContext];
-			//Melder_assert (my d_macGraphicsContext != NULL);
-			//Melder_assert (context == my d_macGraphicsContext);
-            //CGContextSaveGState (context);
-            //CGContextSetTextMatrix (context, CGAffineTransformIdentity);   // this could set the "current context" for CoreText
-
-            CFRange textRange = CFRangeMake (0, [s length]);
-            
-            CFMutableAttributedStringRef string = CFAttributedStringCreateMutable (kCFAllocatorDefault, [s length]);
-            CFAttributedStringReplaceString (string, CFRangeMake (0, 0), (CFStringRef) s);
-            CFAttributedStringSetAttribute (string, textRange, kCTFontAttributeName, ctFont);
-        
-            /*
-             * Measure.
-             */
-        
+
+			CFRange textRange = CFRangeMake (0, [s length]);
+
+			CFMutableAttributedStringRef string = CFAttributedStringCreateMutable (kCFAllocatorDefault, [s length]);
+			CFAttributedStringReplaceString (string, CFRangeMake (0, 0), (CFStringRef) s);
+			CFAttributedStringSetAttribute (string, textRange, kCTFontAttributeName, ctFont);
+
+			/*
+			 * Measure.
+			 */
+
             // Create a path to render text in
-            CGMutablePathRef path = CGPathCreateMutable ();
-            NSRect measureRect = NSMakeRect (0, 0, CGFLOAT_MAX, CGFLOAT_MAX);
-            CGPathAddRect (path, NULL, (CGRect) measureRect);
-            
+			CGMutablePathRef path = CGPathCreateMutable ();
+			NSRect measureRect = NSMakeRect (0, 0, CGFLOAT_MAX, CGFLOAT_MAX);
+			CGPathAddRect (path, NULL, (CGRect) measureRect);
+        
 			CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString ((CFAttributedStringRef) string);
 			CFRange fitRange;
 			CGSize targetSize = CGSizeMake (lc -> width, CGFLOAT_MAX);
 			CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints (framesetter, textRange, NULL, targetSize, & fitRange);
-            CFRelease (framesetter);
-            CFRelease (string);
-            [s release];
-            //CGContextRestoreGState (context);
+			CFRelease (framesetter);
+			CFRelease (string);
+			[s release];
+			CFRelease (path);
 
 			bool isDiacritic = info -> ps.times == 0;
-            lc -> width = isDiacritic ? 0.0 : frameSize.width * lc -> size / 100.0;
-			if (font == kGraphics_font_IPATIMES || font == kGraphics_font_IPAPALATINO) lc -> baseline -= 6;   // BUG: not good enough
-            lc -> baseline *= my fontSize * 0.01;
-            lc -> code = lc -> kar;
+			lc -> width = isDiacritic ? 0.0 : frameSize.width * lc -> size / 100.0;
+			lc -> baseline *= 0.01 * normalSize;
+			lc -> code = lc -> kar;
 			lc -> font.integer = font;
 		#elif mac
 			Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
@@ -672,6 +726,7 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 		if (lc -> link) my d_printf (my d_file, "0 0 0 setrgbcolor\n");
 	} else if (my screen) {
 		iam (GraphicsScreen);
+		bool needBitmappedIPA = false;
 		#if cairo
 			if (my duringXor) {
 			} else {
@@ -679,7 +734,6 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 				// TODO!
 			}
 			int font = lc -> font.integer;
-			int needBitmappedIPA = 0;
 		#elif cocoa
 			/*
 			 * Determine the font family.
@@ -695,7 +749,7 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 			 * Determine the font-style combination.
 			 */
 			CTFontSymbolicTraits ctStyle = ( style & Graphics_BOLD ? kCTFontBoldTrait : 0 ) | ( lc -> style & Graphics_ITALIC ? kCTFontItalicTrait : 0 );
-			#if 1
+			#if 0
 				CFStringRef key = kCTFontSymbolicTrait;
 				CFNumberRef value = CFNumberCreate (NULL, kCFNumberIntType, & ctStyle);
 				CFIndex numberOfValues = 1;
@@ -710,7 +764,11 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 					case kGraphics_font_TIMES:       { cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Times New Roman"); } break;
 					case kGraphics_font_HELVETICA:   { cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Arial"          ); } break;
 					case kGraphics_font_COURIER:     { cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Courier New"    ); } break;
-					case kGraphics_font_PALATINO:    { cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Palatino"       ); } break;
+					case kGraphics_font_PALATINO:    { if (Melder_debug == 900)
+															cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"DG Meta Serif Science");
+													   else
+														    cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Palatino");
+													 } break;
 					case kGraphics_font_SYMBOL:      { cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Symbol"         ); } break;
 					case kGraphics_font_IPATIMES:    { cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Doulos SIL"     ); } break;
 					case kGraphics_font_IPAPALATINO: { cfFont = (CFStringRef) Melder_peekWcsToCfstring (L"Charis SIL"     ); } break;
@@ -731,7 +789,11 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 					case kGraphics_font_TIMES:       { [attributes   setObject: @"Times New Roman"   forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_HELVETICA:   { [attributes   setObject: @"Arial"             forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_COURIER:     { [attributes   setObject: @"Courier New"       forKey: (id) kCTFontNameAttribute]; } break;
-					case kGraphics_font_PALATINO:    { [attributes   setObject: @"Palatino"          forKey: (id) kCTFontNameAttribute]; } break;
+					case kGraphics_font_PALATINO:    { if (Melder_debug == 900)
+															[attributes   setObject: @"DG Meta Serif Science" forKey: (id) kCTFontNameAttribute];
+													   else
+														    [attributes   setObject: @"Palatino"              forKey: (id) kCTFontNameAttribute];
+													 } break;
 					case kGraphics_font_SYMBOL:      { [attributes   setObject: @"Symbol"            forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_IPATIMES:    { [attributes   setObject: @"Doulos SIL"        forKey: (id) kCTFontNameAttribute]; } break;
 					case kGraphics_font_IPAPALATINO: { [attributes   setObject: @"Charis SIL"        forKey: (id) kCTFontNameAttribute]; } break;
@@ -744,7 +806,6 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 			CTFontRef ctFont = CTFontCreateWithFontDescriptor (ctFontDescriptor, lc -> size, NULL);
 			CFRelease (ctFontDescriptor);
 
-			int needBitmappedIPA = 0;
 			bool hasHighUnicodeValues = false;
 			for (long i = 0; i < nchars; i ++) {
 				hasHighUnicodeValues |= codes [i] > 0xFFFF;
@@ -793,77 +854,46 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
              * Draw.
              */
     
-            // Create a path to render text in
-            CGMutablePathRef path = CGPathCreateMutable ();
-            NSRect measureRect = NSMakeRect (0, 0, CGFLOAT_MAX, CGFLOAT_MAX);
-            CGPathAddRect (path, NULL, (CGRect) measureRect);
-
             CGContextSetTextMatrix (my d_macGraphicsContext, CGAffineTransformIdentity);   // this could set the "current context" for CoreText
-
-            // create the framesetter and render text
-            CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString ((CFAttributedStringRef) string);
-			Melder_assert (framesetter != NULL);
-            CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, length), path, NULL);
-			Melder_assert (frame != NULL);
-        
-            CFRange fitRange;
-            CGSize targetSize = CGSizeMake (CGFLOAT_MAX, CGFLOAT_MAX);
-            CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints (framesetter, textRange, NULL, targetSize, & fitRange);
-            CFRelease (path);
             CFRelease (color);
-            CFRelease (frame);
-            path = CGPathCreateMutable ();
-            NSRect drawRect = NSMakeRect (0, 0, frameSize.width, frameSize.height);
-			trace ("frame %f %f", frameSize.width, frameSize.height);
-            CGPathAddRect (path, NULL, (CGRect) drawRect);
-            frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, length), path, NULL);
-			Melder_assert (frame != NULL);
 
 			if (my d_macView) {
 				[my d_macView   lockFocus];
 				my d_macGraphicsContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
 			}
             CGContextSaveGState (my d_macGraphicsContext);
-            CGContextTranslateCTM (my d_macGraphicsContext, xDC, yDC + descent);
+            CGContextTranslateCTM (my d_macGraphicsContext, xDC, yDC);
             if (my yIsZeroAtTheTop) CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
             CGContextRotateCTM (my d_macGraphicsContext, my textRotation * NUMpi / 180.0);
 
-			//CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
-			//Melder_assert (my d_macGraphicsContext != NULL);
-			//Melder_assert (context == my d_macGraphicsContext);
+			CTLineRef line = CTLineCreateWithAttributedString (string);
             if (my duringXor) {
                 CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeDifference);
                 CGContextSetAllowsAntialiasing (my d_macGraphicsContext, false);
-                CTFrameDraw (frame, my d_macGraphicsContext);
+				CTLineDraw (line, my d_macGraphicsContext);
                 CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeNormal);
                 CGContextSetAllowsAntialiasing (my d_macGraphicsContext, true);
             } else {
-                CTFrameDraw (frame, my d_macGraphicsContext);
+				CTLineDraw (line, my d_macGraphicsContext);
             }
+			CFRelease (line);
             CGContextRestoreGState (my d_macGraphicsContext);
-            //CGContextRestoreGState (my d_macGraphicsContext);
 
             // Clean up
-            CFRelease (frame);
-            CFRelease (path);
-            CFRelease (framesetter);
             CFRelease (string);
-			#if 1
-				CFRelease (s);
-			#else
-            	[s release];
-			#endif
+			CFRelease (s);
 			CFRelease (ctFont);
 			if (my d_macView) {
-				CGContextSynchronize (my d_macGraphicsContext);
+				#if useCarbon
+					CGContextSynchronize (my d_macGraphicsContext);
+				#endif
 				[my d_macView   unlockFocus];
 			}
         
 		#elif win
 			int font = lc -> font.integer;
-			int needBitmappedIPA = font == kGraphics_font_IPATIMES && ! ipaAvailable;
+			needBitmappedIPA = font == kGraphics_font_IPATIMES && ! ipaAvailable;
 		#elif mac
-			int needBitmappedIPA = 0;
 			ATSFontRef atsuiFont = (ATSFontRef) lc -> font.integer;
 			Melder_assert (atsuiFont != 0);
 			/*
@@ -966,11 +996,19 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 			if (! needBitmappedIPA) {
 				#if cairo
 					if (my duringXor) {
-						static GdkFont *font = NULL;
-						if (font == NULL) {
-							font = gdk_font_load ("-*-courier-medium-r-normal--*-120-*-*-*-*-iso8859-1");
-						}
-						gdk_draw_text_wc (my d_window, font, my d_gdkGraphicsContext, xDC, yDC, (const GdkWChar *) codes, nchars);
+						#if ALLOW_GDK_DRAWING
+							static GdkFont *font = NULL;
+							if (font == NULL) {
+								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);
 						enum _cairo_font_slant slant   = (lc -> style & Graphics_ITALIC ? CAIRO_FONT_SLANT_ITALIC : CAIRO_FONT_SLANT_NORMAL);
@@ -1010,16 +1048,14 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 						}
 						width += 4;   // For slant.
 						Rectangle (dc, 0, top, width, bottom);
-						SelectFont (dc, my printer || my metafile ? printerFonts [font] [lc -> size] [lc -> style] :
-							screenFonts [font] [lc -> size] [lc -> style]);
+						SelectFont (dc, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
 						SetTextColor (dc, my d_winForegroundColour);
 						TextOutW (dc, 0, baseline, (const wchar_t *) codes16, nchars);
 						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, my printer || my metafile ? printerFonts [font] [lc -> size] [lc -> style] :
-							screenFonts [font] [lc -> size] [lc -> style]);
+						SelectFont (my d_gdiGraphicsContext, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
 						TextOutW (my d_gdiGraphicsContext, xDC, yDC, (const wchar_t *) codes16, nchars);
 						if (lc -> link) SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
 						SelectPen (my d_gdiGraphicsContext, GetStockPen (BLACK_PEN)), SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
@@ -1150,14 +1186,13 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 					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, my printer || my metafile ? printerFonts [font] [lc -> size] [lc -> style] :
-							screenFonts [font] [lc -> size] [lc -> style]);
+						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 = { cosa, - sina, sina, cosa, 0, 0 };
+						XFORM rotate = { (float) cosa, (float) - sina, (float) sina, (float) cosa, 0, 0 };
 						ModifyWorldTransform (my d_gdiGraphicsContext, & rotate, MWT_RIGHTMULTIPLY);
-						XFORM translate = { 1, 0, 0, 1, xDC, yDC };
+						XFORM translate = { 1, 0, 0, 1, (float) xDC, (float) yDC };
 						ModifyWorldTransform (my d_gdiGraphicsContext, & translate, MWT_RIGHTMULTIPLY);
 						TextOutW (my d_gdiGraphicsContext, 0 /*xDC*/, 0 /*yDC*/, (const wchar_t *) codes16, nchars);
 						RestoreDC (my d_gdiGraphicsContext, restore);
@@ -1192,8 +1227,7 @@ static void charDraw (I, int xDC, int yDC, _Graphics_widechar *lc,
 					//Rectangle (dc, 0, 0, maxWidth, maxHeight);
 					SelectPen (dc, GetStockPen (BLACK_PEN));
 					SelectBrush (dc, GetStockBrush (NULL_BRUSH));
-					SelectFont (dc, my printer || my metafile ? printerFonts [font] [lc -> size] [lc -> style] :
-							screenFonts [font] [lc -> size] [lc -> style]);
+					SelectFont (dc, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
 					TextOutW (dc, 0, baseline, (const wchar_t *) codes16, nchars);
 				#endif
 				if (my textRotation == 90.0) { cosa = 0.0; sina = 1.0; }
@@ -1317,10 +1351,10 @@ static void drawOneCell (Graphics me, int xDC, int yDC, _Graphics_widechar lc []
 	_Graphics_widechar *plc, *lastlc;
 	int inLink = 0;
 	switch (my horizontalTextAlignment) {
-		case Graphics_LEFT:      dx = 2; break;
+		case Graphics_LEFT:      dx = 1 + (0.1/72) * my fontSize * my resolution; break;
 		case Graphics_CENTRE:    dx = - width / 2; break;
-		case Graphics_RIGHT:     dx = width ? - width - 1 : 0; break;   // if width is zero, do not step left
-		default:                 dx = 2; break;
+		case Graphics_RIGHT:     dx = width ? - width - (0.1/72) * my fontSize * my resolution : 0; break;   // if width is zero, do not step left
+		default:                 dx = 1 + (0.1/72) * my fontSize * my resolution; break;
 	}
 	switch (my verticalTextAlignment) {
 		case Graphics_BOTTOM:    dy = (0.4/72) * my fontSize * my resolution; break;
@@ -1615,17 +1649,18 @@ static void parseTextIntoCellsLinesRuns (Graphics me, const wchar_t *txt, _Graph
 				in += 2;
 			}
 		} else if (kar == '\"') {
-			kar = ++nquote & 1 ? UNICODE_LEFT_DOUBLE_QUOTATION_MARK : UNICODE_RIGHT_DOUBLE_QUOTATION_MARK;
+			if (! (my font == kGraphics_font_COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode))
+				kar = ++nquote & 1 ? UNICODE_LEFT_DOUBLE_QUOTATION_MARK : UNICODE_RIGHT_DOUBLE_QUOTATION_MARK;
 		} else if (kar == '\'') {
 			kar = UNICODE_RIGHT_SINGLE_QUOTATION_MARK;
 		} else if (kar == '`') {
 			kar = UNICODE_LEFT_SINGLE_QUOTATION_MARK;
 		} else if (kar >= 32 && kar <= 126) {
 			if (kar == 'f') {
-				if (in [0] == 'i' && HAS_FI_AND_FL_LIGATURES) {
+				if (in [0] == 'i' && HAS_FI_AND_FL_LIGATURES && ! (my font == kGraphics_font_COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode)) {
 					kar = UNICODE_LATIN_SMALL_LIGATURE_FI;
 					in ++;
-				} else if (in [0] == 'l' && HAS_FI_AND_FL_LIGATURES) {
+				} else if (in [0] == 'l' && HAS_FI_AND_FL_LIGATURES && ! (my font == kGraphics_font_COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode)) {
 					kar = UNICODE_LATIN_SMALL_LIGATURE_FL;
 					in ++;
 				}
@@ -1864,7 +1899,8 @@ double Graphics_textWidth_ps (Graphics me, const wchar_t *txt, bool useSilipaPS)
 	return Graphics_dxMMtoWC (me, Graphics_textWidth_ps_mm (me, txt, useSilipaPS));
 }
 
-#if mac && useCarbon
+#if mac
+#if useCarbon
 static ATSFontRef findFont (CFStringRef name) {
 	ATSFontRef fontRef = ATSFontFindFromPostScriptName (name, kATSOptionFlagsDefault);
 	if (fontRef == 0 || fontRef == kATSUInvalidFontID) {
@@ -1875,7 +1911,9 @@ static ATSFontRef findFont (CFStringRef name) {
 	}
 	return fontRef;		
 }
-bool _GraphicsMac_tryToInitializeAtsuiFonts (void) {
+#endif
+bool _GraphicsMac_tryToInitializeFonts (void) {
+#if useCarbon
 	if (theTimesAtsuiFont != 0) return true;   // once
 	theTimesAtsuiFont = findFont (CFSTR ("Times"));
 	if (! theTimesAtsuiFont) theTimesAtsuiFont = findFont (CFSTR ("Times New Roman"));
@@ -1912,25 +1950,46 @@ bool _GraphicsMac_tryToInitializeAtsuiFonts (void) {
 	Melder_assert (theTimesAtsuiFont != 0);
 	ATSUFindFontFromName (NULL, 0, 0, 0, 0, kFontArabicLanguage, & theArabicAtsuiFont);
 	return true;
+#else
+    static bool inited = false;
+    if (inited) return true;
+    NSArray *fontNames = [[NSFontManager sharedFontManager] availableFontFamilies];
+    hasTimes = [fontNames containsObject: @"Times"];
+    if (! hasTimes) hasTimes = [fontNames containsObject: @"Times New Roman"];
+    hasHelvetica = [fontNames containsObject: @"Helvetica"];
+    if (! hasHelvetica) hasHelvetica = [fontNames containsObject: @"Arial"];
+    hasCourier = [fontNames containsObject: @"Courier"];
+    if (! hasCourier) hasCourier = [fontNames containsObject: @"Courier New"];
+    hasSymbol = [fontNames containsObject: @"Symbol"];
+    hasPalatino = [fontNames containsObject: @"Palatino"];
+    if (! hasPalatino) hasPalatino = [fontNames containsObject: @"Book Antiqua"];
+    hasDoulos = [fontNames containsObject: @"Doulos SIL"];
+    hasCharis = [fontNames containsObject: @"Charis SIL"];
+	hasIpaSerif = hasDoulos || hasCharis;
+    inited = true;
+    return true;
+#endif
 }
 #endif
 
 void _GraphicsScreen_text_init (GraphicsScreen me) {   /* BUG: should be done as late as possible. */
 	#if gtk
 	#elif cocoa
+        (void) me;
+        Melder_assert (_GraphicsMac_tryToInitializeFonts ());   // should have been handled when setting my useQuartz to true
 	#elif win
 		int font, size, style;
 		if (my printer || my metafile)
 			for (font = kGraphics_font_MIN; font <= kGraphics_font_DINGBATS; font ++)
 				for (size = 0; size <= 4; size ++)
 					for (style = 0; style <= Graphics_BOLD_ITALIC; style ++)
-						if (printerFonts [font] [size] [style]) {
-							DeleteObject (printerFonts [font] [size] [style]);
-							printerFonts [font] [size] [style] = 0;
+						if (fonts [my resolutionNumber] [font] [size] [style]) {
+							//DeleteObject (fonts [my resolutionNumber] [font] [size] [style]);
+							//fonts [my resolutionNumber] [font] [size] [style] = 0;
 						}
 	#elif mac
 		if (theTimesAtsuiFont == 0) {
-			Melder_assert (_GraphicsMac_tryToInitializeAtsuiFonts ());   // should have been handled when setting my useQuartz to true
+			Melder_assert (_GraphicsMac_tryToInitializeFonts ());   // should have been handled when setting my useQuartz to true
 		}
 	#endif
 }
diff --git a/sys/Gui.cpp b/sys/Gui.cpp
index fe86366..8a84dd9 100644
--- a/sys/Gui.cpp
+++ b/sys/Gui.cpp
@@ -133,7 +133,7 @@ void Gui_getWindowPositioningBounds (double *x, double *y, double *width, double
 		if (y) *y = 0;
 		if (width) *width = gdk_screen_get_width (screen);
 		if (height) *height = gdk_screen_get_height (screen);
-	#else
+	#elif ! defined (NO_GRAPHICS)
 		if (x) *x = 0;
 		if (y) *y = 0;
 		if (width) *width = WidthOfScreen (DefaultScreenOfDisplay (XtDisplay (parent)));
diff --git a/sys/Gui.h b/sys/Gui.h
index f04696f..e568110 100644
--- a/sys/Gui.h
+++ b/sys/Gui.h
@@ -2,7 +2,7 @@
 #define _Gui_h_
 /* Gui.h
  *
- * Copyright (C) 1993-2011,2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1993-2011,2012,2013,2014 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
@@ -22,7 +22,11 @@
 /*
  * Determine the widget set.
  */
-#if defined (UNIX)
+#if defined (NO_GRAPHICS)
+	#define gtk 0
+	#define motif 0
+	#define cocoa 0
+#elif defined (UNIX)
 	#define gtk 1
 	#define motif 0
 	#define cocoa 0
@@ -105,6 +109,7 @@
 		- (void) setUserData: (GuiThing) userData;
 	@end
 	typedef NSObject <GuiCocoaAny> *GuiObject;
+	@interface GuiCocoaApplication : NSApplication @end
 	@interface GuiCocoaButton : NSButton <GuiCocoaAny> @end
 	@interface GuiCocoaCheckButton : NSButton <GuiCocoaAny> @end
 	@interface GuiCocoaDrawingArea : NSView <GuiCocoaAny> @end
@@ -119,7 +124,11 @@
 	@interface GuiCocoaOptionMenu : NSPopUpButton <GuiCocoaAny> @end
 	@interface GuiCocoaProgressBar : NSProgressIndicator <GuiCocoaAny> @end
 	@interface GuiCocoaRadioButton : NSButton <GuiCocoaAny> @end
-	@interface GuiCocoaScrollBar : NSScroller <GuiCocoaAny> @end
+	@interface GuiCocoaScale : NSProgressIndicator <GuiCocoaAny> @end
+	@interface GuiCocoaScrollBar : NSScroller <GuiCocoaAny>
+		- (void) scrollBy: (double) step;
+		- (void) magnifyBy: (double) step;
+	@end
 	@interface GuiCocoaScrolledWindow : NSScrollView <GuiCocoaAny> @end
 	@interface GuiCocoaTextField : NSTextField <GuiCocoaAny> @end
 	@interface GuiCocoaTextView : NSTextView <GuiCocoaAny, NSTextViewDelegate> @end
@@ -298,6 +307,8 @@
 		int motif_win_mouseStillDown (void);
 		void motif_win_setUserMessageCallback (int (*userMessageCallback) (void));
 	#endif
+#else
+	typedef void *GuiObject;
 #endif
 
 int Gui_getResolution (GuiObject widget);
@@ -771,6 +782,9 @@ void GuiRadioGroup_end ();
 Thing_declare (GuiScale);
 
 Thing_define (GuiScale, GuiControl) { public:
+	#if cocoa
+		GuiCocoaScale *d_cocoaScale;
+	#endif
 	/*
 	 * Messages:
 	 */
@@ -798,6 +812,7 @@ Thing_define (GuiScrollBar, GuiControl) { public:
 	 * Messages:
 	 */
 	int f_getValue ();
+	int f_getSliderSize ();
 	void f_set (double minimum, double maximum, double value, double sliderSize, double increment, double pageIncrement);
 };
 
@@ -899,6 +914,12 @@ Thing_define (GuiWindow, GuiShell) { public:
 		GtkMenuBar *d_gtkMenuBar;
 	#elif cocoa
 		int d_menuBarWidth;
+		void (*d_tabCallback) (void *boss, GuiMenuItemEvent event);
+		void *d_tabBoss;
+		void (*d_shiftTabCallback) (void *boss, GuiMenuItemEvent event);
+		void *d_shiftTabBoss;
+		void (*d_optionBackspaceCallback) (void *boss, GuiMenuItemEvent event);
+		void *d_optionBackspaceBoss;
 	#elif motif
 		GuiObject d_xmMenuBar;
 	#endif
@@ -923,7 +944,7 @@ Thing_define (GuiWindow, GuiShell) { public:
 
 /* GuiWindow creation flags: */
 #define GuiWindow_FULLSCREEN  1
-GuiWindow GuiWindow_create (int x, int y, int width, int height,
+GuiWindow GuiWindow_create (int x, int y, int width, int height, int minimumWidth, int minimumHeight,
 	const wchar_t *title, void (*goAwayCallback) (void *goAwayBoss), void *goAwayBoss, unsigned long flags);
 	// returns a Form widget that has a new Shell parent.
 
diff --git a/sys/GuiButton.cpp b/sys/GuiButton.cpp
index 75b3d9b..1e1b9c4 100644
--- a/sys/GuiButton.cpp
+++ b/sys/GuiButton.cpp
@@ -212,6 +212,9 @@ GuiButton GuiButton_create (GuiForm parent, int left, int right, int top, int bo
 		if (flags & GuiButton_DEFAULT) {
 			[button setKeyEquivalent: @"\r"];
 		}
+		if (flags & GuiButton_CANCEL) {
+			[button setKeyEquivalent: [NSString stringWithFormat: @"%c", 27]];   // Escape key
+		}
 		if (flags & GuiButton_ATTRACTIVE) {
 			//[button setKeyEquivalent: @"\r"];   // slow!
 			[button highlight: YES];   // lasts only till it's clicked!
diff --git a/sys/GuiCheckButton.cpp b/sys/GuiCheckButton.cpp
index 8f21ba9..c62c28e 100644
--- a/sys/GuiCheckButton.cpp
+++ b/sys/GuiCheckButton.cpp
@@ -1,6 +1,6 @@
 /* GuiCheckButton.cpp
  *
- * Copyright (C) 1993-2012,2013 Paul Boersma, 2007-2008 Stefan de Konink, 2010 Franz Brausse, 2013 Tom Naughton
+ * Copyright (C) 1993-2012,2013,2014 Paul Boersma, 2007-2008 Stefan de Konink, 2010 Franz Brausse, 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
@@ -196,9 +196,7 @@ bool structGuiCheckButton :: f_getValue () {
 }
 
 void structGuiCheckButton :: f_setValue (bool value) {
-	/*
-	 * The value should be set without calling the valueChanged callback.
-	 */
+	GuiControlBlockValueChangedCallbacks block (this);
 	#if gtk
 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (d_widget), value);
 	#elif cocoa
diff --git a/sys/GuiDialog.cpp b/sys/GuiDialog.cpp
index 1115703..d89c632 100644
--- a/sys/GuiDialog.cpp
+++ b/sys/GuiDialog.cpp
@@ -53,7 +53,7 @@ Thing_implement (GuiDialog, GuiShell, 0);
 	- (void) dealloc {   // override
 		GuiDialog me = d_userData;
 		forget (me);
-		Melder_casual ("deleting a dialog");
+		trace ("deleting a dialog");
 		[super dealloc];
 	}
 	- (GuiDialog) userData {
@@ -62,6 +62,18 @@ Thing_implement (GuiDialog, GuiShell, 0);
 	- (void) setUserData: (GuiDialog) userData {
 		d_userData = userData;
 	}
+	- (BOOL) windowShouldClose: (id) sender {
+		GuiCocoaDialog *widget = (GuiCocoaDialog *) sender;
+		GuiDialog me = [widget userData];
+		if (my d_goAwayCallback != NULL) {
+			trace ("calling goAwayCallback)");
+			my d_goAwayCallback (my d_goAwayBoss);
+		} else {
+			trace ("hiding window");
+			[widget orderOut: nil];
+		}
+		return FALSE;
+	}
 	@end
 #elif motif
 	static void _GuiMotifDialog_destroyCallback (GuiObject widget, XtPointer void_me, XtPointer call) {
@@ -109,7 +121,7 @@ GuiDialog GuiDialog_create (GuiWindow parent, int x, int y, int width, int heigh
 		g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_GuiGtkDialog_destroyCallback), me);
 	#elif cocoa
 		(void) parent;
-		NSRect rect = { { x, y }, { width, height } };
+		NSRect rect = { { (CGFloat) x, (CGFloat) y }, { (CGFloat) width, (CGFloat) height } };
 		NSWindow *nsWindow = [[GuiCocoaDialog alloc]
 			initWithContentRect: rect
 			styleMask: NSTitledWindowMask | NSClosableWindowMask
diff --git a/sys/GuiDrawingArea.cpp b/sys/GuiDrawingArea.cpp
index d5f44cb..1813a0c 100644
--- a/sys/GuiDrawingArea.cpp
+++ b/sys/GuiDrawingArea.cpp
@@ -49,7 +49,7 @@ Thing_implement (GuiDrawingArea, GuiControl, 0);
 		// TODO: that helps against the damaged regions outside the rect where the
 		// Graphics drawing is done, but where does that margin come from in the
 		// first place?? Additionally this causes even more flickering
-		//gdk_window_clear_area(widget->window, expose->area.x, expose->area.y, expose->area.width, expose->area.height);
+		//gdk_window_clear_area ((GTK_WIDGET (widget)) -> window, expose->area.x, expose->area.y, expose->area.width, expose->area.height);
 		if (my d_exposeCallback) {
 			struct structGuiDrawingAreaExposeEvent event = { me, 0 };
 			event. x = expose -> area. x;
@@ -65,6 +65,8 @@ Thing_implement (GuiDrawingArea, GuiControl, 0);
 				trace ("the expose callback finished");
 				trace ("locale is %s", setlocale (LC_ALL, NULL));
 				//gdk_window_end_paint ((GTK_WIDGET (widget)) -> window);
+				//gdk_window_flush ((GTK_WIDGET (widget)) -> window);
+				//gdk_flush ();
 			} catch (MelderError) {
 				Melder_flushError ("Redrawing not completed");
 			}
@@ -102,11 +104,11 @@ Thing_implement (GuiDrawingArea, GuiControl, 0);
 			/*
 			 * Translate with the help of /usr/include/gtk-2.0/gdk/gdkkeysyms.h
 			 */
-			if (event. key == GDK_Escape) event. key = 27;
-			if (event. key == GDK_Left)   event. key = 0x2190;
-			if (event. key == GDK_Up)     event. key = 0x2191;
-			if (event. key == GDK_Right)  event. key = 0x2192;
-			if (event. key == GDK_Down)   event. key = 0x2193;
+			if (event. key == GDK_KEY_Escape) event. key = 27;
+			if (event. key == GDK_KEY_Left)   event. key = 0x2190;
+			if (event. key == GDK_KEY_Up)     event. key = 0x2191;
+			if (event. key == GDK_KEY_Right)  event. key = 0x2192;
+			if (event. key == GDK_KEY_Down)   event. key = 0x2193;
 			event. shiftKeyPressed = (gkeyEvent -> state & GDK_SHIFT_MASK) != 0;
 			event. commandKeyPressed = (gkeyEvent -> state & GDK_CONTROL_MASK) != 0;
 			event. optionKeyPressed = (gkeyEvent -> state & GDK_MOD1_MASK) != 0;
@@ -238,7 +240,8 @@ Thing_implement (GuiDrawingArea, GuiControl, 0);
 			struct structGuiDrawingAreaClickEvent event = { me, 0 };
 			NSPoint local_point = [self   convertPoint: [nsEvent locationInWindow]   fromView: nil];
 			event. x = local_point. x;
-			event. y = [self frame]. size. height - local_point. y;
+			//event. y = [self frame]. size. height - local_point. y;
+			event. y = local_point. y;
 			NSUInteger modifiers = [nsEvent modifierFlags];
 			event. shiftKeyPressed = modifiers & NSShiftKeyMask;
 			event. optionKeyPressed = modifiers & NSAlternateKeyMask;
@@ -250,6 +253,39 @@ Thing_implement (GuiDrawingArea, GuiControl, 0);
 			}
 		}
 	}
+	- (void) scrollWheel: (NSEvent *) nsEvent {
+		GuiDrawingArea me = (GuiDrawingArea) d_userData;
+		if (my d_horizontalScrollBar || my d_verticalScrollBar) {
+			if (my d_horizontalScrollBar) {
+				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_horizontalScrollBar -> d_widget;
+				[cocoaScrollBar scrollBy: [nsEvent scrollingDeltaX]];
+			}
+			if (my d_verticalScrollBar) {
+				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_verticalScrollBar -> d_widget;
+				[cocoaScrollBar scrollBy: [nsEvent scrollingDeltaY]];
+			}
+		} else {
+			[super scrollWheel: nsEvent];
+		}
+	}
+	- (void) magnifyWithEvent: (NSEvent *) nsEvent {
+		GuiDrawingArea me = (GuiDrawingArea) d_userData;
+		if (my d_horizontalScrollBar || my d_verticalScrollBar) {
+			if (my d_horizontalScrollBar) {
+				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_horizontalScrollBar -> d_widget;
+				[cocoaScrollBar magnifyBy: [nsEvent magnification]];
+			}
+			if (my d_verticalScrollBar) {
+				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_verticalScrollBar -> d_widget;
+				[cocoaScrollBar magnifyBy: [nsEvent magnification]];
+			}
+		} else {
+			[super magnifyWithEvent: nsEvent];
+		}
+	}
+	- (BOOL) isFlipped {
+		return YES;
+	}
 	- (void) keyDown: (NSEvent *) nsEvent {
 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
 		if (my d_keyCallback) {
@@ -421,7 +457,9 @@ static gboolean _guiGtkDrawingArea_swipeCallback (GuiObject w, GdkEventScroll *e
 	iam (GuiDrawingArea);
 	if (my d_horizontalScrollBar) {
 		double hv = gtk_range_get_value (GTK_RANGE (my d_horizontalScrollBar -> d_widget));
-		double hi = gtk_range_get_adjustment (GTK_RANGE (my d_horizontalScrollBar -> d_widget)) -> step_increment;
+		GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (my d_horizontalScrollBar -> d_widget));
+		gdouble hi;
+		g_object_get (adjustment, "step_increment", & hi, NULL);
 		switch (event -> direction) {
 			case GDK_SCROLL_LEFT:
 				gtk_range_set_value (GTK_RANGE (my d_horizontalScrollBar -> d_widget), hv - hi);
@@ -433,7 +471,9 @@ static gboolean _guiGtkDrawingArea_swipeCallback (GuiObject w, GdkEventScroll *e
 	}
 	if (my d_verticalScrollBar) {
 		double vv = gtk_range_get_value (GTK_RANGE (my d_verticalScrollBar -> d_widget));
-		double vi = gtk_range_get_adjustment (GTK_RANGE (my d_verticalScrollBar -> d_widget)) -> step_increment;
+		GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (my d_verticalScrollBar -> d_widget));
+		gdouble vi;
+		g_object_get (adjustment, "step_increment", & vi, NULL);
 		switch (event -> direction) {
 			case GDK_SCROLL_UP:
 				gtk_range_set_value (GTK_RANGE (my d_verticalScrollBar -> d_widget), vv - vi);
diff --git a/sys/GuiFileSelect.cpp b/sys/GuiFileSelect.cpp
index 3bf2f0b..6b4fda4 100644
--- a/sys/GuiFileSelect.cpp
+++ b/sys/GuiFileSelect.cpp
@@ -177,8 +177,11 @@ wchar_t * GuiFileSelect_getOutfileName (GuiWindow parent, const wchar_t *title,
 		(void) parent;
 		NSSavePanel	*savePanel = [NSSavePanel savePanel];
 		[savePanel setTitle: [NSString stringWithUTF8String: Melder_peekWcsToUtf8 (title)]];
-		[savePanel setNameFieldStringValue: [NSString stringWithUTF8String: Melder_peekWcsToUtf8 (defaultName)]];
-		if ([savePanel runModal] == NSFileHandlingPanelOKButton) {
+		//[savePanel setNameFieldStringValue: [NSString stringWithUTF8String: Melder_peekWcsToUtf8 (defaultName)]];   // from 10.6 on
+		if ([savePanel runModalForDirectory: nil
+			           file: [NSString stringWithUTF8String: Melder_peekWcsToUtf8 (defaultName)]   // deprecated 10.6 but needed 10.5
+			] == NSFileHandlingPanelOKButton)
+		{
 			const char *outfileName_utf8 = [[[savePanel URL] path] UTF8String];
 			structMelderFile file = { 0 };
 			Melder_8bitFileRepresentationToWcs_inline (outfileName_utf8, file. path);
diff --git a/sys/GuiList.cpp b/sys/GuiList.cpp
index addd16b..decffd3 100644
--- a/sys/GuiList.cpp
+++ b/sys/GuiList.cpp
@@ -90,27 +90,27 @@ Thing_implement (GuiList, GuiControl, 0);
 			tc.width = frameRect. size. width;
 			[tc setEditable: NO];
 			[_tableView addTableColumn: tc];
-			
+
 			_tableView. delegate = self;
 			_tableView. dataSource = self;
 			_tableView. allowsEmptySelection = YES;
 			_tableView. headerView = nil;
 			_tableView. target = self;
 			_tableView. action = @selector (_GuiCocoaList_clicked:);
-			
+
 			NSScrollView *sv = [[NSScrollView alloc] initWithFrame: frameRect];
 			[sv setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
 			[sv setBorderType: NSGrooveBorder];
 			[sv setDocumentView: _tableView];   // this retains the table view
 			[sv setHasVerticalScroller: YES];
 			//[sv setHasHorizontalScroller: YES];
-			
+
 			[self addSubview: sv];   // this retains the scroll view
 			//Melder_assert ([sv retainCount] == 2);   // not always true on 10.6
 			[sv release];
 			Melder_assert ([_tableView retainCount] == 2);
 			[_tableView release];
-			
+
 			_contents = [[NSMutableArray alloc] init];
 		}
 		return self;
@@ -128,14 +128,19 @@ Thing_implement (GuiList, GuiControl, 0);
 	}
 
 	/*
-	 * Implement GuiCocaList methods.
+	 * Implement GuiCocoaList methods.
 	 */
 	- (IBAction) _GuiCocoaList_clicked: (id) sender {
+		/*
+		 * This method probably shouldn't do anything,
+		 * because tableViewSelectionDidChange will already have been called at this point.
+		 */
 		(void) sender;
+		trace ("enter");
 		GuiList me = d_userData;
 		if (me && my d_selectionChangedCallback) {
 			struct structGuiListEvent event = { me };
-			my d_selectionChangedCallback (my d_selectionChangedBoss, & event);
+			//my d_selectionChangedCallback (my d_selectionChangedBoss, & event);
 		}
 	}
 
@@ -156,6 +161,9 @@ Thing_implement (GuiList, GuiControl, 0);
 	 * Override TableViewDelegate methods.
 	 */
 	- (void) tableViewSelectionDidChange: (NSNotification *) notification {
+		/*
+		 * This is invoked when the user clicks in the table or uses the arrow keys.
+		 */
 		(void) notification;
 		trace ("enter");
 		GuiList me = d_userData;
@@ -854,6 +862,7 @@ void structGuiList :: f_setTopPosition (long topPosition) {
 		gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (d_widget), path, NULL, FALSE, 0.0, 0.0);
 		gtk_tree_path_free (path);
 	#elif cocoa
+	 // TODO: implement
 	#elif win
 		ListBox_SetTopIndex (d_widget -> window, topPosition - 1);
 	#elif mac
diff --git a/sys/GuiMenu.cpp b/sys/GuiMenu.cpp
index 1a94c6a..095719b 100644
--- a/sys/GuiMenu.cpp
+++ b/sys/GuiMenu.cpp
@@ -53,9 +53,96 @@ void structGuiMenu :: v_destroy () {
 	static NSMenu *theMenuBar;
 	static int theNumberOfMenuBarItems = 0;
 	static NSMenuItem *theMenuBarItems [30];
-	@interface GuiCocoaApplicationDelegate : NSObject { }
-	@end
-	@implementation GuiCocoaApplicationDelegate
+	@implementation GuiCocoaApplication
+	/*
+	 * Override sendEvent() to capture navigation keys (Tab and Shift-Tab) as menu shortcuts
+	 * and to capture text-editing keys (Option-Backspace) as menu shortcuts.
+	 */
+	- (void) sendEvent: (NSEvent *) nsEvent {
+		if ([nsEvent type] == NSKeyDown) {
+			NSString *characters = [nsEvent characters];
+			if ([characters length] == 0) {   // only modifiers?
+				[super sendEvent: nsEvent];   // the default action
+				return;
+			}
+			unichar character = [characters characterAtIndex: 0];   // there is now at least one character, so no range exception can be raised
+			if (Melder_getTracing ()) {
+				for (NSUInteger i = 0; i < [characters length]; i ++) {
+					unichar kar = [characters characterAtIndex: 0];
+					trace ("character [%d]: %d", (int) i, (int) kar);
+				}
+				trace ("modifiers: %d", (int) [nsEvent modifierFlags]);
+			}
+			if (character == NSTabCharacter) {
+				NSWindow *cocoaKeyWindow = [NSApp keyWindow];
+				if ([cocoaKeyWindow class] == [GuiCocoaWindow class]) {
+					GuiWindow window = (GuiWindow) [(GuiCocoaWindow *) cocoaKeyWindow userData];
+					if (window -> d_tabCallback) {
+						try {
+							struct structGuiMenuItemEvent event = { NULL, 0 };
+							window -> d_tabCallback (window -> d_tabBoss, & event);
+						} catch (MelderError) {
+							Melder_flushError ("Tab key not completely handled.");
+						}
+						return;
+					}
+				}
+			} else if (character == NSBackTabCharacter) {
+				/*
+				 * One can get here by pressing Shift-Tab.
+				 *
+				 * But that is not the only way to get here:
+				 * NSBackTabCharacter equals 25, which may be the reason why
+				 * one can get here as well by pressing Ctrl-Y (Y is the 25th letter in the alphabet).
+				 */
+				NSWindow *cocoaKeyWindow = [NSApp keyWindow];
+				if ([cocoaKeyWindow class] == [GuiCocoaWindow class]) {
+					GuiWindow window = (GuiWindow) [(GuiCocoaWindow *) cocoaKeyWindow userData];
+					if ([nsEvent modifierFlags] & NSShiftKeyMask) {
+						/*
+						 * Make sure we got here by Shift-Tab rather than Ctrl-Y.
+						 */
+						if (window -> d_shiftTabCallback) {
+							try {
+								struct structGuiMenuItemEvent event = { NULL, 0 };
+								window -> d_shiftTabCallback (window -> d_shiftTabBoss, & event);
+							} catch (MelderError) {
+								Melder_flushError ("Tab key not completely handled.");
+							}
+							return;
+						}
+					} else {
+						/*
+						 * We probably got in this branch by pressing Ctrl-Y.
+						 * People sometimes press that because it means "yank" (= Paste) in Emacs,
+						 * and indeed sending this key combination on, as we do here,
+						 * implements (together with Ctrl-K = "kil" = Cut)
+						 * a special cut & paste operation in text fields.
+						 */
+						// do nothing, i.e. send on
+					}
+				}
+			} else if (character == NSDeleteCharacter) {
+				NSWindow *cocoaKeyWindow = [NSApp keyWindow];
+				if ([cocoaKeyWindow class] == [GuiCocoaWindow class]) {
+					GuiWindow window = (GuiWindow) [(GuiCocoaWindow *) cocoaKeyWindow userData];
+					if (([nsEvent modifierFlags] & NSAlternateKeyMask) && window -> d_optionBackspaceCallback) {
+						try {
+							struct structGuiMenuItemEvent event = { NULL, 0 };
+							window -> d_optionBackspaceCallback (window -> d_optionBackspaceBoss, & event);
+						} catch (MelderError) {
+							Melder_flushError ("Option-Backspace not completely handled.");
+						}
+						return;
+					}
+				}
+			}
+		}
+		[super sendEvent: nsEvent];   // the default action: send on
+	}
+	/*
+	 * The delegate methods.
+	 */
 	- (void) applicationWillFinishLaunching: (NSNotification *) note
 	{
 		(void) note;
@@ -66,7 +153,8 @@ void structGuiMenu :: v_destroy () {
 	}
 	- (void) application: (NSApplication *) sender openFiles: (NSArray *) fileNames
 	{
-		for (int i = 1; i <= [fileNames count]; i ++) {
+		(void) sender;
+		for (NSUInteger i = 1; i <= [fileNames count]; i ++) {
 			NSString *cocoaFileName = [fileNames objectAtIndex: i - 1];
 			structMelderFile file = { 0 };
 			Melder_8bitFileRepresentationToWcs_inline ([cocoaFileName UTF8String], file. path);
@@ -75,7 +163,6 @@ void structGuiMenu :: v_destroy () {
 		}
 	}
 	@end
-	static id theGuiCocoaApplicationDelegate;
 #elif motif
 	static void _guiMotifMenu_destroyCallback (GuiObject widget, XtPointer void_me, XtPointer call) {
 		(void) void_me;
@@ -94,6 +181,7 @@ void structGuiMenu :: v_hide () {
 	#if gtk
 		gtk_widget_hide (GTK_WIDGET (d_gtkMenuTitle));
 	#elif cocoa
+		[d_cocoaMenuButton setHidden: YES];
 	#elif motif
 		XtUnmanageChild (d_xmMenuTitle);
 	#endif
@@ -103,6 +191,7 @@ void structGuiMenu :: v_setSensitive (bool sensitive) {
 	#if gtk
 		gtk_widget_set_sensitive (GTK_WIDGET (d_gtkMenuTitle), sensitive);
 	#elif cocoa
+		[d_cocoaMenuButton setEnabled: sensitive];
 	#elif motif
 		XtSetSensitive (d_xmMenuTitle, sensitive);
 	#endif
@@ -113,6 +202,7 @@ void structGuiMenu :: v_show () {
 	#if gtk
 		gtk_widget_show (GTK_WIDGET (d_gtkMenuTitle));
 	#elif cocoa
+		[d_cocoaMenuButton setHidden: NO];
 	#elif motif
 		XtManageChild (d_xmMenuTitle);
 	#endif
@@ -201,11 +291,10 @@ GuiMenu GuiMenu_createInWindow (GuiWindow window, const wchar_t *title, long fla
 		gtk_menu_item_set_submenu (GTK_MENU_ITEM (my d_gtkMenuTitle), GTK_WIDGET (my d_widget));
 		_GuiObject_setUserData (my d_widget, me);
 	#elif cocoa
-		if (! theGuiCocoaApplicationDelegate) {
+		if (! theMenuBar) {
 			int numberOfMenus = [[[NSApp mainMenu] itemArray] count];
 			trace ("Number of menus: %d.", numberOfMenus);
-			theGuiCocoaApplicationDelegate = [[GuiCocoaApplicationDelegate alloc] init];
-			[NSApp   setDelegate: theGuiCocoaApplicationDelegate];
+			[NSApp   setDelegate: NSApp];   // the app is its own delegate
 			theMenuBar = [[NSMenu alloc] init];
 			[NSApp   setMainMenu: theMenuBar];
 		}
@@ -244,7 +333,7 @@ GuiMenu GuiMenu_createInWindow (GuiWindow window, const wchar_t *title, long fla
 			} else {
 				window -> d_menuBarWidth += width - 1;
 			}
-			NSRect rect = { { x, y }, { width, height } };
+			NSRect rect = { { (CGFloat) x, (CGFloat) y }, { (CGFloat) width, (CGFloat) height } };
 			my d_cocoaMenuButton = [[GuiCocoaMenuButton alloc]
 				initWithFrame: rect   pullsDown: YES];
 			[my d_cocoaMenuButton   setAutoenablesItems: NO];
@@ -252,6 +341,8 @@ GuiMenu GuiMenu_createInWindow (GuiWindow window, const wchar_t *title, long fla
 			[my d_cocoaMenuButton   setImagePosition: NSImageAbove];   // this centers the text
 			//[nsPopupButton setBordered: NO];
             [my d_cocoaMenuButton   setAutoresizingMask: resizingMask]; // stick to top
+			if (flags & GuiMenu_INSENSITIVE)
+				[my d_cocoaMenuButton setEnabled: NO];
 
 			[[my d_cocoaMenuButton cell]   setArrowPosition: NSPopUpNoArrow /*NSPopUpArrowAtBottom*/];
 			[[my d_cocoaMenuButton cell]   setPreferredEdge: NSMaxYEdge];
@@ -346,6 +437,7 @@ GuiMenu GuiMenu_createInMenu (GuiMenu supermenu, const wchar_t *title, long flag
 		Melder_assert ([my d_cocoaMenu retainCount] == 2);
 		[my d_cocoaMenu release];   // ... so we can release the menu already, even before returning it
 		my d_widget = my d_cocoaMenu;
+		my d_menuItem -> d_widget = (GuiObject) item;
 	#elif motif
 		my d_menuItem -> d_widget = XmCreateCascadeButton (supermenu -> d_widget, Melder_peekWcsToUtf8 (title), NULL, 0);
 		my d_widget = XmCreatePulldownMenu (supermenu -> d_widget, Melder_peekWcsToUtf8 (title), NULL, 0);
@@ -430,7 +522,7 @@ GuiMenu GuiMenu_createInForm (GuiForm form, int left, int right, int top, int bo
 		[my d_cocoaMenuButton   setImagePosition: NSImageAbove];   // this centers the text
 		[[my d_cocoaMenuButton cell]   setArrowPosition: NSPopUpNoArrow /*NSPopUpArrowAtBottom*/];
 
-        NSString *menuTitle = (NSString*)Melder_peekWcsToCfstring (title);
+        NSString *menuTitle = (NSString*) Melder_peekWcsToCfstring (title);
         my d_widget = my d_cocoaMenu = [[GuiCocoaMenu alloc] initWithTitle:menuTitle];
 		[my d_cocoaMenu   setAutoenablesItems: NO];
 		/*
diff --git a/sys/GuiMenuItem.cpp b/sys/GuiMenuItem.cpp
index 53cb4ca..e1ed69a 100644
--- a/sys/GuiMenuItem.cpp
+++ b/sys/GuiMenuItem.cpp
@@ -1,6 +1,6 @@
 /* GuiMenuItem.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-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
@@ -265,7 +265,7 @@ GuiMenuItem GuiMenu_addItem (GuiMenu menu, const wchar_t *title, long flags,
 			GtkAccelGroup *ag = gtk_menu_get_accel_group (GTK_MENU (menu -> d_widget));
 
 			if (key != 0)
-				gtk_widget_add_accelerator (GTK_WIDGET (my d_widget), toggle ? "toggled" : "activate",
+				gtk_widget_add_accelerator (GTK_WIDGET (my d_widget), toggle ? "YouShouldNotGetHere" : "activate",
 					ag, key, modifiers, GTK_ACCEL_VISIBLE);
 
 		#elif cocoa
@@ -283,6 +283,24 @@ GuiMenuItem GuiMenu_addItem (GuiMenu menu, const wchar_t *title, long flags,
 					NSF7FunctionKey, NSF8FunctionKey, NSF9FunctionKey, NSF10FunctionKey, NSF11FunctionKey, NSF12FunctionKey,
 					0, 0, 0 };
 				[menuItem   setKeyEquivalent: [NSString   stringWithCharacters: & acceleratorKeys [accelerator]   length: 1]];
+				if (accelerator == GuiMenu_TAB) {
+					GuiWindow window = (GuiWindow) my d_shell;
+					Melder_assert (window -> classInfo == classGuiWindow);   // fairly safe, because dialogs have no menus
+					if (flags & GuiMenu_SHIFT) {
+						window -> d_shiftTabCallback = commandCallback;
+						window -> d_shiftTabBoss = boss;
+					} else {
+						window -> d_tabCallback = commandCallback;
+						window -> d_tabBoss = boss;
+					}
+				} else if (accelerator == GuiMenu_BACKSPACE) {
+					GuiWindow window = (GuiWindow) my d_shell;
+					Melder_assert (window -> classInfo == classGuiWindow);   // fairly safe, because dialogs have no menus
+					if (flags & GuiMenu_OPTION) {
+						window -> d_optionBackspaceCallback = commandCallback;
+						window -> d_optionBackspaceBoss = boss;
+					}
+				}
 			} else {
 				[menuItem setKeyEquivalent: [NSString stringWithFormat: @"%c", accelerator]];
 			}
diff --git a/sys/GuiOptionMenu.cpp b/sys/GuiOptionMenu.cpp
index d8a819e..375d28f 100644
--- a/sys/GuiOptionMenu.cpp
+++ b/sys/GuiOptionMenu.cpp
@@ -1,6 +1,6 @@
 /* GuiOptionMenu.cpp
  *
- * Copyright (C) 1993-2012,2013 Paul Boersma, 2007 Stefan de Konink, 2013 Tom Naughton
+ * Copyright (C) 1993-2012,2013,2014 Paul Boersma, 2007 Stefan de Konink, 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
@@ -74,15 +74,15 @@ void structGuiOptionMenu :: v_show () {
 	#elif motif
 		XtManageChild (d_xmMenuBar);
     #elif cocoa
-    NSLog(@"cocoa v_show"); // ?
+		//NSLog(@"cocoa structGuiOptionMenu :: v_show"); // ?
 	#endif
 }
 
 void structGuiOptionMenu :: f_init (GuiForm parent, int left, int right, int top, int bottom, unsigned long flags)
 {
-	d_shell = parent -> d_shell;
-	d_parent = parent;
-	d_options = Ordered_create ();
+	our d_shell = parent -> d_shell;
+	our d_parent = parent;
+	our d_options = Ordered_create ();
 	#if gtk
 		d_widget = gtk_combo_box_new_text ();
 		gtk_widget_set_size_request (GTK_WIDGET (d_widget), right - left, bottom - top + 8);
@@ -93,8 +93,8 @@ void structGuiOptionMenu :: f_init (GuiForm parent, int left, int right, int top
     
         GuiCocoaOptionMenu *optionMenu = [[GuiCocoaOptionMenu alloc] init];
 
-        d_widget = (GuiObject) optionMenu;
-		v_positionInForm (d_widget, left, right, top, bottom, parent);
+        our d_widget = (GuiObject) optionMenu;
+		v_positionInForm (our d_widget, left, right, top - 1, bottom + 1, parent);
     
         [optionMenu setUserData: this];
 //        [optionMenu setBezelStyle: NSRoundedBezelStyle];
@@ -166,10 +166,8 @@ void structGuiOptionMenu:: f_addOption (const wchar_t *text) {
 		XtAddCallback (menuItem -> d_widget, XmNvalueChangedCallback, cb_optionChanged, (XtPointer) this);
 		Collection_addItem (d_options, menuItem);
     #elif cocoa
-    
-        GuiCocoaOptionMenu *menu = (GuiCocoaOptionMenu*)d_widget;
-        [menu addItemWithTitle:[NSString stringWithUTF8String:Melder_peekWcsToUtf8 (text)]];
-    
+        GuiCocoaOptionMenu *menu = (GuiCocoaOptionMenu* ) d_widget;
+        [menu addItemWithTitle: [NSString stringWithUTF8String: Melder_peekWcsToUtf8 (text)]];
 	#endif
 }
 
@@ -185,8 +183,8 @@ int structGuiOptionMenu :: f_getValue () {
 				d_value = i;
 		}
     #elif cocoa
-    GuiCocoaOptionMenu *menu = (GuiCocoaOptionMenu*)d_widget;
-    d_value = [menu indexOfSelectedItem] + 1;
+		GuiCocoaOptionMenu *menu = (GuiCocoaOptionMenu *) d_widget;
+		d_value = [menu indexOfSelectedItem] + 1;
 	#endif
 	return d_value;
 }
diff --git a/sys/GuiRadioButton.cpp b/sys/GuiRadioButton.cpp
index 3dde9f5..22b07bd 100644
--- a/sys/GuiRadioButton.cpp
+++ b/sys/GuiRadioButton.cpp
@@ -71,7 +71,7 @@ static int _GuiRadioButton_getPosition (GuiRadioButton me) {
 		GuiRadioButton d_userData;
 	}
 	- (void) dealloc {   // override
-		GuiRadioButton me = d_userData;
+		GuiRadioButton me = self -> d_userData;
 		forget (me);
 		trace ("deleting a radio button");
 		[super dealloc];
@@ -190,12 +190,47 @@ GuiRadioButton GuiRadioButton_create (GuiForm parent, int left, int right, int t
 		}
 		g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_GuiGtkRadioButton_destroyCallback), me);
 		g_signal_connect (GTK_TOGGLE_BUTTON (my d_widget), "toggled", G_CALLBACK (_GuiGtkRadioButton_handleToggle), me);
+	#elif cocoaXXX
+		my d_cocoaRadioButton = [[GuiCocoaRadioButton alloc] init];
+		my d_widget = my d_cocoaRadioButton;
+		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
+		[my d_cocoaRadioButton   setUserData: me];
+		[my d_cocoaRadioButton setButtonType: NSRadioButton];
+		[my d_cocoaRadioButton setTitle: (NSString *) Melder_peekWcsToCfstring (buttonText)];
+		if (flags & GuiCheckButton_SET) {
+			[my d_cocoaRadioButton setState: NSOnState];
+		}
+		[my d_cocoaRadioButton setTarget: my d_cocoaRadioButton];
+		[my d_cocoaRadioButton setAction: @selector (_guiCocoaRadioButton_activateCallback:)];
 	#elif cocoa
 		my d_cocoaRadioButton = [[GuiCocoaRadioButton alloc] init];
 		my d_widget = my d_cocoaRadioButton;
 		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
 		[my d_cocoaRadioButton   setUserData: me];
 		[my d_cocoaRadioButton setButtonType: NSRadioButton];
+		NSImage *image = [my d_cocoaRadioButton image], *alternateImage = [my d_cocoaRadioButton alternateImage];
+		[my d_cocoaRadioButton setButtonType: NSSwitchButton];
+		[my d_cocoaRadioButton setImage: image];
+		[my d_cocoaRadioButton setAlternateImage: alternateImage];
+		[my d_cocoaRadioButton setTitle: (NSString *) Melder_peekWcsToCfstring (buttonText)];
+		if (flags & GuiCheckButton_SET) {
+			[my d_cocoaRadioButton setState: NSOnState];
+		}
+		[my d_cocoaRadioButton setTarget: my d_cocoaRadioButton];
+		[my d_cocoaRadioButton setAction: @selector (_guiCocoaRadioButton_activateCallback:)];
+	#elif cocoa
+		NSRect matrixRect = NSMakeRect (20.0, 20.0, 125.0, 125.0);
+		my d_cocoaRadioButton = [[GuiCocoaRadioButton alloc] initWithFrame:matrixRect];
+		[my d_cocoaRadioButton   setUserData: me];
+		[my d_cocoaRadioButton setButtonType: NSRadioButton];
+		[my d_cocoaRadioButton setTitle: (NSString *) Melder_peekWcsToCfstring (buttonText)];
+    	NSMatrix *radioMatrix = [[NSMatrix alloc] initWithFrame: matrixRect   mode: NSRadioModeMatrix
+			prototype: (NSCell *) [my d_cocoaRadioButton cell]   numberOfRows: 1   numberOfColumns: 1];
+		my d_widget = (GuiObject) radioMatrix; //my d_cocoaRadioButton;
+		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
+		[radioMatrix   addSubview: my d_cocoaRadioButton];
+		[my d_cocoaRadioButton   setUserData: me];
+		[my d_cocoaRadioButton setButtonType: NSRadioButton];
 		[my d_cocoaRadioButton setTitle: (NSString *) Melder_peekWcsToCfstring (buttonText)];
 		if (flags & GuiCheckButton_SET) {
 			[my d_cocoaRadioButton setState: NSOnState];
diff --git a/sys/GuiScale.cpp b/sys/GuiScale.cpp
index 5018064..827c4b0 100644
--- a/sys/GuiScale.cpp
+++ b/sys/GuiScale.cpp
@@ -45,6 +45,23 @@ Thing_implement (GuiScale, GuiControl, 0);
 		forget (me);
 	}
 #elif cocoa
+	@implementation GuiCocoaScale {
+		GuiScale d_userData;
+	}
+	- (void) dealloc {   // override
+		GuiScale me = d_userData;
+		forget (me);
+		trace ("deleting a progress bar");
+		[super dealloc];
+	}
+	- (GuiThing) userData {
+		return d_userData;
+	}
+	- (void) setUserData: (GuiThing) userData {
+		Melder_assert (userData == NULL || Thing_member (userData, classGuiScale));
+		d_userData = static_cast <GuiScale> (userData);
+	}
+	@end
 #elif win
 	void _GuiWinScale_destroy (GuiObject widget) {
 		iam_scale;
@@ -79,6 +96,14 @@ GuiScale GuiScale_create (GuiForm parent, int left, int right, int top, int bott
 		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
 		g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_GuiGtkScale_destroyCallback), me);
 	#elif cocoa
+		my d_cocoaScale = [[GuiCocoaScale alloc] init];
+		my d_widget = my d_cocoaScale;
+		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
+		[my d_cocoaScale   setUserData: me];
+		[my d_cocoaScale   setIndeterminate: false];
+		[my d_cocoaScale   setMinValue: minimum];
+		[my d_cocoaScale   setMaxValue: maximum];
+		[my d_cocoaScale   setDoubleValue: value];
 	#elif motif
 		my d_widget = XmCreateScale (parent -> d_widget, "scale", NULL, 0);
 		_GuiObject_setUserData (my d_widget, me);
@@ -105,6 +130,7 @@ void structGuiScale :: f_setValue (int value) {
 	#if gtk
 		gtk_range_set_value (GTK_RANGE (d_widget), value);
 	#elif cocoa
+		[d_cocoaScale   setDoubleValue: value];
 	#elif motif
 		XmScaleSetValue (d_widget, value);
 	#endif
diff --git a/sys/GuiScrollBar.cpp b/sys/GuiScrollBar.cpp
index 719e5b1..627361c 100644
--- a/sys/GuiScrollBar.cpp
+++ b/sys/GuiScrollBar.cpp
@@ -96,6 +96,41 @@ Thing_implement (GuiScrollBar, GuiControl, 0);
 		[self setDoubleValue: (value - minimum) / spaceLeft];
 	}
 }
+- (void) _update {
+	GuiScrollBar me = (GuiScrollBar) d_userData;
+	[self setMinimum: _m_minimum maximum: _m_maximum value: _m_value sliderSize: _m_sliderSize increment: _m_increment pageIncrement: _m_pageIncrement];
+    if (my d_valueChangedCallback) {
+        struct structGuiScrollBarEvent event = { me };
+        try {
+            my d_valueChangedCallback (my d_valueChangedBoss, & event);
+        } catch (MelderError) {
+            Melder_flushError ("Scroll not completely handled.");
+        }
+    }
+}
+- (void) scrollBy: (double) step {
+	trace ("step %lf", step);
+	if (step == 0) return;
+	_m_value -= 0.3 * step * _m_increment;
+	if (_m_value < _m_minimum)
+		_m_value = _m_minimum;
+	if (_m_value > _m_maximum - _m_sliderSize)
+		_m_value = _m_maximum - _m_sliderSize;
+	[self _update];
+}
+- (void) magnifyBy: (double) step {
+	trace ("step %lf", step);
+	double increase = _m_sliderSize * (exp (- step) - 1.0);
+	_m_sliderSize += increase;
+	if (_m_sliderSize > _m_maximum - _m_minimum)
+		_m_sliderSize = _m_maximum - _m_minimum;
+	_m_value -= 0.5 * increase;
+	if (_m_value < _m_minimum)
+		_m_value = _m_minimum;
+	if (_m_value > _m_maximum - _m_sliderSize)
+		_m_value = _m_maximum - _m_sliderSize;
+	[self _update];
+}
 - (void) valueChanged {
 	GuiScrollBar me = (GuiScrollBar) d_userData;
 	switch ([self hitPart]) {
@@ -292,4 +327,17 @@ int structGuiScrollBar :: f_getValue () {
 	#endif
 }
 
+int structGuiScrollBar :: f_getSliderSize () {
+	#if gtk
+		return 1;   // NYI
+	#elif cocoa
+		GuiCocoaScrollBar *scroller = (GuiCocoaScrollBar *) d_widget;
+		return [scroller m_sliderSize];
+	#elif motif
+		int value, slider, incr, pincr;
+		XmScrollBarGetValues (d_widget, & value, & slider, & incr, & pincr);
+		return slider;
+	#endif
+}
+
 /* End of file GuiScrollBar.cpp */
diff --git a/sys/GuiScrolledWindow.cpp b/sys/GuiScrolledWindow.cpp
index d82e843..a315c78 100644
--- a/sys/GuiScrolledWindow.cpp
+++ b/sys/GuiScrolledWindow.cpp
@@ -39,23 +39,23 @@ Thing_implement (GuiScrolledWindow, GuiControl, 0);
 		forget (me);
 	}
 #elif cocoa
- at implementation GuiCocoaScrolledWindow {
-    GuiScrolledWindow d_userData;
-}
-- (void) dealloc {   // override
-    GuiScrolledWindow me = d_userData;
-    forget (me);
-    trace ("deleting a scrolled window");
-    [super dealloc];
-}
-- (GuiThing) userData {
-    return d_userData;
-}
-- (void) setUserData: (GuiThing) userData {
-    Melder_assert (userData == NULL || Thing_member (userData, classGuiScrolledWindow));
-    d_userData = static_cast <GuiScrolledWindow> (userData);
-}
- at end
+	@implementation GuiCocoaScrolledWindow {
+		GuiScrolledWindow d_userData;
+	}
+	- (void) dealloc {   // override
+		GuiScrolledWindow me = d_userData;
+		forget (me);
+		trace ("deleting a scrolled window");
+		[super dealloc];
+	}
+	- (GuiThing) userData {
+		return d_userData;
+	}
+	- (void) setUserData: (GuiThing) userData {
+		Melder_assert (userData == NULL || Thing_member (userData, classGuiScrolledWindow));
+		d_userData = static_cast <GuiScrolledWindow> (userData);
+	}
+	@end
 #elif win
 	void _GuiWinScrolledWindow_destroy (GuiObject widget) {
 		DestroyWindow (widget -> window);
@@ -89,15 +89,13 @@ GuiScrolledWindow GuiScrolledWindow_create (GuiForm parent, int left, int right,
 		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
 		g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_GuiGtkScrolledWindow_destroyCallback), me);
 	#elif cocoa
-    
         GuiCocoaScrolledWindow *scrollView = [[GuiCocoaScrolledWindow alloc] init];
         my d_widget = (GuiObject) scrollView;
         my v_positionInForm (my d_widget, left, right, top, bottom, parent);
-        [scrollView setUserData:me];
-        [scrollView setHasVerticalScroller:YES];
-        [scrollView setHasHorizontalScroller:YES];
-        [scrollView setBackgroundColor:[NSColor lightGrayColor]];
-    
+        [scrollView setUserData: me];
+        [scrollView setHasVerticalScroller:   YES];
+        [scrollView setHasHorizontalScroller: YES];
+        [scrollView setBackgroundColor: [NSColor lightGrayColor]];
 	#elif motif
 		my d_widget = XmCreateScrolledWindow (parent -> d_widget, "scrolledWindow", NULL, 0);
 		_GuiObject_setUserData (my d_widget, me);
diff --git a/sys/GuiShell.cpp b/sys/GuiShell.cpp
index be5a59c..dc9ee77 100644
--- a/sys/GuiShell.cpp
+++ b/sys/GuiShell.cpp
@@ -79,7 +79,7 @@ void structGuiShell :: f_drain () {
 	#elif cocoa
         //[d_cocoaWindow displayIfNeeded];
         [d_cocoaWindow flushWindow];
-		[d_cocoaWindow display];
+		//[d_cocoaWindow display];
 	#elif win
 	#elif mac
 		Melder_assert (d_xmShell != NULL);
diff --git a/sys/GuiText.cpp b/sys/GuiText.cpp
index 52ddd47..450395f 100644
--- a/sys/GuiText.cpp
+++ b/sys/GuiText.cpp
@@ -1,6 +1,6 @@
 /* GuiText.cpp
  *
- * Copyright (C) 1993-2011,2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1993-2011,2012,2013,2014 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
@@ -911,6 +911,7 @@ void _GuiText_exit (void) {
 		(void) aTextView;
 		(void) affectedCharRange;
 		(void) replacementString;
+		trace ("changing text to: %s", [replacementString UTF8String]);
 		GuiText me = d_userData;
 		if (me && my d_changeCallback) {
 			struct structGuiTextEvent event = { me };
@@ -1282,6 +1283,7 @@ wchar_t * structGuiText :: f_getStringAndSelectionPosition (long *first, long *l
 			*first = nsRange. location;
 			*last = *first + nsRange. length;
 			for (long i = 0; i < *first; i ++) if (result [i] > 0xFFFF) { (*first) --; (*last) --; }
+			for (long i = *first; i < *last; i ++) if (result [i] > 0xFFFF) { (*last) --; }
 			return result;
 		}
 	#elif win
@@ -1364,6 +1366,9 @@ void structGuiText :: f_remove () {
 			gtk_text_buffer_delete_selection (buffer, TRUE, gtk_text_view_get_editable (GTK_TEXT_VIEW (d_widget)));
 		}
 	#elif cocoa
+		if (d_cocoaTextView) {
+			[d_cocoaTextView delete: nil];
+		}
 	#elif win
 		if (! d_editable || ! NativeText_getSelectionRange (d_widget, NULL, NULL)) return;
 		SendMessage (d_widget -> window, WM_CLEAR, 0, 0);   /* This will send the EN_CHANGE message, hence no need to call the valueChangedCallbacks. */
@@ -1492,6 +1497,8 @@ void structGuiText :: f_scrollToSelection () {
 		gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (d_widget), & start, 0.1, false, 0.0, 0.0); 
 		//gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (d_widget), mark, 0.1, false, 0.0, 0.0);
 	#elif cocoa
+		if (d_cocoaTextView)
+			[d_cocoaTextView scrollRangeToVisible: [d_cocoaTextView selectedRange]];
 	#elif win
 		Edit_ScrollCaret (d_widget -> window);
 	#elif mac
@@ -1512,7 +1519,12 @@ void structGuiText :: f_setFontSize (int size) {
 	#if gtk
 		GtkRcStyle *modStyle = gtk_widget_get_modifier_style (GTK_WIDGET (d_widget));
 		trace ("before initializing Pango: locale is %s", setlocale (LC_ALL, NULL));
-		PangoFontDescription *fontDesc = modStyle -> font_desc != NULL ? modStyle->font_desc : pango_font_description_copy (GTK_WIDGET (d_widget) -> style -> font_desc);
+		PangoFontDescription *fontDesc = modStyle -> font_desc != NULL ? modStyle->font_desc :
+			#if ALLOW_GDK_DRAWING
+				pango_font_description_copy (GTK_WIDGET (d_widget) -> style -> font_desc);
+			#else
+				NULL;
+			#endif
 		trace ("during initializing Pango: locale is %s", setlocale (LC_ALL, NULL));
 		pango_font_description_set_absolute_size (fontDesc, size * PANGO_SCALE);
 		trace ("after initializing Pango: locale is %s", setlocale (LC_ALL, NULL));
@@ -1631,7 +1643,7 @@ void structGuiText :: f_setSelection (long first, long last) {
 		last += numberOfLeadingHighUnicodeValues + numberOfSelectedHighUnicodeValues;
 		Melder_free (text);
 		if (isTextControl (d_widget)) {
-			ControlEditTextSelectionRec rec = { first, last };
+			ControlEditTextSelectionRec rec = { (int16_t) first, (int16_t) last };
 			SetControlData (d_widget -> nat.control.handle, kControlEntireControl, kControlEditTextSelectionTag, sizeof (rec), & rec);
 		} else if (isMLTE (this)) {
 			TXNSetSelection (d_macMlteObject, first, last);
@@ -1662,6 +1674,7 @@ void structGuiText :: f_setString (const wchar_t *text) {
 			[d_cocoaTextView shouldChangeTextInRange: nsRange replacementString: nsString];   // to make this action undoable
 			//[[d_cocoaTextView textStorage] replaceCharactersInRange: nsRange withString: nsString];
 			[d_cocoaTextView setString: nsString];
+			[d_cocoaTextView scrollRangeToVisible: NSMakeRange ([[d_cocoaTextView textStorage] length], 0)];   // to the end
 			//[[d_cocoaTextView window] setViewsNeedDisplay: YES];
 			//[[d_cocoaTextView window] display];
 		} else {
diff --git a/sys/GuiWindow.cpp b/sys/GuiWindow.cpp
index ae74fae..afc1ed5 100644
--- a/sys/GuiWindow.cpp
+++ b/sys/GuiWindow.cpp
@@ -1,6 +1,6 @@
 /* GuiWindow.cpp
  *
- * Copyright (C) 1993-2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1993-2012,2013,2014 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
@@ -54,6 +54,35 @@ Thing_implement (GuiWindow, GuiShell, 0);
 		}
 		return TRUE;
 	}
+	static void _GuiWindow_child_resizeCallback (GtkWidget *childWidget, gpointer data) {
+		GtkAllocation *allocation = (GtkAllocation *) data;
+		GtkWidget *parentWidget = gtk_widget_get_parent (childWidget);
+		Thing_cast (GuiThing, child, _GuiObject_getUserData (childWidget));
+		if (child) {
+			GuiControl control = NULL;
+			if (Thing_member (child, classGuiControl)) {
+				control = static_cast <GuiControl> (child);
+			} else if (Thing_member (child, classGuiMenu)) {
+				Thing_cast (GuiMenu, menu, child);
+				control = menu -> d_cascadeButton;
+			}
+			if (control) {
+				/*
+				 * Move and resize.
+				 */
+				trace ("moving child of class %ls", Thing_className (control));
+				int left = control -> d_left, right = control -> d_right, top = control -> d_top, bottom = control -> d_bottom;
+				if (left   <  0) left   += allocation -> width;   // this replicates structGuiControl :: v_positionInForm ()
+				if (right  <= 0) right  += allocation -> width;
+				if (top    <  0) top    += allocation -> height;
+				if (bottom <= 0) bottom += allocation -> height;
+				trace ("moving child to (%d,%d)", left, top);
+				gtk_fixed_move (GTK_FIXED (parentWidget), GTK_WIDGET (childWidget), left, top);
+				gtk_widget_set_size_request (GTK_WIDGET (childWidget), right - left, bottom - top);
+				trace ("moved child of class %ls", Thing_className (control));
+			}
+		}
+	}
 	static gboolean _GuiWindow_resizeCallback (GuiObject widget, GtkAllocation *allocation, gpointer void_me) {
 		(void) widget;
 		iam (GuiWindow);
@@ -68,37 +97,7 @@ Thing_implement (GuiWindow, GuiShell, 0);
 			/*
 			 * We move and resize all the children of the fixed.
 			 */
-			GList *children = GTK_FIXED (widget) -> children;
-			for (GList *l = g_list_first (children); l != NULL; l = g_list_next (l)) {
-				GtkFixedChild *listElement = (GtkFixedChild *) l -> data;
-				GtkWidget *childWidget = listElement -> widget;
-				Melder_assert (childWidget);
-				Thing_cast (GuiThing, child, _GuiObject_getUserData (childWidget));
-				if (child) {
-					GuiControl control = NULL;
-					if (Thing_member (child, classGuiControl)) {
-						control = static_cast <GuiControl> (child);
-					} else if (Thing_member (child, classGuiMenu)) {
-						Thing_cast (GuiMenu, menu, child);
-						control = menu -> d_cascadeButton;
-					}
-					if (control) {
-						/*
-						 * Move and resize.
-						 */
-						trace ("moving child of class %ls", Thing_className (control));
-						int left = control -> d_left, right = control -> d_right, top = control -> d_top, bottom = control -> d_bottom;
-						if (left   <  0) left   += allocation -> width;   // this replicates structGuiControl :: v_positionInForm ()
-						if (right  <= 0) right  += allocation -> width;
-						if (top    <  0) top    += allocation -> height;
-						if (bottom <= 0) bottom += allocation -> height;
-						trace ("moving child to (%d,%d)", left, top);
-						gtk_fixed_move (GTK_FIXED (widget), GTK_WIDGET (childWidget), left, top);
-						gtk_widget_set_size_request (GTK_WIDGET (childWidget), right - left, bottom - top);
-						trace ("moved child of class %ls", Thing_className (control));
-					}
-				}
-			}
+			gtk_container_foreach (GTK_CONTAINER (widget), _GuiWindow_child_resizeCallback, allocation);
 			my d_width = allocation -> width;
 			my d_height = allocation -> height;
 			gtk_widget_set_size_request (GTK_WIDGET (widget), allocation -> width, allocation -> height);
@@ -124,10 +123,13 @@ Thing_implement (GuiWindow, GuiShell, 0);
 		Melder_assert (userData == NULL || Thing_member (userData, classGuiWindow));
 		d_userData = static_cast <GuiWindow> (userData);
 	}
-	@end
-	@interface GuiCocoaWindowDelegate : NSObject <NSWindowDelegate> { } @end
-	@implementation GuiCocoaWindowDelegate {
+	- (void) keyDown: (NSEvent *) theEvent {
+		trace ("key down");
 	}
+	//@end
+	//@interface GuiCocoaWindowDelegate : NSObject <NSWindowDelegate> { } @end
+	//@implementation GuiCocoaWindowDelegate {
+	//}
 	- (BOOL) windowShouldClose: (id) sender {
 		GuiCocoaWindow *widget = (GuiCocoaWindow *) sender;
 		GuiWindow me = (GuiWindow) [widget userData];
@@ -141,7 +143,7 @@ Thing_implement (GuiWindow, GuiShell, 0);
 		return FALSE;
 	}
 	@end
-	static GuiCocoaWindowDelegate *theGuiCocoaWindowDelegate;
+	//static GuiCocoaWindowDelegate *theGuiCocoaWindowDelegate;
 #elif motif
 	static void _GuiMotifWindow_destroyCallback (GuiObject widget, XtPointer void_me, XtPointer call) {
 		(void) widget; (void) call;
@@ -160,7 +162,7 @@ Thing_implement (GuiWindow, GuiShell, 0);
 	}
 #endif
 
-GuiWindow GuiWindow_create (int x, int y, int width, int height,
+GuiWindow GuiWindow_create (int x, int y, int width, int height, int minimumWidth, int minimumHeight,
 	const wchar_t *title, void (*goAwayCallback) (void *goAwayBoss), void *goAwayBoss, unsigned long flags)
 {
 	GuiWindow me = Thing_new (GuiWindow);
@@ -174,30 +176,33 @@ GuiWindow GuiWindow_create (int x, int y, int width, int height,
 		g_signal_connect (G_OBJECT (my d_gtkWindow), "destroy-event", G_CALLBACK (_GuiWindow_destroyCallback), me);
 
 		gtk_window_set_default_size (GTK_WINDOW (my d_gtkWindow), width, height);
-		gtk_window_set_policy (GTK_WINDOW (my d_gtkWindow), TRUE, TRUE, FALSE);
+		gtk_window_set_resizable (GTK_WINDOW (my d_gtkWindow), TRUE);
 		my f_setTitle (title);
 
 		my d_widget = gtk_fixed_new ();
 		_GuiObject_setUserData (my d_widget, me);
 		gtk_widget_set_size_request (GTK_WIDGET (my d_widget), width, height);
 		gtk_container_add (GTK_CONTAINER (my d_gtkWindow), GTK_WIDGET (my d_widget));
+		GdkGeometry geometry = { minimumWidth, minimumHeight, 0, 0, 0, 0, 0, 0, 0, 0, GDK_GRAVITY_NORTH_WEST };
+		gtk_window_set_geometry_hints (my d_gtkWindow, GTK_WIDGET (my d_gtkWindow), & geometry, GDK_HINT_MIN_SIZE);
 		g_signal_connect (G_OBJECT (my d_widget), "size-allocate", G_CALLBACK (_GuiWindow_resizeCallback), me);
 	#elif cocoa
-		NSRect rect = { { x, y }, { width, height } };
+		NSRect rect = { { static_cast<CGFloat>(x), static_cast<CGFloat>(y) }, { static_cast<CGFloat>(width), static_cast<CGFloat>(height) } };
 		my d_cocoaWindow = [[GuiCocoaWindow alloc]
 			initWithContentRect: rect
 			styleMask: NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask
 			backing: NSBackingStoreBuffered
 			defer: false];
-        [my d_cocoaWindow setMinSize: NSMakeSize (150.0, 150.0)];
+		[my d_cocoaWindow setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
+        [my d_cocoaWindow setMinSize: NSMakeSize (minimumWidth, minimumHeight)];
 		my f_setTitle (title);
 		[my d_cocoaWindow makeKeyAndOrderFront: nil];
 		my d_widget = [my d_cocoaWindow contentView];
 		_GuiObject_setUserData (my d_cocoaWindow, me);
-		if (! theGuiCocoaWindowDelegate) {
-			theGuiCocoaWindowDelegate = [[GuiCocoaWindowDelegate alloc] init];
-		}
-		[my d_cocoaWindow setDelegate: theGuiCocoaWindowDelegate];
+		//if (! theGuiCocoaWindowDelegate) {
+		//	theGuiCocoaWindowDelegate = [[GuiCocoaWindowDelegate alloc] init];
+		//}
+		//[my d_cocoaWindow setDelegate: theGuiCocoaWindowDelegate];
 	#elif motif
 		my d_xmShell = XmCreateShell (NULL, flags & GuiWindow_FULLSCREEN ? "Praatwulgfullscreen" : "Praatwulg", NULL, 0);
 		XtVaSetValues (my d_xmShell, XmNdeleteResponse, goAwayCallback ? XmDO_NOTHING : XmUNMAP, NULL);
diff --git a/sys/HyperPage.cpp b/sys/HyperPage.cpp
index 6c68eba..ba23150 100644
--- a/sys/HyperPage.cpp
+++ b/sys/HyperPage.cpp
@@ -1,6 +1,6 @@
 /* HyperPage.cpp
  *
- * Copyright (C) 1996-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1996-2011,2012,2013,2014 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
@@ -450,6 +450,7 @@ if (! my printing) {
 			theCurrentPraatPicture -> colour = Graphics_BLACK;
 			theCurrentPraatPicture -> lineWidth = 1.0;
 			theCurrentPraatPicture -> arrowSize = 1.0;
+			theCurrentPraatPicture -> speckleSize = 1.0;
 			theCurrentPraatPicture -> x1NDC = my d_x;
 			theCurrentPraatPicture -> x2NDC = my d_x + true_width_inches;
 			theCurrentPraatPicture -> y1NDC = my d_y;
@@ -489,6 +490,7 @@ if (! my printing) {
 			Graphics_setLineType (my g, Graphics_DRAWN);
 			Graphics_setLineWidth (my g, 1.0);
 			Graphics_setArrowSize (my g, 1.0);
+			Graphics_setSpeckleSize (my g, 1.0);
 			Graphics_setColour (my g, Graphics_BLACK);
 			/*Graphics_Link *paragraphLinks;
 			long numberOfParagraphLinks = Graphics_getLinks (& paragraphLinks);
@@ -545,6 +547,7 @@ if (! my printing) {
 		theCurrentPraatPicture -> colour = Graphics_BLACK;
 		theCurrentPraatPicture -> lineWidth = 1.0;
 		theCurrentPraatPicture -> arrowSize = 1.0;
+		theCurrentPraatPicture -> speckleSize = 1.0;
 		theCurrentPraatPicture -> x1NDC = my d_x;
 		theCurrentPraatPicture -> x2NDC = my d_x + true_width_inches;
 		theCurrentPraatPicture -> y1NDC = my d_y;
@@ -556,6 +559,9 @@ if (! my printing) {
 		Graphics_WCtoDC (my ps, 0.0, 0.0, & x1DC, & y2DC);
 		Graphics_WCtoDC (my ps, 1.0, 1.0, & x2DC, & y1DC);
 		long shift = (long) (Graphics_getResolution (my ps) * true_height_inches) + (y1DCold - y2DCold);
+		#if cocoa
+			shift = 0;   // this is a FIX
+		#endif
 		Graphics_resetWsViewport (my ps, x1DC, x2DC, y1DC + shift, y2DC + shift);
 		Graphics_setWsWindow (my ps, 0, width_inches, 0, height_inches);
 		theCurrentPraatPicture -> x1NDC = 0;
@@ -580,6 +586,7 @@ if (! my printing) {
 		Graphics_setLineType (my ps, Graphics_DRAWN);
 		Graphics_setLineWidth (my ps, 1.0);
 		Graphics_setArrowSize (my ps, 1.0);
+		Graphics_setSpeckleSize (my ps, 1.0);
 		Graphics_setColour (my ps, Graphics_BLACK);
 		theCurrentPraatApplication = & theForegroundPraatApplication;
 		theCurrentPraatObjects = & theForegroundPraatObjects;
@@ -611,21 +618,21 @@ static void print (I, Graphics graphics) {
 /********** class HyperPage **********/
 
 void structHyperPage :: v_destroy () {
-	forget (links);
-	Melder_free (entryHint);
-	forget (g);
-	for (int i = 0; i < 20; i ++) Melder_free (history [i]. page);
-	Melder_free (currentPageTitle);
+	forget (our links);
+	Melder_free (our entryHint);
+	forget (our g);
+	for (int i = 0; i < 20; i ++) Melder_free (our history [i]. page);
+	Melder_free (our currentPageTitle);
 	if (praatApplication != NULL) {
-		for (int iobject = ((PraatObjects) praatObjects) -> n; iobject >= 1; iobject --) {
-			Melder_free (((PraatObjects) praatObjects) -> list [iobject]. name);
-			forget (((PraatObjects) praatObjects) -> list [iobject]. object);
+		for (int iobject = ((PraatObjects) our praatObjects) -> n; iobject >= 1; iobject --) {
+			Melder_free (((PraatObjects) our praatObjects) -> list [iobject]. name);
+			forget (((PraatObjects) our praatObjects) -> list [iobject]. object);
 		}
-		Melder_free (praatApplication);
-		Melder_free (praatObjects);
-		Melder_free (praatPicture);
+		Melder_free (our praatApplication);
+		Melder_free (our praatObjects);
+		Melder_free (our praatPicture);
 	}
-	HyperPage_Parent :: v_destroy ();
+	our HyperPage_Parent :: v_destroy ();
 }
 
 static void gui_drawingarea_cb_expose (I, GuiDrawingAreaExposeEvent event) {
@@ -795,9 +802,10 @@ static void gui_cb_verticalScroll (I, GuiScrollBarEvent	event) {
 }
 
 static void createVerticalScrollBar (HyperPage me, GuiForm parent) {
+	int height = Machine_getTextHeight ();
 	my verticalScrollBar = GuiScrollBar_createShown (parent,
 		- Machine_getScrollBarWidth (), 0,
-		Machine_getMenuBarHeight () + Machine_getTextHeight () + 12, - Machine_getScrollBarWidth (),
+		Machine_getMenuBarHeight () + (my d_hasExtraRowOfTools ? 2 * height + 19 : height + 12), - Machine_getScrollBarWidth (),
 		0, PAGE_HEIGHT * 5, 0, 25, 1, 24,
 		gui_cb_verticalScroll, me, 0);
 }
@@ -895,7 +903,7 @@ void structHyperPage :: v_createMenus () {
 	Editor_addCommand (this, L"File", L"Print page...", 'P', menu_cb_print);
 	Editor_addCommand (this, L"File", L"-- close --", 0, NULL);
 
-	if (v_hasHistory ()) {
+	if (our v_hasHistory ()) {
 		Editor_addMenu (this, L"Go to", 0);
 		Editor_addCommand (this, L"Go to", L"Search for page...", 0, menu_cb_searchForPage);
 		Editor_addCommand (this, L"Go to", L"Back", GuiMenu_OPTION | GuiMenu_LEFT_ARROW, menu_cb_back);
@@ -948,28 +956,30 @@ void structHyperPage :: v_createChildren () {
 
 	/***** Create navigation buttons. *****/
 
-	if (v_hasHistory ()) {
-		GuiButton_createShown (d_windowForm, 4, 48, y, y + height,
+	if (our v_hasHistory ()) {
+		GuiButton_createShown (our d_windowForm, 4, 48, y, y + height,
 			L"<", gui_button_cb_back, this, 0);
-		GuiButton_createShown (d_windowForm, 54, 98, y, y + height,
+		GuiButton_createShown (our d_windowForm, 54, 98, y, y + height,
 			L">", gui_button_cb_forth, this, 0);
 	}
-	if (v_isOrdered ()) {
-		GuiButton_createShown (d_windowForm, 174, 218, y, y + height,
+	if (our v_isOrdered ()) {
+		GuiButton_createShown (our d_windowForm, 174, 218, y, y + height,
 			L"< 1", gui_button_cb_previousPage, this, 0);
-		GuiButton_createShown (d_windowForm, 224, 268, y, y + height,
+		GuiButton_createShown (our d_windowForm, 224, 268, y, y + height,
 			L"1 >", gui_button_cb_nextPage, this, 0);
 	}
 
 	/***** Create scroll bar. *****/
 
-	createVerticalScrollBar (this, d_windowForm);
+	createVerticalScrollBar (this, our d_windowForm);
 
 	/***** Create drawing area. *****/
 
-	drawingArea = GuiDrawingArea_createShown (d_windowForm, 0, - Machine_getScrollBarWidth (), y + height + 9, - Machine_getScrollBarWidth (),
+	drawingArea = GuiDrawingArea_createShown (our d_windowForm,
+		0, - Machine_getScrollBarWidth (),
+		y + ( our d_hasExtraRowOfTools ? 2 * height + 16 : height + 9 ), - Machine_getScrollBarWidth (),
 		gui_drawingarea_cb_expose, gui_drawingarea_cb_click, NULL, gui_drawingarea_cb_resize, this, GuiDrawingArea_BORDER);
-	drawingArea -> f_setSwipable (NULL, verticalScrollBar);
+	drawingArea -> f_setSwipable (NULL, our verticalScrollBar);
 }
 
 void HyperPage_init (HyperPage me, const wchar_t *title, Data data) {
@@ -1001,7 +1011,7 @@ void HyperPage_clear (HyperPage me) {
 
 void structHyperPage :: v_dataChanged () {
 	int oldError = Melder_hasError ();   // this method can be called during error time
-	(void) v_goToPage (currentPageTitle);
+	(void) our v_goToPage (our currentPageTitle);
 	if (Melder_hasError () && ! oldError) Melder_flushError (NULL);
 	HyperPage_clear (this);
 	updateVerticalScrollBar (this);
diff --git a/sys/HyperPage.h b/sys/HyperPage.h
index 94d58dd..9ae1ce1 100644
--- a/sys/HyperPage.h
+++ b/sys/HyperPage.h
@@ -2,7 +2,7 @@
 #define _HyperPage_h_
 /* HyperPage.h
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2014 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
@@ -36,6 +36,7 @@ Thing_define (HyperPage, Editor) {
 	public:
 		GuiDrawingArea drawingArea;
 		GuiScrollBar verticalScrollBar;
+		bool d_hasExtraRowOfTools;
 		Graphics g, ps;
 		double d_x, d_y, rightMargin, previousBottomSpacing;
 		long d_printingPageNumber;
diff --git a/sys/Interpreter.cpp b/sys/Interpreter.cpp
index 8d35672..e1a2d48 100644
--- a/sys/Interpreter.cpp
+++ b/sys/Interpreter.cpp
@@ -1,6 +1,6 @@
 /* Interpreter.cpp
  *
- * Copyright (C) 1993-2011,2013 Paul Boersma
+ * Copyright (C) 1993-2011,2013,2014 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
@@ -183,7 +183,7 @@ void Melder_includeIncludeFiles (wchar_t **text) {
 			wcscpy (newText + headLength + includeTextLength, L"\n");
 			wcscpy (newText + headLength + includeTextLength + 1, tail);
 			/*
-				Replace the old text with the new.
+				Replace the old text with the new. This will work even within an autostring.
 			 */
 			Melder_free (*text);
 			*text = newText;
@@ -306,9 +306,12 @@ long Interpreter_readParameters (Interpreter me, wchar_t *text) {
 }
 
 UiForm Interpreter_createForm (Interpreter me, GuiWindow parent, const wchar_t *path,
-	void (*okCallback) (UiForm, int, Stackel, const wchar_t *, Interpreter, const wchar_t *, bool, void *), void *okClosure)
+	void (*okCallback) (UiForm, int, Stackel, const wchar_t *, Interpreter, const wchar_t *, bool, void *), void *okClosure,
+	bool selectionOnly)
 {
-	UiForm form = UiForm_create (parent, my dialogTitle [0] ? my dialogTitle : L"Script arguments", okCallback, okClosure, NULL, NULL);
+	UiForm form = UiForm_create (parent,
+		Melder_wcscat (selectionOnly ? L"Run script (selection only): " : L"Run script: ", my dialogTitle),
+		okCallback, okClosure, NULL, NULL);
 	Any radio = NULL;
 	if (path) UiForm_addText (form, L"$file", path);
 	for (int ipar = 1; ipar <= my numberOfParameters; ipar ++) {
@@ -538,49 +541,125 @@ void Interpreter_getArgumentsFromString (Interpreter me, const wchar_t *argument
 	}
 }
 
-static int Interpreter_addNumericVariable (Interpreter me, const wchar_t *key, double value) {
-	InterpreterVariable variable = InterpreterVariable_create (key);
+void Interpreter_getArgumentsFromArgs (Interpreter me, int narg, Stackel args) {
+	trace ("%d arguments", narg);
+	int size = my numberOfParameters;
+	while (size >= 1 && my parameters [size] [0] == '\0')
+		size --;   // ignore trailing fields without a variable name (button, comment)
+	for (int ipar = 1; ipar <= size; ipar ++) {
+		wchar_t *p = my parameters [ipar];
+		/*
+		 * Ignore buttons and comments again.
+		 */
+		if (! *p) continue;
+		/*
+		 * Strip parentheses and colon off parameter name.
+		 */
+		if ((p = wcschr (p, '(')) != NULL) {
+			*p = '\0';
+			if (p - my parameters [ipar] > 0 && p [-1] == '_') p [-1] = '\0';
+		}
+		p = my parameters [ipar];
+		if (*p != '\0' && p [wcslen (p) - 1] == ':') p [wcslen (p) - 1] = '\0';
+	}
+	int iarg = 0;
+	for (int ipar = 1; ipar <= size; ipar ++) {
+		/*
+		 * Ignore buttons and comments again. The buttons will keep their labels as "arguments".
+		 */
+		if (my parameters [ipar] [0] == '\0') continue;
+		Melder_free (my arguments [ipar]);   // erase the current values, probably the default values
+		if (iarg == narg)
+			Melder_throw ("Found ", narg, " arguments but expected more.");
+		Stackel arg = & args [++ iarg];
+		my arguments [ipar] =
+			arg -> which == Stackel_NUMBER ? Melder_wcsdup (Melder_double (arg -> number)) :
+			arg -> which == Stackel_STRING ? Melder_wcsdup (arg -> string) : NULL;   // replace with the actual arguments
+		Melder_assert (my arguments [ipar] != NULL);
+	}
+	if (iarg < narg)
+		Melder_throw ("Found ", narg, " arguments but expected only ", iarg, ".");
+	/*
+	 * Convert booleans and choices to numbers.
+	 */
+	for (int ipar = 1; ipar <= size; ipar ++) {
+		if (my types [ipar] == Interpreter_BOOLEAN) {
+			wchar_t *arg = & my arguments [ipar] [0];
+			if (wcsequ (arg, L"1") || wcsequ (arg, L"yes") || wcsequ (arg, L"on") ||
+			    wcsequ (arg, L"Yes") || wcsequ (arg, L"On") || wcsequ (arg, L"YES") || wcsequ (arg, L"ON"))
+			{
+				wcscpy (arg, L"1");
+			} else if (wcsequ (arg, L"0") || wcsequ (arg, L"no") || wcsequ (arg, L"off") ||
+			    wcsequ (arg, L"No") || wcsequ (arg, L"Off") || wcsequ (arg, L"NO") || wcsequ (arg, L"OFF"))
+			{
+				wcscpy (arg, L"0");
+			} else {
+				Melder_throw ("Unknown value \"", arg, "\" for boolean \"", my parameters [ipar], "\".");
+			}
+		} else if (my types [ipar] == Interpreter_CHOICE) {
+			int jpar;
+			wchar_t *arg = & my arguments [ipar] [0];
+			for (jpar = ipar + 1; jpar <= my numberOfParameters; jpar ++) {
+				if (my types [jpar] != Interpreter_BUTTON && my types [jpar] != Interpreter_OPTION)
+					Melder_throw ("Unknown value \"", arg, "\" for choice \"", my parameters [ipar], "\".");
+				if (wcsequ (my arguments [jpar], arg)) {   // the button labels are in the arguments; see Interpreter_readParameters
+					swprintf (arg, 40, L"%d", jpar - ipar);
+					wcscpy (my choiceArguments [ipar], my arguments [jpar]);
+					break;
+				}
+			}
+			if (jpar > my numberOfParameters)
+				Melder_throw ("Unknown value \"", arg, "\" for choice \"", my parameters [ipar], "\".");
+		} else if (my types [ipar] == Interpreter_OPTIONMENU) {
+			int jpar;
+			wchar_t *arg = & my arguments [ipar] [0];
+			for (jpar = ipar + 1; jpar <= my numberOfParameters; jpar ++) {
+				if (my types [jpar] != Interpreter_OPTION && my types [jpar] != Interpreter_BUTTON)
+					Melder_throw ("Unknown value \"", arg, "\" for option menu \"", my parameters [ipar], "\".");
+				if (wcsequ (my arguments [jpar], arg)) {
+					swprintf (arg, 40, L"%d", jpar - ipar);
+					wcscpy (my choiceArguments [ipar], my arguments [jpar]);
+					break;
+				}
+			}
+			if (jpar > my numberOfParameters)
+				Melder_throw ("Unknown value \"", arg, "\" for option menu \"", my parameters [ipar], "\".");
+		}
+	}
+}
+
+static void Interpreter_addNumericVariable (Interpreter me, const wchar_t *key, double value) {
+	autoInterpreterVariable variable = InterpreterVariable_create (key);
 	variable -> numericValue = value;
-	Collection_addItem (my variables, variable);
-	return 1;
+	Collection_addItem (my variables, variable.transfer());
 }
 
-static InterpreterVariable Interpreter_addStringVariable (Interpreter me, const wchar_t *key, const wchar_t *value) {
-	InterpreterVariable variable = InterpreterVariable_create (key);
+static void Interpreter_addStringVariable (Interpreter me, const wchar_t *key, const wchar_t *value) {
+	autoInterpreterVariable variable = InterpreterVariable_create (key);
 	variable -> stringValue = Melder_wcsdup (value);
-	Collection_addItem (my variables, variable);
-	return variable;
+	Collection_addItem (my variables, variable.transfer());
 }
 
 InterpreterVariable Interpreter_hasVariable (Interpreter me, const wchar_t *key) {
-	long ivar = 0;
-	wchar_t variableNameIncludingProcedureName [1+200];
 	Melder_assert (key != NULL);
-	if (key [0] == '.') {
-		wcscpy (variableNameIncludingProcedureName, my procedureNames [my callDepth]);
-		wcscat (variableNameIncludingProcedureName, key);
-	} else {
-		wcscpy (variableNameIncludingProcedureName, key);
-	}
-	ivar = SortedSetOfString_lookUp (my variables, variableNameIncludingProcedureName);
-	return ivar ? (InterpreterVariable) my variables -> item [ivar] : NULL;
+	long variableNumber = SortedSetOfString_lookUp (my variables,
+		key [0] == '.' ? Melder_wcscat (my procedureNames [my callDepth], key) : key);
+	return variableNumber ? (InterpreterVariable) my variables -> item [variableNumber] : NULL;
 }
 
 InterpreterVariable Interpreter_lookUpVariable (Interpreter me, const wchar_t *key) {
-	InterpreterVariable var = NULL;
-	wchar_t variableNameIncludingProcedureName [1+200];
 	Melder_assert (key != NULL);
-	if (key [0] == '.') {
-		wcscpy (variableNameIncludingProcedureName, my procedureNames [my callDepth]);
-		wcscat (variableNameIncludingProcedureName, key);
-	} else {
-		wcscpy (variableNameIncludingProcedureName, key);
-	}
-	var = Interpreter_hasVariable (me, variableNameIncludingProcedureName);
-	if (var) return var;
-	var = InterpreterVariable_create (variableNameIncludingProcedureName);
-	Collection_addItem (my variables, var);
-	return Interpreter_hasVariable (me, variableNameIncludingProcedureName);
+	const wchar_t *variableNameIncludingProcedureName =
+		key [0] == '.' ? Melder_wcscat (my procedureNames [my callDepth], key) : key;
+	long variableNumber = SortedSetOfString_lookUp (my variables, variableNameIncludingProcedureName);
+	if (variableNumber) return (InterpreterVariable) my variables -> item [variableNumber];   // already exists
+	/*
+	 * The variable doesn't yet exist: create a new one.
+	 */
+	autoInterpreterVariable variable = InterpreterVariable_create (variableNameIncludingProcedureName);
+	InterpreterVariable variable_ref = variable.peek();
+	Collection_addItem (my variables, variable.transfer());
+	return variable_ref;
 }
 
 static long lookupLabel (Interpreter me, const wchar_t *labelName) {
@@ -692,6 +771,7 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 		/*
 		 * Connect continuation lines.
 		 */
+		trace ("connect continuation lines");
 		for (lineNumber = numberOfLines; lineNumber >= 2; lineNumber --) {
 			wchar_t *line = lines [lineNumber];
 			if (line [0] == '.' && line [1] == '.' && line [2] == '.') {
@@ -763,6 +843,7 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 		 * Execute commands.
 		 */
 		#define wordEnd(c)  (c == '\0' || c == ' ' || c == '\t')
+		trace ("going to handle %ld lines", numberOfLines);
 		for (lineNumber = 1; lineNumber <= numberOfLines; lineNumber ++) {
 			if (my stopped) break;
 			try {
@@ -774,15 +855,17 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 				/*
 				 * Substitute variables.
 				 */
-				for (p = & command2. string [0]; *p !='\0'; p ++) if (*p == '\'') {
+				trace ("substituting variables");
+				for (p = & command2. string [0]; *p != '\0'; p ++) if (*p == '\'') {
 					/*
 					 * Found a left quote. Search for a matching right quote.
 					 */
 					wchar_t *q = p + 1, varName [300], *r, *s, *colon;
 					int precision = -1, percent = FALSE;
 					while (*q != '\0' && *q != '\'' && q - p < 299) q ++;
-					if (*q == '\0') break;   /* No matching right quote: done with this line. */
-					if (q - p == 1 || q - p >= 299) continue;   /* Ignore empty variable names. */
+					if (*q == '\0') break;   // no matching right quote? done with this line!
+					if (q - p == 1 || q - p >= 299) continue;   // ignore empty and too long variable names
+					trace ("found %ld", (long) (q - p - 1));
 					/*
 					 * Found a right quote. Get potential variable name.
 					 */
@@ -813,6 +896,7 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 						p = q - 1;   /* Go to before next quote. */
 					}
 				}
+				trace ("resume");
 				c0 = command2.string [0];   /* Resume in order to allow things like 'c$' = 5 */
 				if ((c0 < 'a' || c0 > 'z') && c0 != '@' && ! (c0 == '.' && command2.string [1] >= 'a' && command2.string [1] <= 'z')) {
 					praat_executeCommand (me, command2.string);
@@ -832,17 +916,22 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 						wchar_t *p = command2.string + 1;
 						while (*p == ' ' || *p == '\t') p ++;   // skip whitespace
 						wchar_t *callName = p;
-						while (*p != '\0' && *p != ' ' && *p != '\t' && *p != '(') p ++;
+						while (*p != '\0' && *p != ' ' && *p != '\t' && *p != '(' && *p != ':') p ++;
 						if (p == callName) Melder_throw ("Missing procedure name after \"@\".");
-						if (*p == '\0') Melder_throw ("Missing parenthesis after procedure name.");
-						bool parenthesisFound = *p == '(';
-						*p = '\0';   // close procedure name
-						if (! parenthesisFound) {
-							p ++;   // step over first white space
-							while (*p != '\0' && (*p == ' ' || *p == '\t') && *p != '(') p ++;   // skip more whitespace
-							if (*p != '(') Melder_throw ("Missing parenthesis after procedure name \"", callName, "\".");
+						bool hasArguments = ( *p != '\0' );
+						if (hasArguments) {
+							bool parenthesisOrColonFound = ( *p == '(' || *p == ':' );
+							*p = '\0';   // close procedure name
+							if (! parenthesisOrColonFound) {
+								p ++;   // step over first white space
+								while (*p != '\0' && (*p == ' ' || *p == '\t')) p ++;   // skip more whitespace
+								hasArguments = ( *p != '\0' );
+								parenthesisOrColonFound = ( *p == '(' || *p == ':' );
+								if (hasArguments && ! parenthesisOrColonFound)
+									Melder_throw ("Missing parenthesis or colon after procedure name \"", callName, "\".");
+							}
+							p ++;   // step over parenthesis or colon
 						}
-						p ++;   // step over parenthesis
 						int callLength = wcslen (callName);
 						long iline = 1;
 						for (; iline <= numberOfLines; iline ++) {
@@ -853,7 +942,7 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 							q = lines [iline] + 10;
 							while (*q == ' ' || *q == '\t') q ++;   // skip whitespace before procedure name
 							wchar_t *procName = q;
-							while (*q != '\0' && *q != ' ' && *q != '\t' && *q != '(') q ++;
+							while (*q != '\0' && *q != ' ' && *q != '\t' && *q != '(' && *q != ':') q ++;
 							if (q == procName) Melder_throw ("Missing procedure name after 'procedure'.");
 							if (q - procName == callLength && wcsnequ (procName, callName, callLength)) {
 								/*
@@ -862,13 +951,12 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 								if (++ my callDepth > Interpreter_MAX_CALL_DEPTH)
 									Melder_throw ("Call depth greater than ", Interpreter_MAX_CALL_DEPTH, ".");
 								wcscpy (my procedureNames [my callDepth], callName);
-								parenthesisFound = *q == '(';
-								if (! parenthesisFound) {
-									q ++;   // step over first white space
-									while (*q != '\0' && (*q == ' ' || *q == '\t') && *q != '(') q ++;   // skip more whitespace
-									if (*q != '(') Melder_throw ("Missing parenthesis after procedure name \"", callName, "\".");
+								bool parenthesisOrColonFound = ( *q == '(' || *q == ':' );
+								if (*q) q ++;   // step over parenthesis or colon or first white space
+								if (! parenthesisOrColonFound) {
+									while (*q == ' ' || *q == '\t') q ++;   // skip more whitespace
+									if (*q == '(' || *q == ':') q ++;   // step over parenthesis or colon
 								}
-								++ q;   // step over parenthesis
 								while (*q && *q != ')') {
 									static MelderString argument = { 0 };
 									MelderString_empty (& argument);
@@ -967,9 +1055,9 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 							wchar_t *p = command2.string + 5, *callName, *procName;
 							long iline;
 							int hasArguments, callLength;
-							while (*p == ' ' || *p == '\t') p ++;
+							while (*p == ' ' || *p == '\t') p ++;   // skip whitespace
 							callName = p;
-							while (*p != '\0' && *p != ' ' && *p != '\t') p ++;
+							while (*p != '\0' && *p != ' ' && *p != '\t' && *p != '(' && *p != ':') p ++;
 							if (p == callName) Melder_throw ("Missing procedure name after 'call'.");
 							hasArguments = *p != '\0';
 							*p = '\0';   /* Close procedure name. */
@@ -983,7 +1071,7 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 								q = lines [iline] + 10;
 								while (*q == ' ' || *q == '\t') q ++;
 								procName = q;
-								while (*q != '\0' && *q != ' ' && *q != '\t') q ++;
+								while (*q != '\0' && *q != ' ' && *q != '\t' && *q != '(' && *q != ':') q ++;
 								if (q == procName) Melder_throw ("Missing procedure name after 'procedure'.");
 								hasParameters = *q != '\0';
 								if (q - procName == callLength && wcsnequ (procName, callName, callLength)) {
@@ -995,16 +1083,21 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 										Melder_throw ("Call depth greater than ", Interpreter_MAX_CALL_DEPTH, ".");
 									wcscpy (my procedureNames [my callDepth], callName);
 									if (hasParameters) {
+										bool parenthesisOrColonFound = ( *q == '(' || *q == ':' );
+										q ++;   // step over parenthesis or colon or first white space
+										if (! parenthesisOrColonFound) {
+											while (*q == ' ' || *q == '\t') q ++;   // skip more whitespace
+											if (*q == '(' || *q == ':') q ++;   // step over parenthesis or colon
+										}
 										++ p;   /* First argument. */
-										++ q;   /* First parameter. */
-										while (*q) {
+										while (*q && *q != ')') {
 											wchar_t *par, save;
 											static MelderString arg = { 0 };
 											MelderString_empty (& arg);
 											while (*p == ' ' || *p == '\t') p ++;
-											while (*q == ' ' || *q == '\t') q ++;
+											while (*q == ' ' || *q == '\t' || *q == ',' || *q == ')') q ++;
 											par = q;
-											while (*q != '\0' && *q != ' ' && *q != '\t') q ++;   /* Collect parameter name. */
+											while (*q != '\0' && *q != ' ' && *q != '\t' && *q != ',' && *q != ')') q ++;   /* Collect parameter name. */
 											if (*q) {   /* Does anything follow the parameter name? */
 												if (*p == '\"') {
 													p ++;   /* Skip initial quote. */
@@ -1146,9 +1239,9 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 						} else if (wcsnequ (command2.string, L"exit", 4)) {
 							if (command2.string [4] == '\0') {
 								lineNumber = numberOfLines;   /* Go after end. */
-							} else {
+							} else if (command2.string [4] == ' ') {
 								Melder_throw (command2.string + 5);
-							}
+							} else fail = TRUE;
 						} else if (wcsnequ (command2.string, L"echo ", 5)) {
 							/*
 							 * Make sure that lines like "echo = 3" will not be regarded as assignments.
@@ -1361,6 +1454,7 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 					 * Found an unknown word starting with a lower-case letter, optionally preceded by a period.
 					 * See whether the word is a variable name.
 					 */
+					trace ("found an unknown word starting with a lower-case letter, optionally preceded by a period");
 					wchar_t *p = & command2.string [0];
 					/*
 					 * Variable names consist of a sequence of letters, digits, and underscores,
@@ -1372,6 +1466,7 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 						/*
 						 * Assign to a string variable.
 						 */
+						trace ("detected an assignment to a string variable");
 						wchar_t *endOfVariable = ++ p;
 						wchar_t *variableName = command2.string;
 						int withFile;
@@ -1453,10 +1548,10 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 							 */
 							MelderString_empty (& valueString);   // empty because command may print nothing; also makes sure that valueString.string exists
 							autoMelderDivertInfo divert (& valueString);
-							praat_executeCommand (me, p);
+							int status = praat_executeCommand (me, p);
 							InterpreterVariable var = Interpreter_lookUpVariable (me, variableName);
 							Melder_free (var -> stringValue);
-							var -> stringValue = Melder_wcsdup (valueString.string);
+							var -> stringValue = Melder_wcsdup (status ? valueString.string : L"");
 						} else {
 							/*
 							 * Evaluate a string expression and assign the result to the variable.
@@ -1467,7 +1562,9 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 							 *       ... else "" fi
 							 */
 							wchar_t *stringValue;
+							trace ("evaluating string expression");
 							Interpreter_stringExpression (me, p, & stringValue);
+							trace ("assigning to string variable %ls", variableName);
 							InterpreterVariable var = Interpreter_lookUpVariable (me, variableName);
 							Melder_free (var -> stringValue);
 							var -> stringValue = stringValue;   /* var becomes owner */
@@ -1571,8 +1668,10 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 							MelderString_empty (& valueString);
 							autoMelderDivertInfo divert (& valueString);
 							MelderString_appendCharacter (& valueString, 1);
-							praat_executeCommand (me, p);
-							if (valueString.string [0] == 1) {
+							int status = praat_executeCommand (me, p);
+							if (status == 0) {
+								value = NUMundefined;
+							} else if (valueString.string [0] == 1) {
 								int IOBJECT, result = 0, found = 0;
 								WHERE (SELECTED) { result = IOBJECT; found += 1; }
 								if (found > 1) {
@@ -1656,7 +1755,8 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 		my running = false;
 		my stopped = false;
 	} catch (MelderError) {
-		if (! wcsnequ (lines [lineNumber], L"exit ", 5) && ! assertionFailed) {   // don't show the message twice!
+		bool normalExplicitExit = wcsnequ (lines [lineNumber], L"exit ", 5) || Melder_hasError (L"Script exited.");
+		if (! normalExplicitExit && ! assertionFailed) {   // don't show the message twice!
 			while (lines [lineNumber] [0] == '\0') {   // did this use to be a continuation line?
 				lineNumber --;
 				Melder_assert (lineNumber > 0);   // originally empty lines that stayed empty should not generate errors
@@ -1666,7 +1766,11 @@ void Interpreter_run (Interpreter me, wchar_t *text) {
 		my numberOfLabels = 0;
 		my running = false;
 		my stopped = false;
-		throw;
+		if (wcsequ (Melder_getError (), L"\nScript exited.\n")) {
+			Melder_clearError ();
+		} else {
+			throw;
+		}
 	}
 }
 
diff --git a/sys/Interpreter.h b/sys/Interpreter.h
index 373d1f7..900d218 100644
--- a/sys/Interpreter.h
+++ b/sys/Interpreter.h
@@ -2,7 +2,7 @@
 #define _Interpreter_h_
 /* Interpreter.h
  *
- * Copyright (C) 1993-2011,2013 Paul Boersma
+ * Copyright (C) 1993-2011,2013,2014 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
@@ -68,9 +68,11 @@ void Melder_includeIncludeFiles (wchar_t **text);
 long Interpreter_readParameters (Interpreter me, wchar_t *text);
 Thing_declare (UiForm);
 UiForm Interpreter_createForm (Interpreter me, GuiWindow parent, const wchar_t *fileName,
-	void (*okCallback) (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *closure), void *okClosure);
+	void (*okCallback) (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *closure), void *okClosure,
+	bool selectionOnly);
 void Interpreter_getArgumentsFromDialog (Interpreter me, Any dialog);
 void Interpreter_getArgumentsFromString (Interpreter me, const wchar_t *arguments);
+void Interpreter_getArgumentsFromArgs (Interpreter me, int nargs, Stackel args);
 void Interpreter_run (Interpreter me, wchar_t *text);   // destroys 'text'
 void Interpreter_stop (Interpreter me);   // can be called from any procedure called deep-down by the interpreter; will stop before next line
 void Interpreter_voidExpression (Interpreter me, const wchar_t *expression);
diff --git a/sys/ManPages.cpp b/sys/ManPages.cpp
index c19ca6e..c3f1903 100644
--- a/sys/ManPages.cpp
+++ b/sys/ManPages.cpp
@@ -1,6 +1,6 @@
 /* ManPages.cpp
  *
- * Copyright (C) 1996-2012 Paul Boersma
+ * Copyright (C) 1996-2012,2014 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
@@ -163,7 +163,7 @@ static void readOnePage (ManPages me, MelderReadText text) {
 		} catch (MelderError) {
 			Melder_throw ("Cannot find text.");
 		}
-		for (const wchar_t *p = extractLink (par -> text, NULL, link); p != NULL; p = extractLink (par -> text, p, link)) {
+		for (const wchar_t *plink = extractLink (par -> text, NULL, link); plink != NULL; plink = extractLink (par -> text, plink, link)) {
 			/*
 			 * Now, `link' contains the link text, with spaces and all.
 			 * Transform it into a file name.
@@ -550,6 +550,7 @@ static void writeParagraphsAsHtml (ManPages me, MelderFile file, ManPage_Paragra
 			theCurrentPraatPicture -> colour = Graphics_BLACK;
 			theCurrentPraatPicture -> lineWidth = 1.0;
 			theCurrentPraatPicture -> arrowSize = 1.0;
+			theCurrentPraatPicture -> speckleSize = 1.0;
 			theCurrentPraatPicture -> x1NDC = 0;
 			theCurrentPraatPicture -> x2NDC = paragraph -> width;
 			theCurrentPraatPicture -> y1NDC = 0;
diff --git a/sys/Manual.cpp b/sys/Manual.cpp
index 973910c..1d821a8 100644
--- a/sys/Manual.cpp
+++ b/sys/Manual.cpp
@@ -1,6 +1,6 @@
 /* Manual.cpp
  *
- * Copyright (C) 1996-2011 Paul Boersma
+ * Copyright (C) 1996-2011,2014 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
@@ -97,16 +97,16 @@ static void menu_cb_searchForPageList (EDITOR_ARGS) {
 }
 
 void structManual :: v_draw () {
-	ManPages manPages = (ManPages) data;
+	ManPages manPages = (ManPages) our data;
 	ManPage page;
 	ManPage_Paragraph paragraph;
 	#if motif
 	Graphics_clearWs (g);
 	#endif
-	if (path == SEARCH_PAGE) {
+	if (our path == SEARCH_PAGE) {
 		HyperPage_pageTitle (this, L"Best matches");
 		HyperPage_intro (this, L"The best matches to your query seem to be:");
-		for (int i = 1; i <= numberOfMatches; i ++) {
+		for (int i = 1; i <= our numberOfMatches; i ++) {
 			wchar_t link [300];
 			page = (ManPage) manPages -> pages -> item [matches [i]];
 			swprintf (link, 300, L"\\bu @@%ls", page -> title);
@@ -115,7 +115,7 @@ void structManual :: v_draw () {
 		return;
 	}
 	page = (ManPage) manPages -> pages -> item [path];
-	if (! paragraphs) return;
+	if (! our paragraphs) return;
 	HyperPage_pageTitle (this, page -> title);
 	for (paragraph = & page -> paragraphs [0]; paragraph -> type != 0; paragraph ++) {
 		switch (paragraph -> type) {
@@ -149,25 +149,25 @@ void structManual :: v_draw () {
 			default: break;
 		}
 	}
-	if (ManPages_uniqueLinksHither (manPages, path)) {
+	if (ManPages_uniqueLinksHither (manPages, our path)) {
 		long ilink, jlink, lastParagraph = 0;
-		int goAhead = TRUE;
+		bool goAhead = true;
 		while (page -> paragraphs [lastParagraph]. type != 0) lastParagraph ++;
 		if (lastParagraph > 0) {
 			const wchar_t *text = page -> paragraphs [lastParagraph - 1]. text;
 			if (text == NULL || text [0] == '\0' || text [wcslen (text) - 1] != ':') {
-				if (printing && suppressLinksHither)
-					goAhead = FALSE;
+				if (our printing && our suppressLinksHither)
+					goAhead = false;
 				else
 					HyperPage_entry (this, L"Links to this page");
 			}
 		}
 		if (goAhead) for (ilink = 1; ilink <= page -> nlinksHither; ilink ++) {
 			long link = page -> linksHither [ilink];
-			int alreadyShown = FALSE;
+			bool alreadyShown = false;
 			for (jlink = 1; jlink <= page -> nlinksThither; jlink ++)
 				if (page -> linksThither [jlink] == link)
-					alreadyShown = TRUE;
+					alreadyShown = true;
 			if (! alreadyShown) {
 				const wchar_t *title = ((ManPage) manPages -> pages -> item [page -> linksHither [ilink]]) -> title;
 				wchar_t linkText [304];
@@ -176,7 +176,7 @@ void structManual :: v_draw () {
 			}
 		}
 	}
-	if (! printing && page -> date) {
+	if (! our printing && page -> date) {
 		wchar_t signature [100];
 		long date = page -> date;
 		int imonth = date % 10000 / 100;
@@ -185,9 +185,9 @@ void structManual :: v_draw () {
 			wcsequ (page -> author, L"ppgb") ? L"Paul Boersma" :
 			wcsequ (page -> author, L"djmw") ? L"David Weenink" : page -> author,
 			date % 100, month [imonth], date / 10000);
-		HyperPage_any (this, L"", p_font, p_fontSize, 0, 0.0,
+		HyperPage_any (this, L"", our p_font, our p_fontSize, 0, 0.0,
 			0.0, 0.0, 0.1, 0.1, HyperPage_ADD_BORDER);
-		HyperPage_any (this, signature, p_font, p_fontSize, Graphics_ITALIC, 0.0,
+		HyperPage_any (this, signature, our p_font, our p_fontSize, Graphics_ITALIC, 0.0,
 			0.03, 0.0, 0.1, 0.0, 0);
 	}
 }
@@ -427,31 +427,28 @@ static void gui_cb_search (GUI_ARGS) {
 }
 
 void structManual :: v_createChildren () {
+	ManPages pages = (ManPages) our data;   // has been installed here by Editor_init ()
+	our d_hasExtraRowOfTools = pages -> dynamic;
 	Manual_Parent :: v_createChildren ();
-	ManPages pages = (ManPages) data;   // has been installed here by Editor_init ()
 	#if defined (macintosh)
 		#define STRING_SPACING 8
 	#else
 		#define STRING_SPACING 2
 	#endif
 	int height = Machine_getTextHeight (), y = Machine_getMenuBarHeight () + 4;
-	homeButton = GuiButton_createShown (d_windowForm, 104, 168, y, y + height,
+	our homeButton = GuiButton_createShown (our d_windowForm, 104, 168, y, y + height,
 		L"Home", gui_button_cb_home, this, 0);
 	if (pages -> dynamic) {
-		#if motif
-			XtVaSetValues (drawingArea -> d_widget, XmNtopOffset, y + height * 2 + 16, NULL);
-			XtVaSetValues (verticalScrollBar -> d_widget, XmNtopOffset, y + height * 2 + 16, NULL);
-		#endif
-		recordButton = GuiButton_createShown (d_windowForm, 4, 79, y+height+8, y+height+8 + height,
+		our recordButton = GuiButton_createShown (our d_windowForm, 4, 79, y+height+8, y+height+8 + height,
 			L"Record", gui_button_cb_record, this, 0);
-		playButton = GuiButton_createShown (d_windowForm, 85, 160, y+height+8, y+height+8 + height,
+		our playButton = GuiButton_createShown (our d_windowForm, 85, 160, y+height+8, y+height+8 + height,
 			L"Play", gui_button_cb_play, this, 0);
-		publishButton = GuiButton_createShown (d_windowForm, 166, 166 + 175, y+height+8, y+height+8 + height, 
+		our publishButton = GuiButton_createShown (our d_windowForm, 166, 166 + 175, y+height+8, y+height+8 + height,
 			L"Copy last played to list", gui_button_cb_publish, this, 0);
 	}
-	GuiButton_createShown (d_windowForm, 274, 274 + 69, y, y + height,
+	GuiButton_createShown (our d_windowForm, 274, 274 + 69, y, y + height,
 		L"Search:", gui_button_cb_search, this, GuiButton_DEFAULT);
-	searchText = GuiText_createShown (d_windowForm, 274+69 + STRING_SPACING, 452 + STRING_SPACING - 2, y, y + Gui_TEXTFIELD_HEIGHT, 0);
+	our searchText = GuiText_createShown (our d_windowForm, 274+69 + STRING_SPACING, 452 + STRING_SPACING - 2, y, y + Gui_TEXTFIELD_HEIGHT, 0);
 }
 
 static void menu_cb_help (EDITOR_ARGS) { EDITOR_IAM (Manual); HyperPage_goToPage (me, L"Manual"); }
@@ -491,35 +488,35 @@ void structManual :: v_defaultHeaders (EditorCommand cmd) {
 }
 
 long structManual :: v_getNumberOfPages () {
-	ManPages manPages = (ManPages) data;
+	ManPages manPages = (ManPages) our data;
 	return manPages -> pages -> size;
 }
 
 long structManual :: v_getCurrentPageNumber () {
-	return path ? path : 1;
+	return our path ? our path : 1;
 }
 
 void structManual :: v_goToPage_i (long pageNumber) {
-	ManPages manPages = (ManPages) data;
+	ManPages manPages = (ManPages) our data;
 	if (pageNumber < 1 || pageNumber > manPages -> pages -> size) {
 		if (pageNumber == SEARCH_PAGE) {
-			path = SEARCH_PAGE;
-			Melder_free (currentPageTitle);
+			our path = SEARCH_PAGE;
+			Melder_free (our currentPageTitle);
 			return;
 		} else Melder_throw ("Page ", pageNumber, " not found.");
 	}
-	path = pageNumber;
+	our path = pageNumber;
 	ManPage page = (ManPage) manPages -> pages -> item [path];
-	paragraphs = page -> paragraphs;
-	numberOfParagraphs = 0;
+	our paragraphs = page -> paragraphs;
+	our numberOfParagraphs = 0;
 	ManPage_Paragraph par = paragraphs;
-	while ((par ++) -> type) numberOfParagraphs ++;
-	Melder_free (currentPageTitle);
-	currentPageTitle = Melder_wcsdup_f (page -> title);
+	while ((par ++) -> type) our numberOfParagraphs ++;
+	Melder_free (our currentPageTitle);
+	our currentPageTitle = Melder_wcsdup_f (page -> title);
 }
 
 int structManual :: v_goToPage (const wchar_t *title) {
-	ManPages manPages = (ManPages) data;
+	ManPages manPages = (ManPages) our data;
 	if (title [0] == '\\' && title [1] == 'F' && title [2] == 'I') {
 		structMelderFile file = { 0 };
 		MelderDir_relativePathToFile (& manPages -> rootDirectory, title + 3, & file);
@@ -538,7 +535,7 @@ int structManual :: v_goToPage (const wchar_t *title) {
 		long i = ManPages_lookUp (manPages, title);
 		if (! i)
 			Melder_throw ("Page \"", title, "\" not found.");
-		v_goToPage_i (i);
+		our v_goToPage_i (i);
 		return 1;
 	}
 }
diff --git a/sys/MelderThread.h b/sys/MelderThread.h
new file mode 100644
index 0000000..d7f6491
--- /dev/null
+++ b/sys/MelderThread.h
@@ -0,0 +1,173 @@
+#ifndef _MelderThread_h_
+#define _MelderThread_h_
+/* MelderThread.h
+ *
+ * Copyright (C) 2014 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 <vector>
+#include "Thing.h"
+
+#if defined (_WIN32)
+	#define USE_WINTHREADS 1
+	#define USE_PTHREADS 0
+	#define USE_CPPTHREADS 0
+#elif defined (macintosh)
+	#define USE_WINTHREADS 0
+	#define USE_PTHREADS 1
+	#define USE_CPPTHREADS 0
+#else
+	#define USE_WINTHREADS 0
+	#define USE_PTHREADS 1
+	#define USE_CPPTHREADS 0
+#endif
+
+#if USE_WINTHREADS
+	#include <windows.h>
+	#include <process.h>
+	#define MelderThread_MUTEX(_mutex)  static CRITICAL_SECTION _mutex
+	#define MelderThread_MUTEX_INIT(_mutex)  InitializeCriticalSection (& _mutex)
+	#define MelderThread_LOCK(_mutex)  EnterCriticalSection (& _mutex)
+	#define MelderThread_UNLOCK(_mutex)  LeaveCriticalSection (& _mutex)
+	#define MelderThread_RETURN_TYPE  DWORD WINAPI
+	#define MelderThread_RETURN  return 0;
+#elif USE_PTHREADS
+	#include <pthread.h>
+	#define MelderThread_MUTEX(_mutex)  static pthread_mutex_t _mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER
+	#define MelderThread_MUTEX_INIT(_mutex)  (void) 0
+	#define MelderThread_LOCK(_mutex)  pthread_mutex_lock (& _mutex)
+	#define MelderThread_UNLOCK(_mutex)  pthread_mutex_unlock (& _mutex)
+	#define MelderThread_RETURN_TYPE  void *
+	#define MelderThread_RETURN  return NULL;
+#elif USE_CPPTHREADS
+	#include <mutex>
+	#include <thread>
+	#define MelderThread_MUTEX(_mutex)  static std::mutex _mutex
+	#define MelderThread_MUTEX_INIT(_mutex)  (void) 0
+	#define MelderThread_LOCK(_mutex)  std::lock_guard <std::mutex> lock (_mutex)
+	#define MelderThread_UNLOCK(_mutex)  (void) 0
+	#define MelderThread_RETURN_TYPE  void
+	#define MelderThread_RETURN  return;
+#else
+	/* No threads. Make single-threaded. */
+	#define MelderThread_MUTEX(_mutex)  static int _mutex
+	#define MelderThread_MUTEX_INIT(_mutex)  (void) 0
+	#define MelderThread_LOCK(_mutex)  (void) 0
+	#define MelderThread_UNLOCK(_mutex)  (void) 0
+	#define MelderThread_RETURN_TYPE  void
+	#define MelderThread_RETURN  return;
+#endif
+
+#if 0
+	/* For debugging of lock code only. */
+	#define MelderThread_MUTEX(_mutex)  static volatile int _mutex
+	#define MelderThread_MUTEX_INIT(_mutex)  _mutex = 0
+	#define MelderThread_LOCK(_mutex)  do { while (_mutex) ; _mutex = 1; } while (0)
+	#define MelderThread_UNLOCK(_mutex)  _mutex = 0
+#endif
+
+static int MelderThread_getNumberOfProcessors () {
+	#if USE_WINTHREADS
+		return 8;
+	#elif USE_PTHREADS
+		return 8;
+	#elif USE_CPPTHREADS
+		return std::thread::hardware_concurrency ();
+	#else
+		return 1;
+	#endif
+}
+
+#if USE_WINTHREADS
+	template <class T> void MelderThread_run (DWORD (WINAPI *func) (T *), _Thing_auto <T> *args, int numberOfThreads) {
+		if (numberOfThreads == 1) {
+			func (args [0].peek());
+		} else {
+			std::vector <HANDLE> threads (numberOfThreads);
+			try {
+				for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+					threads [ithread - 1] = CreateThread (NULL, 0,
+						(DWORD (WINAPI *)(void *)) func, (void *) args [ithread - 1].peek(), 0, NULL);
+				}
+				func (args [numberOfThreads - 1].peek());
+			} catch (MelderError) {
+				for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+					WaitForSingleObject (threads [ithread - 1], INFINITE);
+					CloseHandle (threads [ithread - 1]);
+				}
+				throw;
+			}
+			for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+				WaitForSingleObject (threads [ithread - 1], INFINITE);
+				CloseHandle (threads [ithread - 1]);
+			}
+		}
+	}
+#elif USE_PTHREADS
+	template <class T> void MelderThread_run (void * (*func) (T *), _Thing_auto <T> *args, int numberOfThreads) {
+		if (numberOfThreads == 1) {
+			func (args [0].peek());
+		} else {
+			std::vector <pthread_t> threads (numberOfThreads);
+			try {
+				for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+					(void) pthread_create (& threads [ithread - 1],
+						NULL, (void*(*)(void *)) func, (void *) args [ithread - 1].peek());
+				}
+				func (args [numberOfThreads - 1].peek());
+			} catch (MelderError) {
+				for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+					pthread_join (threads [ithread - 1], NULL);
+				}
+				throw;
+			}
+			for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+				pthread_join (threads [ithread - 1], NULL);
+			}
+		}
+	}
+#elif USE_CPPTHREADS
+	template <class T> void MelderThread_run (void * (*func) (T *), _Thing_auto <T> *args, int numberOfThreads) {
+		if (numberOfThreads == 1) {
+			func (args [0].peek());
+		} else {
+			std::vector <std::thread> thread (numberOfThreads);
+			try {
+				for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+					thread [ithread - 1] = std::thread (func, args [ithread - 1].peek());
+				}
+				func (args [numberOfThreads - 1].peek());
+			} catch (MelderError) {
+				for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+					if (thread [ithread - 1]. joinable ())
+						thread [ithread - 1]. join ();
+				}
+				throw;
+			}
+			for (int ithread = 1; ithread < numberOfThreads; ithread ++) {
+				thread [ithread - 1]. join ();
+			}
+		}
+	}
+#else
+	template <class T> void MelderThread_run (void (*func) (T *), _Thing_auto <T> *args, int numberOfThreads) {
+		func (args [0].peek());
+	}
+#endif
+
+#endif
+/* End of file MelderThread.h */
diff --git a/sys/Picture.cpp b/sys/Picture.cpp
index ccac239..035fe24 100644
--- a/sys/Picture.cpp
+++ b/sys/Picture.cpp
@@ -1,6 +1,6 @@
 /* Picture.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 Paul Boersma, 2008 Stefan de Konink, 2010 Franz Brauße
  *
  * 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,28 +17,14 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-/*
- * pb 2002/03/07 GPL
- * pb 2003/07/19 extra null byte in Windows metafile name
- * pb 2004/02/19 outer selection
- * pb 2004/09/05 inner selection
- * pb 2005/05/19 EPS files have the option to switch off the screen preview
- * pb 2005/09/18 useSilipaPS
- * pb 2006/10/28 erased MacOS 9 stuff
- * pb 2007/11/30 erased Graphics_printf
- * sdk 2008/05/09 Picture_selfExpose
- * pb 2009/07/22 Picture_writeToPdfFile
- * fb 2010/03/01 cairo fix for black 1px borders
- * pb 2011/05/15 C++
- * pb 2011/07/08 C++
- * pb 2013/08/31 removed all GTK code that redrew the whole Picture window while dragging
- */
-
 #include "melder.h"
 #include "Gui.h"
 #include "Printer.h"
 #include "Picture.h"
 #include "site.h"
+#ifdef _WIN32
+	#include "GraphicsP.h"
+#endif
 
 struct structPicture {
 	GuiDrawingArea drawingArea;
@@ -136,8 +122,10 @@ static void gui_drawingarea_cb_expose (I, GuiDrawingAreaExposeEvent event) {
 		 * The size of the viewable part of the drawing area may have changed.
 		 */
 		Melder_assert (event -> widget);
-		gdk_cairo_reset_clip ((cairo_t *) Graphics_x_getCR (my graphics),          GDK_DRAWABLE (GTK_WIDGET (event -> widget -> d_widget) -> window));
-		gdk_cairo_reset_clip ((cairo_t *) Graphics_x_getCR (my selectionGraphics), GDK_DRAWABLE (GTK_WIDGET (event -> widget -> d_widget) -> window));
+		#if ALLOW_GDK_DRAWING
+			gdk_cairo_reset_clip ((cairo_t *) Graphics_x_getCR (my graphics),          GDK_DRAWABLE (GTK_WIDGET (event -> widget -> d_widget) -> window));
+			gdk_cairo_reset_clip ((cairo_t *) Graphics_x_getCR (my selectionGraphics), GDK_DRAWABLE (GTK_WIDGET (event -> widget -> d_widget) -> window));
+		#endif
 	#endif
 	drawMarkers (me);
 	Graphics_play ((Graphics) my graphics, (Graphics) my graphics);
@@ -175,6 +163,7 @@ static void gui_drawingarea_cb_click (I, GuiDrawingAreaClickEvent event) {
 		ixstart = ix < (ix1 + ix2) / 2 ? ix2 : ix1;
 		iystart = iy < (iy1 + iy2) / 2 ? iy2 : iy1;
 	}
+	//while (Graphics_mouseStillDown (my selectionGraphics)) {
 	do {
 		Graphics_getMouseLocation (my selectionGraphics, & xWC, & yWC);
 		ix = 1 + floor (xWC * SQUARES / SIDE);
@@ -199,6 +188,7 @@ static void gui_drawingarea_cb_click (I, GuiDrawingAreaClickEvent event) {
 			oldix = ix; oldiy = iy;
 		}
 	} while (Graphics_mouseStillDown (my selectionGraphics));
+	// }
 	#if cocoa
 		Graphics_updateWs (my selectionGraphics);   // to change the dark red back into black
 	#endif
@@ -229,11 +219,8 @@ Picture Picture_create (GuiDrawingArea drawingArea, bool sensitive) {
 		} else {
 			/*
 			 * Create a dummy Graphics.
-			 * This has device coordinates from 0 to 32767.
-			 * This will be mapped on an area of 12x12 inches,
-			 * so the resolution is 32767 / 12 = 2731.
 			 */
-			my graphics = Graphics_create (2731);
+			my graphics = Graphics_create (600);
 		}
 		Graphics_setWsWindow (my graphics, 0.0, 12.0, 0.0, 12.0);
 		Graphics_setViewport (my graphics, my selx1, my selx2, my sely1, my sely2);
@@ -373,7 +360,8 @@ static HENHMETAFILE copyToMetafile (Picture me) {
 	SetRect (& rect, my selx1 * 2540, (12 - my sely2) * 2540, my selx2 * 2540, (12 - my sely1) * 2540);
 	dc = CreateEnhMetaFile (defaultPrinter. hDC, NULL, & rect, L"Praat\0");
 	if (! dc) Melder_throw ("Cannot create Windows metafile.");
-	resolution = GetDeviceCaps (dc, LOGPIXELSX);   // Virtual PC: 360
+	resolution = GetDeviceCaps (dc, LOGPIXELSX);   // Virtual PC: 360; Parallels Desktop: 600
+	//Melder_fatal ("resolution %d", resolution);
 	if (Melder_debug == 6) {
 		DEVMODE *devMode = * (DEVMODE **) defaultPrinter. hDevMode;
 		MelderInfo_open ();
@@ -414,7 +402,7 @@ static HENHMETAFILE copyToMetafile (Picture me) {
 void Picture_copyToClipboard (Picture me) {
 	try {
 		HENHMETAFILE metafile = copyToMetafile (me);
-		OpenClipboard (NULL);
+		OpenClipboard (((GraphicsScreen) my graphics) -> d_winWindow);
 		EmptyClipboard ();
 		SetClipboardData (CF_ENHMETAFILE, metafile);
 		CloseClipboard ();
@@ -462,6 +450,24 @@ void Picture_writeToPdfFile (Picture me, MelderFile file) {
 	}
 }
 
+void Picture_writeToPngFile_300 (Picture me, MelderFile file) {
+	try {
+		autoGraphics graphics = Graphics_create_pngfile (file, 300, my selx1, my selx2, my sely1, my sely2);
+		Graphics_play ((Graphics) my graphics, graphics.peek());
+	} catch (MelderError) {
+		Melder_throw ("Picture not written to PNG file ", file, ".");
+	}
+}
+
+void Picture_writeToPngFile_600 (Picture me, MelderFile file) {
+	try {
+		autoGraphics graphics = Graphics_create_pngfile (file, 600, my selx1, my selx2, my sely1, my sely2);
+		Graphics_play ((Graphics) my graphics, graphics.peek());
+	} catch (MelderError) {
+		Melder_throw ("Picture not written to PNG file ", file, ".");
+	}
+}
+
 static void print (I, Graphics printer) {
 	iam (Picture);
 	Graphics_play ((Graphics) my graphics, printer);
diff --git a/sys/Picture.h b/sys/Picture.h
index 80314ae..ef28dc7 100644
--- a/sys/Picture.h
+++ b/sys/Picture.h
@@ -2,7 +2,7 @@
 #define _Picture_h_
 /* Picture.h
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -115,6 +115,8 @@ void Picture_readFromPraatPictureFile (Picture me, MelderFile file);
 
 void Picture_writeToEpsFile (Picture me, MelderFile file, int includeFonts, int useSilipaPS);
 void Picture_writeToPdfFile (Picture me, MelderFile file);
+void Picture_writeToPngFile_300 (Picture me, MelderFile file);
+void Picture_writeToPngFile_600 (Picture me, MelderFile file);
 
 void Picture_print (Picture me);
 void Picture_printToPostScriptPrinter (Picture me, int spots, int paperSize, int rotation, double magnification);
diff --git a/sys/Printer.cpp b/sys/Printer.cpp
index 4cb050e..793c681 100644
--- a/sys/Printer.cpp
+++ b/sys/Printer.cpp
@@ -1,6 +1,6 @@
 /* Printer.cpp
  *
- * Copyright (C) 1998-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1998-2011,2012,2013,2014 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
@@ -46,6 +46,11 @@
 #include "praat.h"   // topShell
 #include "Ui.h"
 #include "site.h"
+#include "GraphicsP.h"
+
+#if cocoa
+	#include "Picture.h"
+#endif
 
 /*
  * Everything must look the same on every printer, including on PDF,
@@ -67,7 +72,9 @@ void Printer_prefs (void) {
 	Preferences_addEnum (L"Printer.fontChoiceStrategy", & thePrinter. fontChoiceStrategy, kGraphicsPostscript_fontChoiceStrategy, kGraphicsPostscript_fontChoiceStrategy_DEFAULT);
 }
 
-#if defined (macintosh)
+#if cocoa
+	static NSView *theMacView;
+#elif defined (macintosh)
 	static PMPrintSession theMacPrintSession;
 	static PMPageFormat theMacPageFormat;
 	static PMPrintSettings theMacPrintSettings;
@@ -83,6 +90,7 @@ void Printer_prefs (void) {
 	int Printer_postScript_printf (void *stream, const char *format, ... ) {
 		#if defined (_WIN32)
 			static union { char chars [3002]; short shorts [1501]; } theLine;
+		#elif cocoa
 		#elif defined (macintosh)
 			static Handle theLine;
 		#endif
@@ -176,6 +184,8 @@ Printer_postScript_printf (NULL, "8 8 scale initclip\n");
 
 void Printer_nextPage (void) {
 	#if cocoa
+		[theMacView endPage];
+		[theMacView beginPageInRect: [theMacView bounds] atPlacement: NSMakePoint (0, 0)];
 	#elif defined (_WIN32)
 		if (thePrinter. postScript) {
 			exitPostScriptPage ();
@@ -202,6 +212,8 @@ void Printer_nextPage (void) {
 
 int Printer_pageSetup (void) {
 	#if cocoa
+		NSPageLayout *cocoaPageSetupDialog = [NSPageLayout pageLayout];
+		[cocoaPageSetupDialog runModal];
 	#elif defined (_WIN32)
 	#elif defined (macintosh)
 		Boolean accepted;
@@ -300,6 +312,40 @@ int Printer_postScriptSettings (void) {
 	}
 #endif
 
+#if cocoa
+	static void (*theDraw) (void *boss, Graphics g);
+	static void *theBoss;
+	@interface GuiCocoaPrintingArea : NSView @end
+	@implementation GuiCocoaPrintingArea {
+		//GuiButton d_userData;
+	}
+	- (void) drawRect: (NSRect) dirtyRect {
+		trace ("printing %f %f %f %f", dirtyRect. origin. x, dirtyRect. origin. y, dirtyRect. size. width, dirtyRect. size. height);
+		int currentPage = [[NSPrintOperation currentOperation] currentPage];
+		thePrinter. graphics = Graphics_create_screenPrinter (NULL, self);
+		theDraw (theBoss, thePrinter. graphics);
+		forget (thePrinter. graphics);
+	}
+	- (BOOL) isFlipped {
+		return YES;
+	}
+	- (NSPoint) locationOfPrintRect: (NSRect) aRect {
+		(void) aRect;
+		return NSMakePoint (0.0, 0.0);   // the origin of the rect's coordinate system is always the top left corner of the physical page
+	}
+	- (BOOL) knowsPageRange: (NSRangePointer) range {
+		range -> length = 1;
+		return YES;
+	}
+	- (NSRect) rectForPage: (NSInteger) pageNumber {
+		(void) pageNumber;   // every page has the same rectangle
+		return [self bounds];
+	}
+	- (void) printOperationDidRun: (NSPrintOperation *) printOperation  success: (BOOL) success  contextInfo: (void *) contextInfo {
+	}
+	@end
+#endif
+
 int Printer_print (void (*draw) (void *boss, Graphics g), void *boss) {
 	try {
 		#if defined (UNIX)
@@ -316,6 +362,48 @@ int Printer_print (void (*draw) (void *boss, Graphics g), void *boss) {
 			system (command);
 			MelderFile_delete (& tempFile);
 		#elif cocoa
+			theDraw = draw;
+			theBoss = boss;
+			NSPrintInfo *info = [NSPrintInfo sharedPrintInfo];
+			NSSize paperSize = [info paperSize];
+			//NSLog (@"%f %f", paperSize. width, paperSize. height);
+			thePrinter. paperWidth = paperSize. width / 0.12;
+			thePrinter. paperHeight = paperSize. height / 0.12;
+			[info setLeftMargin: 0.0];
+			[info setRightMargin: 0.0];
+			[info setTopMargin: 0.0];
+			[info setBottomMargin: 0.0];
+			/*
+			 * Although the paper size reported may be 595 x 842 points (A4),
+			 * 783 points (just under 11 inches) is the largest height that keeps the view on one page.
+			 */
+			int viewWidth = paperSize. width;
+			int viewHeight = paperSize. height;
+			NSLog (@"%d %d", viewWidth, viewHeight);
+			NSRect rect = NSMakeRect (0, 0, viewWidth, viewHeight);
+			NSView *cocoaPrintingArea = [[GuiCocoaPrintingArea alloc] initWithFrame: rect];
+			theMacView = cocoaPrintingArea;
+			[cocoaPrintingArea setBoundsSize: NSMakeSize (viewWidth / 0.12, viewHeight / 0.12)];   // 72 points per inch / 600 dpi = 0.12 points per dot
+			[cocoaPrintingArea setBoundsOrigin: NSMakePoint (0, 0)];
+			NSPrintOperation *op = [NSPrintOperation
+				printOperationWithView: cocoaPrintingArea];
+			#if 1
+				if (op) [op runOperation];
+			#else
+				/*
+				 * This may crash with multiple pages.
+				 */
+				if (op) {
+					[op setCanSpawnSeparateThread: NO];
+					NSView *pictureView = ((GraphicsScreen) Picture_getGraphics ((Picture) boss)) -> d_macView;
+					[op
+						runOperationModalForWindow: [pictureView window]
+						delegate: cocoaPrintingArea
+						didRunSelector: @selector(printOperationDidRun:success:contextInfo:)
+						contextInfo: NULL
+					];
+				}
+			#endif
 		#elif defined (_WIN32)
 			int postScriptCode = POSTSCRIPT_PASSTHROUGH;
 			DOCINFO docInfo;
@@ -428,8 +516,8 @@ int Printer_print (void (*draw) (void *boss, Graphics g), void *boss) {
 			PMGetOrientation (theMacPageFormat, & orientation);
 			thePrinter. orientation = orientation == kPMLandscape ||
 				orientation == kPMReverseLandscape ? kGraphicsPostscript_orientation_LANDSCAPE : kGraphicsPostscript_orientation_PORTRAIT;
-			PMSessionBeginDocument (theMacPrintSession, theMacPrintSettings, theMacPageFormat);   // PMSessionBeginCGDocumentNoDialog
-			PMSessionBeginPage (theMacPrintSession, theMacPageFormat, NULL);   // PMSessionBeginPageNoDialog
+			PMSessionBeginDocument (theMacPrintSession, theMacPrintSettings, theMacPageFormat);
+			PMSessionBeginPage (theMacPrintSession, theMacPageFormat, NULL);
 			PMSessionGetGraphicsContext (theMacPrintSession, kPMGraphicsContextQuickdraw, (void **) & theMacPort);
 			/*
 			 * On PostScript, the point (0, 0) is the bottom left corner of the paper, which is fine.
@@ -448,8 +536,8 @@ int Printer_print (void (*draw) (void *boss, Graphics g), void *boss) {
 			draw (boss, thePrinter. graphics);
 			forget (thePrinter. graphics);
 			if (theMacPort) {
-				PMSessionEndPage (theMacPrintSession);   // PMSessionEndPageNoDialog
-				PMSessionEndDocument (theMacPrintSession);   // PMSessionEndDocumentNoDialog
+				PMSessionEndPage (theMacPrintSession);
+				PMSessionEndDocument (theMacPrintSession);
 				theMacPort = NULL;
 			}
 		#endif
diff --git a/sys/ScriptEditor.cpp b/sys/ScriptEditor.cpp
index e055b78..c4a0752 100644
--- a/sys/ScriptEditor.cpp
+++ b/sys/ScriptEditor.cpp
@@ -67,12 +67,14 @@ void structScriptEditor :: v_goAway () {
 
 static void args_ok (UiForm sendingForm, int narg_dummy, Stackel args_dummy, const wchar_t *sendingString_dummy, Interpreter interpreter_dummy, const wchar_t *invokingButtonTitle, bool modified_dummy, I) {
 	iam (ScriptEditor);
+	(void) narg_dummy;
+	(void) args_dummy;
 	(void) sendingString_dummy;
 	(void) interpreter_dummy;
 	(void) invokingButtonTitle;
 	(void) modified_dummy;
+	autostring text = my textWidget -> f_getString ();
 	structMelderFile file = { 0 };
-	wchar_t *text = my textWidget -> f_getString ();   // BUG: auto
 	if (my name [0]) {
 		Melder_pathToFile (my name, & file);
 		MelderFile_setDefaultDir (& file);
@@ -83,40 +85,60 @@ static void args_ok (UiForm sendingForm, int narg_dummy, Stackel args_dummy, con
 
 	autoPraatBackground background;
 	if (my name [0]) MelderFile_setDefaultDir (& file);
-	Interpreter_run (my interpreter, text);
-	Melder_free (text);
+	Interpreter_run (my interpreter, text.peek());
 }
 
-static void run (ScriptEditor me, wchar_t **text) {
+static void args_ok_selectionOnly (UiForm sendingForm, int narg_dummy, Stackel args_dummy, const wchar_t *sendingString_dummy, Interpreter interpreter_dummy, const wchar_t *invokingButtonTitle, bool modified_dummy, I) {
+	iam (ScriptEditor);
+	(void) narg_dummy;
+	(void) args_dummy;
+	(void) sendingString_dummy;
+	(void) interpreter_dummy;
+	(void) invokingButtonTitle;
+	(void) modified_dummy;
+	autostring text = my textWidget -> f_getSelection ();
+	if (text.peek() == NULL)
+		Melder_throw ("No text is selected any longer.\nPlease reselect or click Cancel.");
+	structMelderFile file = { 0 };
+	if (my name [0]) {
+		Melder_pathToFile (my name, & file);
+		MelderFile_setDefaultDir (& file);
+	}
+	Melder_includeIncludeFiles (& text);
+
+	Interpreter_getArgumentsFromDialog (my interpreter, sendingForm);
+
+	autoPraatBackground background;
+	if (my name [0]) MelderFile_setDefaultDir (& file);
+	Interpreter_run (my interpreter, text.peek());
+}
+
+static void menu_cb_run (EDITOR_ARGS) {
+	EDITOR_IAM (ScriptEditor);
+	if (my interpreter -> running)
+		Melder_throw ("The script is already running (paused). Please close or continue the pause or demo window.");
+	autostring text = my textWidget -> f_getString ();
 	structMelderFile file = { 0 };
 	if (my name [0]) {
 		Melder_pathToFile (my name, & file);
 		MelderFile_setDefaultDir (& file);
 	}
-	Melder_includeIncludeFiles (text);
-	int npar = Interpreter_readParameters (my interpreter, *text);
+	Melder_includeIncludeFiles (& text);
+	int npar = Interpreter_readParameters (my interpreter, text.peek());
 	if (npar) {
 		/*
 		 * Pop up a dialog box for querying the arguments.
 		 */
 		forget (my argsDialog);
-		my argsDialog = Interpreter_createForm (my interpreter, my d_windowForm, NULL, args_ok, me);
+		my argsDialog = Interpreter_createForm (my interpreter, my d_windowForm, NULL, args_ok, me, false);
 		UiForm_do (my argsDialog, false);
 	} else {
 		autoPraatBackground background;
 		if (my name [0]) MelderFile_setDefaultDir (& file);
-		Interpreter_run (my interpreter, *text);
+		Interpreter_run (my interpreter, text.peek());
 	}
 }
 
-static void menu_cb_run (EDITOR_ARGS) {
-	EDITOR_IAM (ScriptEditor);
-	if (my interpreter -> running)
-		Melder_throw ("The script is already running (paused). Please close or continue the pause or demo window.");
-	autostring text = my textWidget -> f_getString ();   // BUG: not an autostring (the text pointer can be changed by including include files)
-	run (me, & text);
-}
-
 static void menu_cb_runSelection (EDITOR_ARGS) {
 	EDITOR_IAM (ScriptEditor);
 	if (my interpreter -> running)
@@ -124,7 +146,25 @@ static void menu_cb_runSelection (EDITOR_ARGS) {
 	autostring text = my textWidget -> f_getSelection ();
 	if (text.peek() == NULL)
 		Melder_throw ("No text selected.");
-	run (me, & text);
+	structMelderFile file = { 0 };
+	if (my name [0]) {
+		Melder_pathToFile (my name, & file);
+		MelderFile_setDefaultDir (& file);
+	}
+	Melder_includeIncludeFiles (& text);
+	int npar = Interpreter_readParameters (my interpreter, text.peek());
+	if (npar) {
+		/*
+		 * Pop up a dialog box for querying the arguments.
+		 */
+		forget (my argsDialog);
+		my argsDialog = Interpreter_createForm (my interpreter, my d_windowForm, NULL, args_ok_selectionOnly, me, true);
+		UiForm_do (my argsDialog, false);
+	} else {
+		autoPraatBackground background;
+		if (my name [0]) MelderFile_setDefaultDir (& file);
+		Interpreter_run (my interpreter, text.peek());
+	}
 }
 
 static void menu_cb_addToMenu (EDITOR_ARGS) {
diff --git a/sys/Strings.cpp b/sys/Strings.cpp
index d5cdd8b..59e7a26 100644
--- a/sys/Strings.cpp
+++ b/sys/Strings.cpp
@@ -145,7 +145,7 @@ static Strings Strings_createAsFileOrDirectoryList (const wchar_t *path, int typ
 				Melder_wcsTo8bitFileRepresentation_inline (filePath. string, buffer8);
 				struct stat stats;
 				if (stat (buffer8, & stats) != 0) {
-					Melder_throw ("Cannot look at file ", filePath. string, ".");
+					//Melder_throw ("Cannot look at file ", filePath. string, ".");
 					//stats. st_mode = -1L;
 				}
 				//Melder_casual ("statted %s", filePath. string);
diff --git a/sys/TextEditor.cpp b/sys/TextEditor.cpp
index db9c421..201429a 100644
--- a/sys/TextEditor.cpp
+++ b/sys/TextEditor.cpp
@@ -594,7 +594,7 @@ static void menu_cb_convertToCString (EDITOR_ARGS) {
 		} else if (*p == '\\') {
 			MelderInfo_write (L"\\\\");
 		} else if (*p < 0 || *p > 127) {
-			uint32 kar = *p;
+			uint32_t kar = *p;
 			if (kar <= 0xFFFF) {
 				MelderInfo_write (L"\\u", hex [kar >> 12], hex [(kar >> 8) & 0x0000000F], hex [(kar >> 4) & 0x0000000F], hex [kar & 0x0000000F]);
 			} else {
@@ -641,6 +641,20 @@ static void menu_cb_fontSize (EDITOR_ARGS) {
 	EDITOR_END
 }
 
+static void gui_text_cb_change (I, GuiTextEvent event) {
+	(void) event;
+	iam (TextEditor);
+	if (! my dirty) {
+		my dirty = TRUE;
+		my v_nameChanged ();
+	}
+}
+
+void structTextEditor :: v_createChildren () {
+	textWidget = GuiText_createShown (d_windowForm, 0, 0, Machine_getMenuBarHeight (), 0, GuiText_SCROLLED);
+	textWidget -> f_setChangeCallback (gui_text_cb_change, this);
+}
+
 void structTextEditor :: v_createMenus () {
 	TextEditor_Parent :: v_createMenus ();
 	if (v_fileBased ()) {
@@ -658,8 +672,8 @@ void structTextEditor :: v_createMenus () {
 		Editor_addCommand (this, L"File", L"Save as...", 'S', menu_cb_saveAs);
 	}
 	Editor_addCommand (this, L"File", L"-- close --", 0, NULL);
-	Editor_addCommand (this, L"Edit", L"Undo", 'Z', menu_cb_undo);
-	Editor_addCommand (this, L"Edit", L"Redo", 'Y', menu_cb_redo);
+	textWidget -> f_setUndoItem (Editor_addCommand (this, L"Edit", L"Undo", 'Z', menu_cb_undo));
+	textWidget -> f_setRedoItem (Editor_addCommand (this, L"Edit", L"Redo", 'Y', menu_cb_redo));
 	Editor_addCommand (this, L"Edit", L"-- cut copy paste --", 0, NULL);
 	Editor_addCommand (this, L"Edit", L"Cut", 'X', menu_cb_cut);
 	Editor_addCommand (this, L"Edit", L"Copy", 'C', menu_cb_copy);
@@ -686,22 +700,6 @@ void structTextEditor :: v_createMenus () {
 	#endif
 }
 
-static void gui_text_cb_change (I, GuiTextEvent event) {
-	(void) event;
-	iam (TextEditor);
-	if (! my dirty) {
-		my dirty = TRUE;
-		my v_nameChanged ();
-	}
-}
-
-void structTextEditor :: v_createChildren () {
-	textWidget = GuiText_createShown (d_windowForm, 0, 0, Machine_getMenuBarHeight (), 0, GuiText_SCROLLED);
-	textWidget -> f_setChangeCallback (gui_text_cb_change, this);
-	textWidget -> f_setUndoItem (Editor_getMenuCommand (this, L"Edit", L"Undo") -> itemWidget);
-	textWidget -> f_setRedoItem (Editor_getMenuCommand (this, L"Edit", L"Redo") -> itemWidget);
-}
-
 void structTextEditor :: init (const wchar_t *initialText) {
 	Editor_init (this, 0, 0, 600, 400, L"", NULL);
 	setFontSize (this, p_fontSize);
diff --git a/sys/Thing.h b/sys/Thing.h
index 0db447c..8292eb0 100644
--- a/sys/Thing.h
+++ b/sys/Thing.h
@@ -2,7 +2,7 @@
 #define _Thing_h_
 /* Thing.h
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -51,6 +51,7 @@ typedef void *Any;   /* Prevent compile-time type checking. */
 #define my  me ->
 #define thy  thee ->
 #define his  him ->
+#define our  this ->
 
 typedef struct structClassInfo *ClassInfo;
 struct structClassInfo {
@@ -119,7 +120,7 @@ class structThing {
 			 */
 };
 
-#define forget(thing)  do { _Thing_forget (thing); thing = NULL; } while (0)
+#define forget(thing)  do { _Thing_forget (thing); thing = NULL; } while (false)
 /*
 	Function:
 		free all memory associated with 'thing'.
diff --git a/sys/Ui.cpp b/sys/Ui.cpp
index 062ebab..ea26458 100644
--- a/sys/Ui.cpp
+++ b/sys/Ui.cpp
@@ -366,6 +366,17 @@ void UiHistory_write_expandQuotes (const wchar_t *string) {
 		if (*p == '\"') MelderString_append (& theHistory, L"\"\""); else MelderString_appendCharacter (& theHistory, *p);
 	}
 }
+void UiHistory_write_colonize (const wchar_t *string) {
+	if (string == NULL) return;
+	for (const wchar_t *p = & string [0]; *p != '\0'; p ++) {
+		if (*p == '.' && p [1] == '.' && p [2] == '.') {
+			MelderString_append (& theHistory, L":");
+			p += 2;
+		} else {
+			MelderString_appendCharacter (& theHistory, *p);
+		}
+	}
+}
 wchar_t *UiHistory_get (void) { return theHistory.string; }
 void UiHistory_clear (void) { MelderString_empty (& theHistory); }
 
@@ -462,44 +473,43 @@ static void UiForm_okOrApply (I, GuiButton button, int hide) {
 		 * Write everything to history. Before destruction!
 		 */
 		if (! my isPauseForm) {
-			UiHistory_write (L"\ndo (\"");
-			UiHistory_write_expandQuotes (my invokingButtonTitle);
-			UiHistory_write (L"\"");
+			UiHistory_write (L"\n");
+			UiHistory_write_colonize (my invokingButtonTitle);
 			int size = my numberOfFields;
 			while (size >= 1 && my field [size] -> type == UI_LABEL)
 				size --;   // ignore trailing fields without a value
+			int next = 0;
 			for (int ifield = 1; ifield <= size; ifield ++) {
 				UiField field = my field [ifield];
 				switch (field -> type) {
 					case UI_REAL: case UI_REAL_OR_UNDEFINED: case UI_POSITIVE: {
-						UiHistory_write (L", ");
+						UiHistory_write (next -- ? L", " : L" ");
 						UiHistory_write (Melder_double (field -> realValue));
 					} break; case UI_INTEGER: case UI_NATURAL: case UI_CHANNEL: {
-						UiHistory_write (L", ");
+						UiHistory_write (next -- ? L", " : L" ");
 						UiHistory_write (Melder_integer (field -> integerValue));
 					} break; case UI_WORD: case UI_SENTENCE: case UI_TEXT: {
-						UiHistory_write (L", \"");
+						UiHistory_write (next -- ? L", \"" : L" \"");
 						UiHistory_write_expandQuotes (field -> stringValue);
 						UiHistory_write (L"\"");
 					} break; case UI_BOOLEAN: {
-						UiHistory_write (field -> integerValue ? L", \"yes\"" : L", \"no\"");
+						UiHistory_write (field -> integerValue ? (next -- ? L", \"yes\"" : L" \"yes\"") : (next -- ? L", \"no\"" : L" \"no\""));
 					} break; case UI_RADIO: case UI_OPTIONMENU: {
 						UiOption b = static_cast <UiOption> (field -> options -> item [field -> integerValue]);
-						UiHistory_write (L", \"");
+						UiHistory_write (next -- ? L", \"" : L" \"");
 						UiHistory_write_expandQuotes (b -> name);
 						UiHistory_write (L"\"");
 					} break; case UI_LIST: {
-						UiHistory_write (L", \"");
+						UiHistory_write (next -- ? L", \"" : L" \"");
 						UiHistory_write_expandQuotes (field -> strings [field -> integerValue]);
 						UiHistory_write (L"\"");
 					} break; case UI_COLOUR: {
-						UiHistory_write (L", \"");
+						UiHistory_write (next -- ? L", \"" : L" \"");
 						UiHistory_write (Graphics_Colour_name (field -> colourValue));
 						UiHistory_write (L"\"");
 					}
 				}
 			}
-			UiHistory_write (L")");
 		}
 		if (hide) {
 			my d_dialogForm -> f_hide ();
@@ -908,7 +918,14 @@ void UiForm_finish (I) {
 		my helpButton = GuiButton_createShown (form, HELP_BUTTON_X, HELP_BUTTON_X + HELP_BUTTON_WIDTH, y, y + Gui_PUSHBUTTON_HEIGHT,
 			L"Help", gui_button_cb_help, me, 0);
 	}
-	if (my numberOfFields > 1 || (my numberOfFields > 0 && my field [1] -> type != UI_LABEL)) {
+	bool commentsOnly = true;
+	for (long ifield = 1; ifield <= my numberOfFields; ifield ++) {
+		if (my field [ifield] -> type != UI_LABEL) {
+			commentsOnly = false;
+			break;
+		}
+	}
+	if (! commentsOnly) {
 		if (my isPauseForm) {
 			my revertButton = GuiButton_createShown (form,
 				HELP_BUTTON_X, HELP_BUTTON_X + REVERT_BUTTON_WIDTH,
diff --git a/sys/Ui.h b/sys/Ui.h
index c08ec19..dbf16f9 100644
--- a/sys/Ui.h
+++ b/sys/Ui.h
@@ -237,6 +237,7 @@ void UiFile_hide (void);
 
 void UiHistory_write (const wchar_t *string);
 void UiHistory_write_expandQuotes (const wchar_t *string);
+void UiHistory_write_colonize (const wchar_t *string);
 wchar_t *UiHistory_get (void);
 void UiHistory_clear (void);
 
diff --git a/sys/UiFile.cpp b/sys/UiFile.cpp
index 314ed99..81f0bc3 100644
--- a/sys/UiFile.cpp
+++ b/sys/UiFile.cpp
@@ -1,6 +1,6 @@
 /* UiFile.cpp
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -83,11 +83,11 @@ void UiInfile_do (I) {
 		for (long ifile = 1; ifile <= infileNames -> size; ifile ++) {
 			SimpleString infileName = (SimpleString) infileNames -> item [ifile];
 			Melder_pathToFile (infileName -> string, & my file);
-			UiHistory_write (L"\ndo (\"");
-			UiHistory_write_expandQuotes (my invokingButtonTitle);
-			UiHistory_write (L"\", \"");
+			UiHistory_write (L"\n");
+			UiHistory_write_colonize (my invokingButtonTitle);
+			UiHistory_write (L" \"");
 			UiHistory_write_expandQuotes (infileName -> string);
-			UiHistory_write (L"\")");
+			UiHistory_write (L"\"");
 			structMelderFile file;
 			MelderFile_copy (& my file, & file);
 			try {
@@ -158,17 +158,17 @@ void UiOutfile_do (I, const wchar_t *defaultName) {
 	Melder_pathToFile (outfileName, & my file);
 	structMelderFile file;
 	MelderFile_copy (& my file, & file);   // save, because okCallback could destroy me
-	UiHistory_write (L"\ndo (\"");
-	UiHistory_write_expandQuotes (my invokingButtonTitle);
+	UiHistory_write (L"\n");
+	UiHistory_write_colonize (my invokingButtonTitle);
 	try {
 		my okCallback ((UiForm) me, 0, NULL, NULL, NULL, my invokingButtonTitle, false, my okClosure);
 	} catch (MelderError) {
 		Melder_error_ ("File ", & file, " not finished.");
 		Melder_flushError (NULL);
 	}
-	UiHistory_write (L"\", \"");
+	UiHistory_write (L" \"");
 	UiHistory_write (outfileName);
-	UiHistory_write (L"\")");
+	UiHistory_write (L"\"");
 	Melder_free (outfileName);
 }
 
diff --git a/sys/UiPause.cpp b/sys/UiPause.cpp
index 133e9c1..cd88d9f 100644
--- a/sys/UiPause.cpp
+++ b/sys/UiPause.cpp
@@ -167,6 +167,20 @@ int UiPause_end (int numberOfContinueButtons, int defaultContinueButton, int can
 						gtk_main_iteration ();
 					} while (! thePauseForm_clicked);
 				#elif cocoa
+					do {
+						NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+						//[theDemoEditor -> d_windowForm -> d_cocoaWindow   flushWindow];
+						NSEvent *nsEvent = [NSApp
+							nextEventMatchingMask: NSAnyEventMask
+							untilDate: [NSDate distantFuture]   // wait
+							inMode: NSDefaultRunLoopMode
+							dequeue: YES
+						];
+						Melder_assert (nsEvent != NULL);
+						[NSApp  sendEvent: nsEvent];
+						[NSApp  updateWindows];   // called automatically?
+						[pool release];
+					} while (! thePauseForm_clicked);
 				#elif motif
 					do {
 						XEvent event;
diff --git a/sys/abcio.cpp b/sys/abcio.cpp
index edf4daa..633b7ee 100644
--- a/sys/abcio.cpp
+++ b/sys/abcio.cpp
@@ -820,7 +820,9 @@ int bingeti2LE (FILE *f) {
 		if (binario_shortLE2 && Melder_debug != 18) {
 			signed short s;
 			if (fread (& s, sizeof (signed short), 1, f) != 1) readError (f, "a signed short integer.");
-			return (int) s;   // with sign extension if an int is 4 bytes
+			int result = (int) s;   // with sign extension if an int is 4 bytes
+			Melder_assert (result >= -32768 && result <= 32767);
+			return result;   // with sign extension if an int is 4 bytes
 		} else {
 			unsigned char bytes [2];
 			if (fread (bytes, sizeof (unsigned char), 2, f) != 2) readError (f, "two bytes.");
diff --git a/sys/melder.cpp b/sys/melder.cpp
index 80b1e57..6928153 100644
--- a/sys/melder.cpp
+++ b/sys/melder.cpp
@@ -1,6 +1,6 @@
 /* melder.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma, 2008 Stefan de Konink, 2010 Franz Brausse, 2013 Tom Naughton
+ * Copyright (C) 1992-2012,2013,2014 Paul Boersma, 2008 Stefan de Konink, 2010 Franz Brausse, 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
@@ -46,6 +46,7 @@
 	#endif
 	#include "Gui.h"
 #endif
+#include "MelderThread.h"
 
 #include "enums_getText.h"
 #include "melder_enums.h"
@@ -56,6 +57,7 @@
 
 bool Melder_batch;   // don't we have a GUI?- Set once at application start-up
 bool Melder_backgrounding;   // are we running a script?- Set and unset dynamically
+bool Melder_asynchronous;
 char Melder_buffer1 [30001], Melder_buffer2 [30001];
 unsigned long Melder_systemVersion;
 
@@ -141,6 +143,7 @@ void Melder_casual (const char *format, ...) {
 /********** PROGRESS **********/
 
 static int theProgressDepth = 0;
+static bool theProgressCancelled = false;
 void Melder_progressOff (void) { theProgressDepth --; }
 void Melder_progressOn (void) { theProgressDepth ++; }
 
@@ -260,6 +263,10 @@ static bool waitWhileProgress (double progress, const wchar_t *message, GuiDialo
 		#elif cocoa
 			scale -> f_setValue (progress);
 			//[scale -> d_cocoaProgressBar   displayIfNeeded];
+			if (theProgressCancelled) {
+				theProgressCancelled = false;
+				return false;
+			}
 		#elif motif
 			scale -> f_setValue (progress);
 			XmUpdateDisplay (dia -> d_widget);
@@ -269,20 +276,30 @@ static bool waitWhileProgress (double progress, const wchar_t *message, GuiDialo
 	return true;
 }
 
-#if gtk
+#if gtk || macintosh
 static void progress_dia_close (void *cancelButton) {
-	g_object_set_data (G_OBJECT ((* (GuiButton *) cancelButton) -> d_widget), "pressed", (gpointer) 1);
+	theProgressCancelled = true;
+	#if gtk
+		g_object_set_data (G_OBJECT ((* (GuiButton *) cancelButton) -> d_widget), "pressed", (gpointer) 1);
+	#else
+		(void) cancelButton;
+	#endif
 }
 static void progress_cancel_btn_press (void *cancelButton, GuiButtonEvent event) {
 	(void) event;
-	g_object_set_data (G_OBJECT ((* (GuiButton *) cancelButton) -> d_widget), "pressed", (gpointer) 1);
+	theProgressCancelled = true;
+	#if gtk
+		g_object_set_data (G_OBJECT ((* (GuiButton *) cancelButton) -> d_widget), "pressed", (gpointer) 1);
+	#else
+		(void) cancelButton;
+	#endif
 }
 #endif
 
 static void _Melder_dia_init (GuiDialog *dia, GuiProgressBar *scale, GuiLabel *label1, GuiLabel *label2, GuiButton *cancelButton, bool hasMonitor) {
 	trace ("creating the dialog");
 	*dia = GuiDialog_create (Melder_topShell, 200, 100, 400, hasMonitor ? 430 : 200, L"Work in progress",
-		#if gtk
+		#if gtk || macintosh
 			progress_dia_close, cancelButton,
 		#else
 			NULL, NULL,
@@ -299,7 +316,7 @@ static void _Melder_dia_init (GuiDialog *dia, GuiProgressBar *scale, GuiLabel *l
 	trace ("creating the cancel button");
 	*cancelButton = GuiButton_createShown (*dia, 0, 400, 170, 170 + Gui_PUSHBUTTON_HEIGHT,
 		L"Interrupt",
-		#if gtk
+		#if gtk || macintosh
 			progress_cancel_btn_press, cancelButton,
 		#else
 			NULL, NULL,
@@ -765,7 +782,15 @@ void Melder_beep (void) {
 
 /*********** FATAL **********/
 
+MelderThread_MUTEX (theMelder_fatal_mutex);
+
+void Melder_message_init () {
+	static bool inited = false;
+	if (! inited) { MelderThread_MUTEX_INIT (theMelder_fatal_mutex); inited = true; }
+}
+
 int Melder_fatal (const char *format, ...) {
+	MelderThread_LOCK (theMelder_fatal_mutex);
 	const char *lead = strstr (format, "Praat cannot start up") ? "" :
 		"Praat will crash. Notify the author (paul.boersma at uva.nl) with the following information:\n";
 	va_list arg;
@@ -791,7 +816,7 @@ static void mac_message (NSAlertStyle macAlertType, const wchar_t *messageW) {
 	static unichar messageU [4000];
 	int messageLength = wcslen (messageW);
 	int j = 0;
-	for (int i = 0; i < messageLength && j <= 4000 - 2; i ++) {
+	for (int i = 0; i < messageLength && j <= 4000 - 3; i ++) {
 		uint32_t kar = messageW [i];
 		if (kar <= 0xFFFF) {
 			messageU [j ++] = kar;
@@ -801,6 +826,7 @@ static void mac_message (NSAlertStyle macAlertType, const wchar_t *messageW) {
 			messageU [j ++] = 0xDC00 | (kar & 0x3FF);
 		}
 	}
+	messageU [j] = '\0';   // append null byte because we are going to search this string
 
 	/*
 	 * Split up the message between header (will appear in bold) and rest.
@@ -826,15 +852,6 @@ static void mac_message (NSAlertStyle macAlertType, const wchar_t *messageW) {
 		CreateStandardAlert (macAlertType, messageCF, NULL, NULL, & dialog);
 		CFRelease (messageCF);
 		RunStandardAlert (dialog, NULL, NULL);
-	#elif fhgfdghdggfkdsgfXXX
-		NSString *header = NULL, *rest = NULL;
-		header = [[NSString alloc] initWithCharacters: messageU   length: lineBreak - messageU];   // note: init can change the object pointer!
-		if (lineBreak - messageU != j) {
-			rest = [[NSString alloc] initWithCharacters: lineBreak + 1   length: j - 1 - (lineBreak - messageU)];
-		}
-		NSRunAlertPanel (header, rest, NULL, NULL, NULL);
-		[header release];
-		if (rest) [rest release];
 	#else
 		/*
 		 * Create an alert dialog with an icon that is appropriate for the level.
@@ -845,15 +862,19 @@ static void mac_message (NSAlertStyle macAlertType, const wchar_t *messageW) {
 		 * Add the header in bold.
 		 */
 		NSString *header = [[NSString alloc] initWithCharacters: messageU   length: lineBreak - messageU];   // note: init can change the object pointer!
-		[alert setMessageText: header];
-		[header release];
+		if (header) {   // make this very safe, because we can be at error time or at fatal time
+			[alert setMessageText: header];
+			[header release];
+		}
 		/*
 		 * Add the rest of the message in small type.
 		 */
-		if (lineBreak - messageU != j) {
+		if (lineBreak - messageU < j) {
 			NSString *rest = [[NSString alloc] initWithCharacters: lineBreak + 1   length: j - 1 - (lineBreak - messageU)];
-			[alert setInformativeText: rest];
-			[rest release];
+			if (rest) {   // make this very safe, because we can be at error time or at fatal time
+				[alert setInformativeText: rest];
+				[rest release];
+			}
 		}
 		/*
 		 * Display the alert dialog and synchronously wait for the user to click OK.
diff --git a/sys/melder.h b/sys/melder.h
index 16f204d..a0d061c 100644
--- a/sys/melder.h
+++ b/sys/melder.h
@@ -2,7 +2,7 @@
 #define _melder_h_
 /* melder.h
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -34,20 +34,14 @@
 #include <stddef.h>
 #include <wchar.h>
 #ifdef __MINGW32__
+	#undef swprintf
 	#define swprintf  _snwprintf
+	//#define swprintf  __mingw_snwprintf
 #endif
 #include <stdbool.h>
 #include <stdint.h>
 
 typedef wchar_t wchar;
-typedef int8_t int8;
-typedef uint8_t uint8;
-typedef int16_t int16;
-typedef uint16_t uint16;
-typedef int32_t int32;
-typedef uint32_t uint32;
-typedef int64_t int64;
-typedef uint64_t uint64;
 
 bool Melder_wcsequ_firstCharacterCaseInsensitive (const wchar_t *string1, const wchar_t *string2);
 
@@ -65,11 +59,17 @@ bool Melder_wcsequ_firstCharacterCaseInsensitive (const wchar_t *string1, const
 	#define NULL  ((void *) 0)
 #endif
 
+/*
+ * Operating system version control.
+ */
+#define ALLOW_GDK_DRAWING  1
+/* */
+
 typedef struct { double red, green, blue, transparency; } double_rgbt;
 
 /********** NUMBER TO STRING CONVERSION **********/
 
-/*
+/**
 	The following routines return a static string, chosen from a circularly used set of 11 buffers.
 	You can call at most 11 of them in one Melder_casual call, for instance.
 */
@@ -110,6 +110,7 @@ void Melder_writeToConsole (const wchar_t *message, bool useStderr);
 /* These routines also maintain a count of the total number of blocks allocated. */
 
 void Melder_alloc_init (void);   // to be called around program start-up
+void Melder_message_init (void);   // to be called around program start-up
 void * _Melder_malloc (unsigned long size);
 #define Melder_malloc(type,numberOfElements)  (type *) _Melder_malloc ((numberOfElements) * sizeof (type))
 void * _Melder_malloc_f (unsigned long size);
@@ -135,7 +136,7 @@ wchar_t * Melder_wcsExpandBackslashSequences (const wchar_t *string);
 wchar_t * Melder_wcsReduceBackslashSequences (const wchar_t *string);
 void Melder_wcsReduceBackslashSequences_inline (const wchar_t *string);
 
-/*
+/**
  * Text encodings.
  */
 void Melder_textEncoding_prefs (void);
@@ -149,13 +150,13 @@ enum kMelder_textOutputEncoding Melder_getOutputEncoding (void);
  * these constants should stay separate from the above encoding constants
  * because they occur in the same fields of struct MelderFile.
  */
-#define kMelder_textInputEncoding_FLAC  0x464C4143
-#define kMelder_textOutputEncoding_ASCII  0x41534349
-#define kMelder_textOutputEncoding_ISO_LATIN1  0x4C415401
-#define kMelder_textOutputEncoding_FLAC  0x464C4143
+const uint32_t kMelder_textInputEncoding_FLAC = 0x464C4143;
+const uint32_t kMelder_textOutputEncoding_ASCII = 0x41534349;
+const uint32_t kMelder_textOutputEncoding_ISO_LATIN1 = 0x4C415401;
+const uint32_t kMelder_textOutputEncoding_FLAC = 0x464C4143;
 
-typedef uint16 MelderUtf16;
-typedef uint32 MelderUtf32;
+typedef uint16_t MelderUtf16;
+typedef uint32_t MelderUtf32;
 
 bool Melder_isValidAscii (const wchar_t *string);
 bool Melder_strIsValidUtf8 (const char *string);
@@ -516,7 +517,7 @@ void Melder_beep (void);
 extern int Melder_debug;
 
 /* The following trick uses Melder_debug only because it is the only plain variable known to exist at the moment. */
-#define Melder_offsetof(klas,member) (char *) & ((klas) & Melder_debug) -> member - (char *) & Melder_debug
+#define Melder_offsetof(klas,member) (int) ((char *) & ((klas) & Melder_debug) -> member - (char *) & Melder_debug)
 
 /********** ERROR **********/
 
@@ -572,6 +573,7 @@ void Melder_throw (const MelderArg& arg1, const MelderArg& arg2, const MelderArg
 	const MelderArg& arg9, const MelderArg& arg10, const MelderArg& arg11, const MelderArg& arg12,
 	const MelderArg& arg13, const MelderArg& arg14, const MelderArg& arg15, const MelderArg& arg16,
 	const MelderArg& arg17 = L"", const MelderArg& arg18 = L"", const MelderArg& arg19 = L"", const MelderArg& arg20 = L"");
+void Melder_error_noLine (const MelderArg& arg1);
 void Melder_error_ (const MelderArg& arg1);
 void Melder_error_ (const MelderArg& arg1, const MelderArg& arg2);
 void Melder_error_ (const MelderArg& arg1, const MelderArg& arg2, const MelderArg& arg3);
@@ -811,7 +813,7 @@ int Melder_publishPlayed (void);
 
 extern unsigned long Melder_systemVersion;
 /*
-	For Macintosh, this is set in the Motif emulator.
+	For Macintosh, this is set in praat_init.
 */
 
 /********** SCRATCH TEXT BUFFERS **********/
@@ -843,6 +845,7 @@ void MelderGui_create (/* GuiWindow */ void *parent);
 extern bool Melder_batch;   // true if run from the batch or from an interactive command-line interface
 extern bool Melder_backgrounding;   /* True if running a script. */
 extern bool Melder_consoleIsAnsi;
+extern bool Melder_asynchronous;   // true if specified by the "asynchronous" directive in a script
 #ifndef CONTROL_APPLICATION
 	typedef struct structGuiWindow *GuiWindow;
 	extern GuiWindow Melder_topShell;
@@ -916,8 +919,6 @@ double MelderAudio_getOutputSilenceBefore (void);
 #endif
 void MelderAudio_setOutputSilenceAfter (double silenceAfter);
 double MelderAudio_getOutputSilenceAfter (void);
-void MelderAudio_setOutputUsesBlocking (bool outputUsesBlocking);
-bool MelderAudio_getOutputUsesBlocking (void);
 void MelderAudio_setUseInternalSpeaker (bool useInternalSpeaker);   // for HP-UX and Sun
 bool MelderAudio_getUseInternalSpeaker (void);
 void MelderAudio_setOutputMaximumAsynchronicity (enum kMelder_asynchronicityLevel maximumAsynchronicity);
@@ -925,7 +926,7 @@ enum kMelder_asynchronicityLevel MelderAudio_getOutputMaximumAsynchronicity (voi
 long MelderAudio_getOutputBestSampleRate (long fsamp);
 
 extern bool MelderAudio_isPlaying;
-void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSamples, int numberOfChannels,
+void MelderAudio_play16 (int16_t *buffer, long sampleRate, long numberOfSamples, int numberOfChannels,
 	bool (*playCallback) (void *playClosure, long numberOfSamplesPlayed),   // return true to continue, false to stop
 	void *playClosure);
 bool MelderAudio_stopPlaying (bool isExplicit);   // returns true if sound was playing
@@ -991,8 +992,6 @@ void Melder_readAudioToShort (FILE *f, int numberOfChannels, int encoding, short
 void MelderFile_writeFloatToAudio (MelderFile file, int numberOfChannels, int encoding, double **buffer, long numberOfSamples, int warnIfClipped);
 void MelderFile_writeShortToAudio (MelderFile file, int numberOfChannels, int encoding, const short *buffer, long numberOfSamples);
 
-void Melder_audioTrigger (void);
-
 /********** QUANTITY **********/
 
 #define MelderQuantity_NONE  0
@@ -1223,5 +1222,10 @@ public:
 	}
 };
 
+struct autoMelderAsynchronous {
+	autoMelderAsynchronous () { Melder_asynchronous = true; }
+	~autoMelderAsynchronous () { Melder_asynchronous = false; }
+};
+
 /* End of file melder.h */
 #endif
diff --git a/sys/melder_audio.cpp b/sys/melder_audio.cpp
index 0affecc..052222b 100644
--- a/sys/melder_audio.cpp
+++ b/sys/melder_audio.cpp
@@ -1,6 +1,6 @@
 /* melder_audio.cpp
  *
- * Copyright (C) 1992-2011,2012,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2012,2013,2014 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
@@ -42,13 +42,6 @@
  * pb 2011/04/05 C++
  */
 
-#include "melder.h"
-#include "Gui.h"
-#include "Preferences.h"
-#include "NUM.h"
-#include <time.h>
-#define my  me ->
-
 #if defined (macintosh)
 	#include <sys/time.h>
 	#include <math.h>
@@ -71,11 +64,18 @@
 	#include <errno.h>
 #endif
 
+#include "melder.h"
+#include "Gui.h"
+#include "Preferences.h"
+#include "NUM.h"
+#include <time.h>
+#define my  me ->
+
 #include "../external/portaudio/portaudio.h"
 
 static struct {
 	enum kMelder_asynchronicityLevel maximumAsynchronicity;
-	bool useInternalSpeaker, inputUsesPortAudio, outputUsesPortAudio, outputUsesBlocking;
+	bool useInternalSpeaker, inputUsesPortAudio, outputUsesPortAudio;
 	double silenceBefore, silenceAfter;
 } preferences;
 
@@ -83,7 +83,6 @@ void Melder_audio_prefs (void) {
 	Preferences_addEnum (L"Audio.maximumAsynchronicity", & preferences. maximumAsynchronicity, kMelder_asynchronicityLevel, kMelder_asynchronicityLevel_DEFAULT);
 	Preferences_addBool (L"Audio.useInternalSpeaker", & preferences. useInternalSpeaker, true);
 	Preferences_addBool (L"Audio.outputUsesPortAudio2", & preferences. outputUsesPortAudio, kMelderAudio_outputUsesPortAudio_DEFAULT);
-	Preferences_addBool (L"Audio.outputUsesBlocking2", & preferences. outputUsesBlocking, false);
 	Preferences_addDouble (L"Audio.silenceBefore", & preferences. silenceBefore, kMelderAudio_outputSilenceBefore_DEFAULT);
 	Preferences_addDouble (L"Audio.silenceAfter", & preferences. silenceAfter, kMelderAudio_outputSilenceAfter_DEFAULT);
 	Preferences_addBool (L"Audio.inputUsesPortAudio2", & preferences. inputUsesPortAudio, kMelderAudio_inputUsesPortAudio_DEFAULT);
@@ -106,12 +105,6 @@ void MelderAudio_setOutputUsesPortAudio (bool outputUsesPortAudio) {
 }
 bool MelderAudio_getOutputUsesPortAudio (void) { return preferences. outputUsesPortAudio; }
 
-void MelderAudio_setOutputUsesBlocking (bool outputUsesBlocking) {
-	MelderAudio_stopPlaying (MelderAudio_IMPLICIT);
-	preferences. outputUsesBlocking = outputUsesBlocking;
-}
-bool MelderAudio_getOutputUsesBlocking (void) { return preferences. outputUsesBlocking; }
-
 void MelderAudio_setUseInternalSpeaker (bool useInternalSpeaker) {
 	MelderAudio_stopPlaying (MelderAudio_IMPLICIT);
 	preferences. useInternalSpeaker = useInternalSpeaker;
@@ -148,19 +141,22 @@ bool MelderAudio_isPlaying;
 static double theStartingTime = 0.0;
 
 static struct MelderPlay {
-	const int16_t *buffer;
+	int16_t *buffer;
 	long sampleRate, numberOfSamples, samplesLeft, samplesSent, samplesPlayed;
 	unsigned int asynchronicity;
 	int numberOfChannels;
 	bool explicitStop, fakeMono;
+	volatile int volatile_interrupted;
 	bool (*callback) (void *closure, long samplesPlayed);
 	void *closure;
-	#if motif
+	#if cocoa
+		CFRunLoopTimerRef cocoaTimer;
+	#elif motif
 		XtWorkProcId workProcId_motif;
 	#elif gtk
 		gint workProcId_gtk;
 	#endif
-	bool usePortAudio, blocking, supports_paComplete;
+	bool usePortAudio, supports_paComplete;
 	PaStream *stream;
 	double paStartingTime;
 	#if defined (macintosh)
@@ -191,7 +187,99 @@ bool MelderAudio_stopWasExplicit (void) {
 static bool flush (void) {
 	struct MelderPlay *me = & thePlay;
 	if (my usePortAudio) {
-		if (my stream != NULL) Pa_CloseStream (my stream), my stream = NULL;
+		if (my stream != NULL) {
+			#ifdef linux
+
+				Pa_Sleep (200);   // this reduces the chance of seeing the Alsa/PulseAudio deadlock:
+				/*
+					(gdb) thread apply all bt
+
+					Thread 13 (Thread 0x7fffde1d2700 (LWP 25620)):
+					#0  0x00007ffff65a3d67 in pthread_cond_wait@@GLIBC_2.3.2 ()
+					   from /lib/x86_64-linux-gnu/libpthread.so.0
+					#1  0x00007fffec0b3980 in pa_threaded_mainloop_wait () from /usr/lib/x86_64-linux-gnu/libpulse.so.0
+					#2  0x00007fffde407054 in pulse_wait_operation ()
+					   from /usr/lib/x86_64-linux-gnu/alsa-lib/libasound_module_pcm_pulse.so
+					#3  0x00007fffde405c10 in ?? ()
+					   from /usr/lib/x86_64-linux-gnu/alsa-lib/libasound_module_pcm_pulse.so
+					#4  0x00007ffff6843708 in alsa_snd_pcm_drop () from /usr/lib/x86_64-linux-gnu/libasound.so.2
+					#5  0x0000000000812de0 in AlsaStop ()
+					#6  0x00000000008183e1 in OnExit ()
+					#7  0x0000000000818483 in CallbackThreadFunc ()
+					#8  0x00007ffff659fe9a in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
+					#9  0x00007ffff5aba3fd in clone () from /lib/x86_64-linux-gnu/libc.so.6
+					#10 0x0000000000000000 in ?? ()
+
+					Thread 12 (Thread 0x7fffdffff700 (LWP 25619)):
+					#0  0x00007ffff659d9b0 in __pthread_mutex_lock_full () from /lib/x86_64-linux-gnu/libpthread.so.0
+					#1  0x00007fffdf3d7e1e in pa_mutex_lock () from /usr/lib/x86_64-linux-gnu/libpulsecommon-1.1.so
+					#2  0x00007fffec0b3369 in ?? () from /usr/lib/x86_64-linux-gnu/libpulse.so.0
+					#3  0x00007fffec0a476c in pa_mainloop_poll () from /usr/lib/x86_64-linux-gnu/libpulse.so.0
+					#4  0x00007fffec0a4dd9 in pa_mainloop_iterate () from /usr/lib/x86_64-linux-gnu/libpulse.so.0
+					#5  0x00007fffec0a4e90 in pa_mainloop_run () from /usr/lib/x86_64-linux-gnu/libpulse.so.0
+					#6  0x00007fffec0b330f in ?? () from /usr/lib/x86_64-linux-gnu/libpulse.so.0
+					#7  0x00007fffdf3d8d18 in ?? () from /usr/lib/x86_64-linux-gnu/libpulsecommon-1.1.so
+					#8  0x00007ffff659fe9a in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
+					#9  0x00007ffff5aba3fd in clone () from /lib/x86_64-linux-gnu/libc.so.6
+					#10 0x0000000000000000 in ?? ()
+
+					Thread 3 (Thread 0x7fffefd8b700 (LWP 25610)):
+					#0  0x00007ffff5aaea43 in poll () from /lib/x86_64-linux-gnu/libc.so.6
+					#1  0x00007ffff6ae9ff6 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#2  0x00007ffff6aea45a in g_main_loop_run () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#3  0x00007ffff4bb75e6 in ?? () from /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
+					#4  0x00007ffff6b0b9b5 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#5  0x00007ffff659fe9a in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
+					---Type <return> to continue, or q <return> to quit---
+					#6  0x00007ffff5aba3fd in clone () from /lib/x86_64-linux-gnu/libc.so.6
+					#7  0x0000000000000000 in ?? ()
+
+					Thread 2 (Thread 0x7ffff058c700 (LWP 25609)):
+					#0  0x00007ffff5aaea43 in poll () from /lib/x86_64-linux-gnu/libc.so.6
+					#1  0x00007ffff6ae9ff6 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#2  0x00007ffff6aea45a in g_main_loop_run () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#3  0x00007ffff059698b in ?? () from /usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so
+					#4  0x00007ffff6b0b9b5 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#5  0x00007ffff659fe9a in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
+					#6  0x00007ffff5aba3fd in clone () from /lib/x86_64-linux-gnu/libc.so.6
+					#7  0x0000000000000000 in ?? ()
+
+					Thread 1 (Thread 0x7ffff7fce940 (LWP 25608)):
+					#0  0x00007ffff65a1148 in pthread_join () from /lib/x86_64-linux-gnu/libpthread.so.0
+					#1  0x000000000081073e in PaUnixThread_Terminate ()
+					#2  0x0000000000818239 in RealStop ()
+					#3  0x00000000008182c7 in AbortStream ()
+					#4  0x0000000000811ce5 in Pa_CloseStream ()
+					#5  0x0000000000753a6d in flush ()
+					#6  0x0000000000753dae in MelderAudio_stopPlaying ()
+					#7  0x00000000004d80e1 in Sound_playPart ()
+					#8  0x00000000004f7b48 in structSoundEditor::v_play ()
+					#9  0x00000000004e7197 in gui_drawingarea_cb_click ()
+					#10 0x00000000007d0d35 in _GuiGtkDrawingArea_clickCallback ()
+					#11 0x00007ffff78d6e78 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
+					#12 0x00007ffff6da6ca2 in g_closure_invoke () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+					#13 0x00007ffff6db7d71 in ?? () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+					#14 0x00007ffff6dbfd4e in g_signal_emit_valist ()
+					   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+					#15 0x00007ffff6dc0212 in g_signal_emit () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+					#16 0x00007ffff79f1231 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
+					#17 0x00007ffff78d5003 in gtk_propagate_event () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
+					#18 0x00007ffff78d5363 in gtk_main_do_event () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
+					#19 0x00007ffff7549cac in ?? () from /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0
+					#20 0x00007ffff6ae9d13 in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#21 0x00007ffff6aea060 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					---Type <return> to continue, or q <return> to quit---
+					#22 0x00007ffff6aea45a in g_main_loop_run () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
+					#23 0x00007ffff78d4397 in gtk_main () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
+					#24 0x00000000007909eb in praat_run ()
+					#25 0x000000000040e009 in main ()
+
+					Also see http://sourceforge.net/p/audacity/mailman/audacity-devel/thread/200912181409.49839.businessmanprogrammersteve@gmail.com/
+				*/
+			#endif
+			Pa_CloseStream (my stream);
+			my stream = NULL;
+		}
 	} else {
 	#if defined (macintosh)
 	#elif defined (linux)
@@ -244,12 +332,14 @@ bool MelderAudio_stopPlaying (bool explicitStop) {
 	struct MelderPlay *me = & thePlay;
 	my explicitStop = explicitStop;
 	if (! MelderAudio_isPlaying || my asynchronicity < kMelder_asynchronicityLevel_ASYNCHRONOUS) return false;
-	(void) flush ();
-	#if motif
+	#if cocoa
+		CFRunLoopRemoveTimer (CFRunLoopGetCurrent (), thePlay. cocoaTimer, kCFRunLoopCommonModes);
+	#elif motif
 		XtRemoveWorkProc (thePlay. workProcId_motif);
 	#elif gtk
-		gtk_idle_remove (thePlay. workProcId_gtk);
+		g_source_remove (thePlay. workProcId_gtk);
 	#endif
+	(void) flush ();
 	return true;
 }
 
@@ -259,33 +349,33 @@ static bool workProc (void *closure) {
 //n ++;
 //Melder_casual("workProc %ld", n);
 	if (my usePortAudio) {
-		if (my blocking) {
-			if (my samplesLeft > 0) {
-				int dsamples = my samplesLeft > 2000 ? 2000 : my samplesLeft;
-				Pa_WriteStream (my stream, (void *) & my buffer [my samplesSent * my numberOfChannels], dsamples);
-				my samplesLeft -= dsamples;
-				my samplesSent += dsamples;
-				my samplesPlayed = (Melder_clock () - theStartingTime - Pa_GetStreamInfo (my stream) -> outputLatency) * my sampleRate;
+		#if defined (linux)
+			double timeElapsed = Melder_clock () - theStartingTime - Pa_GetStreamInfo (my stream) -> outputLatency;
+			long samplesPlayed = timeElapsed * my sampleRate;
+			if (my callback && ! my callback (my closure, samplesPlayed)) {
+				my volatile_interrupted = 1;
+				return flush ();
+			}
+			if (my samplesLeft == 0) {
+				return flush ();
+			}
+		#elif defined (linuxXXX)
+			/*
+			 * Not all hostApis support paComplete or wait till all buffers have been played in Pa_StopStream.
+			 * Once pa_win_ds implements this, we can simply do:
+			 */
+			if (Pa_IsStreamActive (my stream)) {
 				if (my callback && ! my callback (my closure, my samplesPlayed))
 					return flush ();
-			} else /*if (my samplesPlayed >= my numberOfSamples)*/ {
+			} else {
 				Pa_StopStream (my stream);
 				my samplesPlayed = my numberOfSamples;
 				return flush ();
 			}
-		} else {
 			/*
-			 * Not all hostApis support paComplete or wait till all buffers have been played in Pa_StopStream.
-			 * Once pa_win_ds implements this, we can simply do:
-			 * if (Pa_IsStreamActive (my stream)) {
-			 *    if (my callback && ! my callback (my closure, my samplesPlayed))
-			 *       return flush ();
-			 * } else {
-			 *    my samplesPlayed = my numberOfSamples;
-			 *    return flush ();
-			 * }
 			 * But then we also have to use paComplete in the stream callback.
 			 */
+		#else
 			double timeElapsed = Melder_clock () - theStartingTime - Pa_GetStreamInfo (my stream) -> outputLatency;
 			my samplesPlayed = timeElapsed * my sampleRate;
 			if (my supports_paComplete && Pa_IsStreamActive (my stream)) {
@@ -304,7 +394,7 @@ static bool workProc (void *closure) {
 				return flush ();
 			}
 			Pa_Sleep (10);
-		}
+		#endif
 	} else {
 	#if defined (macintosh)
 	#elif defined (linux)
@@ -352,7 +442,16 @@ static bool workProc (void *closure) {
 	(void) closure;
 	return false;
 }
-#if motif
+#if cocoa
+static void workProc_cocoa (CFRunLoopTimerRef timer, void *closure) {
+	bool result = workProc (closure);
+	if (result) {
+		CFRunLoopTimerInvalidate (timer);
+		//CFRunLoopRemoveTimer (CFRunLoopGetCurrent (), timer);
+		
+	}
+}
+#elif motif
 static bool workProc_motif (XtPointer closure) {
 	return workProc ((void *) closure);
 }
@@ -362,48 +461,6 @@ static gint workProc_gtk (gpointer closure) {
 }
 #endif
 
-#if defined (macintosh)
-# define FloatToUnsigned(f)  \
-	 ((unsigned long)(((long)((f) - 2147483648.0)) + 2147483647L + 1))
-static void double2real10 (double x, unsigned char *bytes) {
-	int sign, exponent;
-	double fMantissa, fsMantissa;
-	unsigned long highMantissa, lowMantissa;
-	if (x < 0.0) { sign = 0x8000; x *= -1; }
-	else sign = 0;
-	if (x == 0.0) { exponent = 0; highMantissa = 0; lowMantissa = 0; }
-	else {
-		fMantissa = frexp (x, & exponent);
-		if ((exponent > 16384) || ! (fMantissa < 1))   /* Infinity or Not-a-Number. */
-			{ exponent = sign | 0x7FFF; highMantissa = 0; lowMantissa = 0; }   /* Infinity. */
-		else {   /* Finite */
-			exponent += 16382;   /* Add bias. */
-			if (exponent < 0) {   /* Denormalized. */
-				fMantissa = ldexp (fMantissa, exponent);
-				exponent = 0;
-			}
-			exponent |= sign;
-			fMantissa = ldexp (fMantissa, 32);          
-			fsMantissa = floor (fMantissa); 
-			highMantissa = FloatToUnsigned (fsMantissa);
-			fMantissa = ldexp (fMantissa - fsMantissa, 32); 
-			fsMantissa = floor (fMantissa); 
-			lowMantissa = FloatToUnsigned (fsMantissa);
-		}
-	}
-	bytes [0] = exponent >> 8;
-	bytes [1] = exponent;
-	bytes [2] = highMantissa >> 24;
-	bytes [3] = highMantissa >> 16;
-	bytes [4] = highMantissa >> 8;
-	bytes [5] = highMantissa;
-	bytes [6] = lowMantissa >> 24;
-	bytes [7] = lowMantissa >> 16;
-	bytes [8] = lowMantissa >> 8;
-	bytes [9] = lowMantissa;
-}
-#endif
-
 static int thePaStreamCallback (const void *input, void *output,
 	unsigned long frameCount,
 	const PaStreamCallbackTimeInfo* timeInfo,
@@ -414,6 +471,11 @@ static int thePaStreamCallback (const void *input, void *output,
 	(void) timeInfo;
 	(void) userData;
 	struct MelderPlay *me = & thePlay;
+	if (my volatile_interrupted) {
+		memset (output, '\0', 2 * frameCount * my numberOfChannels);
+		my samplesPlayed = my numberOfSamples;
+		return my supports_paComplete ? paComplete : paContinue;
+	}
 	if (statusFlags & paOutputUnderflow) {
 		if (Melder_debug == 20) Melder_casual ("output underflow");
 	}
@@ -425,6 +487,7 @@ static int thePaStreamCallback (const void *input, void *output,
 		if (Melder_debug == 20) Melder_casual ("play %ls %ls", Melder_integer (dsamples),
 			Melder_double (Pa_GetStreamCpuLoad (my stream)));
 		memset (output, '\0', 2 * frameCount * my numberOfChannels);
+		Melder_assert (my buffer != NULL);
 		memcpy (output, (char *) & my buffer [my samplesSent * my numberOfChannels], 2 * dsamples * my numberOfChannels);
 		my samplesLeft -= dsamples;
 		my samplesSent += dsamples;
@@ -432,12 +495,13 @@ static int thePaStreamCallback (const void *input, void *output,
 	} else /*if (my samplesPlayed >= my numberOfSamples)*/ {
 		memset (output, '\0', 2 * frameCount * my numberOfChannels);
 		my samplesPlayed = my numberOfSamples;
+		trace ("paComplete");
 		return my supports_paComplete ? paComplete : paContinue;
 	}
 	return paContinue;
 }
 
-void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSamples, int numberOfChannels,
+void MelderAudio_play16 (int16_t *buffer, long sampleRate, long numberOfSamples, int numberOfChannels,
 	bool (*playCallback) (void *playClosure, long numberOfSamplesPlayed), void *playClosure)
 {
 	struct MelderPlay *me = & thePlay;
@@ -453,14 +517,15 @@ void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSa
 	my closure = playClosure;
 	my asynchronicity =
 		Melder_batch ? kMelder_asynchronicityLevel_SYNCHRONOUS :
-		Melder_backgrounding ? kMelder_asynchronicityLevel_INTERRUPTABLE :
+		(Melder_backgrounding && ! Melder_asynchronous) ? kMelder_asynchronicityLevel_INTERRUPTABLE :
 		kMelder_asynchronicityLevel_ASYNCHRONOUS;
 	if (my asynchronicity > preferences. maximumAsynchronicity)
 		my asynchronicity = preferences. maximumAsynchronicity;
+	trace ("asynchronicity %d", (int) my asynchronicity);
 	my usePortAudio = preferences. outputUsesPortAudio;
-	my blocking = preferences. outputUsesBlocking;
 	my explicitStop = MelderAudio_IMPLICIT;
 	my fakeMono = false;
+	my volatile_interrupted = 0;
 
 	my samplesLeft = numberOfSamples;
 	my samplesSent = 0;
@@ -474,152 +539,137 @@ void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSa
 			if (err) Melder_fatal ("PortAudio does not initialize: %s", Pa_GetErrorText (err));
 			paInitialized = true;
 		}
-		my supports_paComplete = Pa_GetHostApiInfo (Pa_GetDefaultHostApi ()) -> type != paDirectSound;
+		my supports_paComplete = Pa_GetHostApiInfo (Pa_GetDefaultHostApi ()) -> type != paDirectSound &&false;
 		PaStreamParameters outputParameters = { 0 };
 		outputParameters. device = Pa_GetDefaultOutputDevice ();
 		const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo (outputParameters. device);
+		if (my numberOfChannels > deviceInfo -> maxOutputChannels) {
+			my numberOfChannels = deviceInfo -> maxOutputChannels;
+		}
+		if (numberOfChannels > my numberOfChannels) {
+			/*
+			 * Redistribute the in channels over the out channels.
+			 */
+			if (numberOfChannels == 4 && my numberOfChannels == 2) {   // a common case
+				int16_t *in = & my buffer [0], *out = & my buffer [0];
+				for (long isamp = 1; isamp <= numberOfSamples; isamp ++) {
+					long in1 = *in ++, in2 = *in ++, in3 = *in ++, in4 = *in ++;
+					*out ++ = (in1 + in2) / 2;
+					*out ++ = (in3 + in4) / 2;
+				}
+			} else {
+				int16_t *in = & my buffer [0], *out = & my buffer [0];
+				for (long isamp = 1; isamp <= numberOfSamples; isamp ++) {
+					for (long iout = 1; iout <= my numberOfChannels; iout ++) {
+						long outValue = 0;
+						long numberOfIn = numberOfChannels / my numberOfChannels;
+						if (iout == my numberOfChannels)
+							numberOfIn += numberOfChannels % my numberOfChannels;
+						for (long iin = 1; iin <= numberOfIn; iin ++)
+							outValue += *in ++;
+						outValue /= numberOfIn;
+						*out ++ = outValue;
+					}
+				}
+			}
+		}
 		outputParameters. channelCount = my numberOfChannels;
 		outputParameters. sampleFormat = paInt16;
 		if (deviceInfo != NULL) outputParameters. suggestedLatency = deviceInfo -> defaultLowOutputLatency;
 		outputParameters. hostApiSpecificStreamInfo = NULL;
 		err = Pa_OpenStream (& my stream, NULL, & outputParameters, my sampleRate, paFramesPerBufferUnspecified,
-			paDitherOff, my blocking ? NULL : thePaStreamCallback, me);
+			paDitherOff, thePaStreamCallback, me);
 		if (err) Melder_throw ("PortAudio cannot open sound output: ", Pa_GetErrorText (err), ".");
 		theStartingTime = Melder_clock ();
-		if (my blocking) {
-			err = Pa_StartStream (my stream);
-			if (err) Melder_throw ("PortAudio cannot start sound output: ", Pa_GetErrorText (err), ".");
-			if (my asynchronicity == kMelder_asynchronicityLevel_SYNCHRONOUS) {
-				Pa_WriteStream (my stream, buffer, numberOfSamples);
-				Pa_StopStream (my stream);
-				my samplesPlayed = my numberOfSamples;
-			} else if (my asynchronicity <= kMelder_asynchronicityLevel_INTERRUPTABLE) {
-				bool interrupted = false;
-				while (my samplesLeft > 0 && ! interrupted) {
-					long numberOfWriteSamplesAvailableWithoutBlocking = Pa_GetStreamWriteAvailable (my stream);
-					if (Melder_debug == 20)
-						Melder_casual ("Pa_GetStreamWriteAvailable: %ld", numberOfWriteSamplesAvailableWithoutBlocking);
-					long maximumNumberOfSamplesToWrite = 1000;
-					long dsamples = my samplesLeft > maximumNumberOfSamplesToWrite ? maximumNumberOfSamplesToWrite : my samplesLeft;
-					Pa_WriteStream (my stream, (void *) & my buffer [my samplesSent * my numberOfChannels], dsamples);
-					my samplesLeft -= dsamples;
-					my samplesSent += dsamples;
-					my samplesPlayed = (Melder_clock () - theStartingTime - Pa_GetStreamInfo (my stream) -> outputLatency) * my sampleRate;
-					if (my callback && ! my callback (my closure, my samplesPlayed))
-						interrupted = true;
-					if (my asynchronicity == kMelder_asynchronicityLevel_INTERRUPTABLE && ! interrupted) {
-						#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;
-								}
-							}
-						#elif defined (_WIN32)
-							MSG event;
-							if (PeekMessage (& event, 0, 0, 0, PM_REMOVE) && event. message == WM_KEYDOWN) {
-								if (LOWORD (event. wParam) == VK_ESCAPE) {
-									my explicitStop = MelderAudio_EXPLICIT;
-									interrupted = true;
-								}
-							}
-						#endif
-					}				
-					if (interrupted) {
-						flush ();
-						return;
-					}
-				}
-				Pa_StopStream (my stream);
-			} else {
-				#if motif
-					my workProcId_motif = GuiAddWorkProc (workProc_motif, NULL);
-				#elif gtk
-					my workProcId_gtk = gtk_idle_add (workProc_gtk, NULL);
-				#endif
-				return;
-			}
-		} else {
-			err = Pa_StartStream (my stream);
-			if (err) Melder_throw ("PortAudio cannot start sound output: ", Pa_GetErrorText (err), ".");
-			my paStartingTime = Pa_GetStreamTime (my stream);
-			if (my asynchronicity <= kMelder_asynchronicityLevel_INTERRUPTABLE) {
-				for (;;) {
-					double timeElapsed = Melder_clock () - theStartingTime - Pa_GetStreamInfo (my stream) -> outputLatency;
-					my samplesPlayed = timeElapsed * my sampleRate;
-					if (my samplesPlayed >= my numberOfSamples + my sampleRate / 20) {
+		err = Pa_StartStream (my stream);
+		if (err) Melder_throw ("PortAudio cannot start sound output: ", Pa_GetErrorText (err), ".");
+		my paStartingTime = Pa_GetStreamTime (my stream);
+		if (my asynchronicity <= kMelder_asynchronicityLevel_INTERRUPTABLE) {
+			for (;;) {
+				#if defined (linux)
+					/*
+					 * This is how PortAudio was designed to work.
+					 */
+					if (my samplesLeft == 0) {
 						my samplesPlayed = my numberOfSamples;
 						break;
 					}
-					bool interrupted = false;
-					if (my asynchronicity != kMelder_asynchronicityLevel_SYNCHRONOUS && my callback &&
-						! my callback (my closure, my samplesPlayed))
-						interrupted = true;
+				#else
 					/*
-					 * Safe operation: only listen to key-down events.
-					 * Do this on the lowest level that will work.
+					 * A version that doesn't trust that the stream callback will complete.
 					 */
-					if (my asynchronicity == kMelder_asynchronicityLevel_INTERRUPTABLE && ! interrupted) {
-						#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;
-								}
+					double timeElapsed = Melder_clock () - theStartingTime - Pa_GetStreamInfo (my stream) -> outputLatency;
+					long samplesPlayed = timeElapsed * my sampleRate;
+					if (samplesPlayed >= my numberOfSamples + my sampleRate / 20) {
+						my samplesPlayed = my numberOfSamples;
+						break;
+					}
+				#endif
+				bool interrupted = false;
+				if (my asynchronicity != kMelder_asynchronicityLevel_SYNCHRONOUS && my callback &&
+					! my callback (my closure, my samplesPlayed))
+					interrupted = true;
+				/*
+				 * Safe operation: only listen to key-down events.
+				 * Do this on the lowest level that will work.
+				 */
+				if (my asynchronicity == kMelder_asynchronicityLevel_INTERRUPTABLE && ! interrupted) {
+					#if gtk
+						// TODO: implement a reaction to the Escape key
+					#elif cocoa
+						// TODO: implement a reaction to the Escape key
+					#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;
 							}
-						#elif defined (_WIN32)
-							MSG event;
-							if (PeekMessage (& event, 0, 0, 0, PM_REMOVE) && event. message == WM_KEYDOWN) {
-								if (LOWORD (event. wParam) == VK_ESCAPE) {
-									my explicitStop = MelderAudio_EXPLICIT;
-									interrupted = true;
-								}
+						}
+					#elif defined (_WIN32)
+						MSG event;
+						if (PeekMessage (& event, 0, 0, 0, PM_REMOVE) && event. message == WM_KEYDOWN) {
+							if (LOWORD (event. wParam) == VK_ESCAPE) {
+								my explicitStop = MelderAudio_EXPLICIT;
+								interrupted = true;
 							}
-						#endif
-					}
-					if (interrupted) {
-						flush ();
-						return;
-					}
-					Pa_Sleep (10);
+						}
+					#endif
 				}
-				if (my samplesPlayed != my numberOfSamples) {
-					Melder_fatal ("Played %ld instead of %ld samples.", my samplesPlayed, my numberOfSamples);
+				if (interrupted) {
+					flush ();
+					return;
 				}
-				Pa_AbortStream (my stream);
-			} else /* my asynchronicity == kMelder_asynchronicityLevel_ASYNCHRONOUS */ {
-				#if motif
-					my workProcId_motif = GuiAddWorkProc (workProc_motif, NULL);
-				#elif gtk
-					my workProcId_gtk = gtk_idle_add (workProc_gtk, NULL);
-				#endif
-				return;
+				Pa_Sleep (10);
+			}
+			if (my samplesPlayed != my numberOfSamples) {
+				Melder_fatal ("Played %ld instead of %ld samples.", my samplesPlayed, my numberOfSamples);
 			}
+			#ifndef linux
+				Pa_AbortStream (my stream);
+			#endif
+		} else /* my asynchronicity == kMelder_asynchronicityLevel_ASYNCHRONOUS */ {
+			#if cocoa
+				CFRunLoopTimerContext context = { 0, NULL, NULL, NULL, NULL };
+				my cocoaTimer = CFRunLoopTimerCreate (NULL, CFAbsoluteTimeGetCurrent () + 0.02,
+					0.02, 0, 0, workProc_cocoa, & context);
+				CFRunLoopAddTimer (CFRunLoopGetCurrent (), my cocoaTimer, kCFRunLoopCommonModes);
+			#elif motif
+				my workProcId_motif = GuiAddWorkProc (workProc_motif, NULL);
+			#elif gtk
+				my workProcId_gtk = g_idle_add (workProc_gtk, NULL);
+			#endif
+			return;
 		}
 		flush ();
 		return;
@@ -659,7 +709,7 @@ void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSa
 						for (long isamp = 0; isamp < numberOfSamples; isamp ++) {
 							newBuffer [isamp + isamp] = newBuffer [isamp + isamp + 1] = buffer [isamp];
 						}
-						my buffer = (const int16_t *) newBuffer;
+						my buffer = newBuffer;
 						my numberOfChannels = 2;
 					} else {
 						Melder_throw ("Cannot set number of channels to .", my numberOfChannels, ".");
@@ -697,7 +747,9 @@ void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSa
 						my samplesPlayed = my numberOfSamples;
 					}
 				} else /* my asynchronicity == kMelder_asynchronicityLevel_ASYNCHRONOUS */ {
-					my workProcId_gtk = gtk_idle_add (workProc_gtk, NULL);
+					#ifndef NO_GRAPHICS
+						my workProcId_gtk = g_idle_add (workProc_gtk, NULL);
+					#endif
 					return;
 				}
 				flush ();
@@ -803,9 +855,4 @@ void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSa
 	}
 }
 
-/********** WAITING FOR SOUND INPUT **********/
-
-void Melder_audioTrigger (void) {
-}
-
 /* End of file melder_audio.cpp */
diff --git a/sys/melder_audiofiles.cpp b/sys/melder_audiofiles.cpp
index 66ed421..6d6672e 100644
--- a/sys/melder_audiofiles.cpp
+++ b/sys/melder_audiofiles.cpp
@@ -61,6 +61,7 @@
 #define WAVE_FORMAT_ALAW  0x0006
 #define WAVE_FORMAT_MULAW  0x0007
 #define WAVE_FORMAT_DVI_ADPCM  0x0011
+#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
 
 void MelderFile_writeAudioFileHeader (MelderFile file, int audioFileType, long sampleRate, long numberOfSamples, int numberOfChannels, int numberOfBitsPerSamplePoint) {
 	try {
@@ -134,22 +135,35 @@ void MelderFile_writeAudioFileHeader (MelderFile file, int audioFileType, long s
 			} break;
 			case Melder_WAV: {
 				try {
+					bool needExtensibleFormat =
+						numberOfBitsPerSamplePoint > 16 ||
+						numberOfChannels > 2 ||
+						numberOfBitsPerSamplePoint != numberOfBytesPerSamplePoint * 8;
+					const int formatSize = needExtensibleFormat ? 40 : 16;
 					long dataSize = numberOfSamples * numberOfBytesPerSamplePoint * numberOfChannels;
 
 					/* RIFF Chunk: contains all other chunks. */
 					if (fwrite ("RIFF", 1, 4, f) != 4) Melder_throw ("Error in file while trying to write the RIFF statement.");
-					binputi4LE (4 + (12 + 16) + (4 + dataSize), f);
+					binputi4LE (4 + (12 + formatSize) + (4 + dataSize), f);
 					if (fwrite ("WAVE", 1, 4, f) != 4) Melder_throw ("Error in file while trying to write the WAV file type.");
 
-					/* Format Chunk: 8 + 16 bytes. */
+					/* Format Chunk: if 16-bits audio, then 8 + 16 bytes; else 8 + 40 bytes. */
 					if (fwrite ("fmt ", 1, 4, f) != 4) Melder_throw ("Error in file while trying to write the FMT statement.");
-					binputi4LE (16, f);
-					binputi2LE (WAVE_FORMAT_PCM, f);
+					binputi4LE (formatSize, f);
+					binputi2LE (needExtensibleFormat ? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM, f);
 					binputi2LE (numberOfChannels, f);
 					binputi4LE (sampleRate, f);   // number of samples per second
 					binputi4LE (sampleRate * numberOfBytesPerSamplePoint * numberOfChannels, f);   // average number of bytes per second
 					binputi2LE (numberOfBytesPerSamplePoint * numberOfChannels, f);   // block alignment
-					binputi2LE (numberOfBitsPerSamplePoint, f);
+					binputi2LE (numberOfBytesPerSamplePoint * 8, f);   // padded bits per sample
+					if (needExtensibleFormat) {
+						binputi2LE (22, f);   // extensionSize
+						binputi2LE (numberOfBitsPerSamplePoint, f);   // valid bits per sample
+						binputi4LE (0, f);   // speaker position mask
+						binputi2LE (WAVE_FORMAT_PCM, f);
+						if (fwrite ("\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71",
+							1, 14, f) != 14) Melder_throw ("Error in file while trying to write the subformat.");
+					}
 
 					/* Data Chunk: 8 bytes + samples. */
 					if (fwrite ("data", 1, 4, f) != 4) Melder_throw ("Error in file while trying to write the DATA statement.");
@@ -440,7 +454,7 @@ static void Melder_checkAiffFile (FILE *f, int *numberOfChannels, int *encoding,
 static void Melder_checkWavFile (FILE *f, int *numberOfChannels, int *encoding,
 	double *sampleRate, long *startOfData, long *numberOfSamples)
 {
-	char data [8], chunkID [4];
+	char data [14], chunkID [4];
 	bool formatChunkPresent = false, dataChunkPresent = false;
 	int numberOfBitsPerSamplePoint = -1;
 	long dataChunkSize = -1;
@@ -464,7 +478,7 @@ static void Melder_checkWavFile (FILE *f, int *numberOfChannels, int *encoding,
 			/*
 			 * Found a Format Chunk.
 			 */
-			int winEncoding = bingeti2LE (f);
+			uint16_t winEncoding = bingetu2LE (f);
 			formatChunkPresent = true;			
 			*numberOfChannels = bingeti2LE (f);
 			if (*numberOfChannels < 1) Melder_throw ("Too few sound channels (", *numberOfChannels, ").");
@@ -501,8 +515,43 @@ static void Melder_checkWavFile (FILE *f, int *numberOfChannels, int *encoding,
 						"Please use uncompressed audio files. If you must open this file,\n"
 						"please use an audio converter program to convert it first to normal (PCM) WAV format\n"
 						"(Praat may have difficulty analysing the poor recording, though).");
+				case WAVE_FORMAT_EXTENSIBLE: {
+					if (chunkSize < 40)
+						Melder_throw ("Not enough format data in extensible WAV format");
+					(void) bingeti2LE (f);   // extensionSize
+					(void) bingeti2LE (f);   // validBitsPerSample
+					(void) bingeti4LE (f);   // channelMask
+					uint16_t winEncoding2 = bingetu2LE (f);   // override
+					switch (winEncoding2) {
+						case WAVE_FORMAT_PCM:
+							*encoding =
+								numberOfBitsPerSamplePoint > 24 ? Melder_LINEAR_32_LITTLE_ENDIAN :
+								numberOfBitsPerSamplePoint > 16 ? Melder_LINEAR_24_LITTLE_ENDIAN :
+								numberOfBitsPerSamplePoint > 8 ? Melder_LINEAR_16_LITTLE_ENDIAN :
+								Melder_LINEAR_8_UNSIGNED;
+							break;
+						case WAVE_FORMAT_IEEE_FLOAT:
+							*encoding = Melder_IEEE_FLOAT_32_LITTLE_ENDIAN;
+							break;
+						case WAVE_FORMAT_ALAW:
+							*encoding = Melder_ALAW;
+							break;
+						case WAVE_FORMAT_MULAW:
+							*encoding = Melder_MULAW;
+							break;
+						case WAVE_FORMAT_DVI_ADPCM:
+							Melder_throw ("Cannot read lossy compressed audio files (this one is DVI ADPCM).\n"
+								"Please use uncompressed audio files. If you must open this file,\n"
+								"please use an audio converter program to convert it first to normal (PCM) WAV format\n"
+								"(Praat may have difficulty analysing the poor recording, though).");
+						default:
+							Melder_throw ("Unsupported Windows audio encoding ", winEncoding2, ".");
+					}
+					if (fread (data, 1, 14, f) < 14)   Melder_throw ("File too small: no SubFormat data.");
+					continue;   // next chunk
+				}
 				default:
-					Melder_throw ("Unsupported Windows audio encoding %d.", winEncoding);
+					Melder_throw ("Unsupported Windows audio encoding ", winEncoding, ".");
 			}
 			if (chunkSize & 1) chunkSize ++;
 			for (long i = 17; i <= chunkSize; i ++)
diff --git a/sys/melder_debug.cpp b/sys/melder_debug.cpp
index 05229f3..ac78833 100644
--- a/sys/melder_debug.cpp
+++ b/sys/melder_debug.cpp
@@ -1,6 +1,6 @@
 /* melder_debug.cpp
  *
- * Copyright (C) 2000-2012 Paul Boersma
+ * Copyright (C) 2000-2012,2014 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,8 +17,11 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+#include "melder.h"
+#ifdef linux
+	#include "GuiP.h"
+#endif
 #include <time.h>
-#include "GuiP.h"
 #include "praat_version.h"
 
 int Melder_debug = 0;
@@ -70,6 +73,8 @@ the behaviour of that program changes in the following way:
 44: trace Collection
 45: tracing structMatrix :: read ()
 46: trace GTK parent sizes in _GuiObject_position ()
+47: force resampling in OTGrammar RIP
+900: use DG Meta Serif Science instead of Palatino
 1264: Mac: Sound_recordFixedTime uses microphone "FW Solo (1264)"
 
 (negative values are for David)
@@ -79,7 +84,7 @@ the behaviour of that program changes in the following way:
 static bool theTracing = false;
 static structMelderFile theTracingFile = { 0 };
 
-#if gtk
+#ifdef linux
 static void theGtkLogHandler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer unused_data) {
 	Melder_trace_ (NULL, 0, "GTK", "%s", message);
 }
@@ -98,7 +103,7 @@ void Melder_setTracing (bool tracing) {
 	if (! tracing)
 		trace ("switch tracing off in Praat version %s at %s", xstr (PRAAT_VERSION_STR), ctime (& today));
 	theTracing = tracing;
-	#if gtk
+	#ifdef linux
 		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,         NULL);
@@ -142,7 +147,7 @@ void Melder_trace_ (const char *fileName, int lineNumber, const char *functionNa
 		fprintf (f, strchr (".!?,;", lastCharacter) ? "\n" : ".\n");
 		Melder_fclose (& theTracingFile, f);
 	} catch (MelderError) {
-		// ignore
+		Melder_clearError ();   // ignore
 	}
 }
 
diff --git a/sys/melder_error.cpp b/sys/melder_error.cpp
index 6577712..445e8f0 100644
--- a/sys/melder_error.cpp
+++ b/sys/melder_error.cpp
@@ -88,6 +88,11 @@ void Melder_flushError (const char *format, ...) {
 	va_end (arg);
 }
 
+void Melder_error_noLine (const MelderArg& arg1)
+{
+	if (arg1.argW) { if (arg1.type == 1) appendErrorW (arg1.argW); else appendErrorA (arg1.arg8); }
+}
+
 void Melder_error_ (const MelderArg& arg1)
 {
 	if (arg1.argW) { if (arg1.type == 1) appendErrorW (arg1.argW); else appendErrorA (arg1.arg8); }
diff --git a/sys/melder_files.cpp b/sys/melder_files.cpp
index fc8514d..4f6702a 100644
--- a/sys/melder_files.cpp
+++ b/sys/melder_files.cpp
@@ -1,6 +1,6 @@
 /* melder_files.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -66,9 +66,9 @@ using namespace std;
 	#include "macport_off.h"
 #endif
 #include <errno.h>
-#include "melder.h"
 #include "flac_FLAC_stream_encoder.h"
 #include "abcio.h"
+#include "melder.h"
 
 #if defined (macintosh)
 	#include <sys/stat.h>
@@ -235,6 +235,17 @@ void Melder_relativePathToFile (const wchar_t *path, MelderFile file) {
 		 *    LPT1:
 		 *    \\host\path
 		 */
+		structMelderDir dir = { { 0 } };
+		if (path [0] == '~' && path [1] == '/') {
+			Melder_getHomeDir (& dir);
+			swprintf (file -> path, kMelder_MAXPATH+1, L"%ls%ls", dir. path, & path [1]);
+			for (;;) {
+				wchar_t *slash = wcschr (file -> path, '/');
+				if (slash == NULL) break;
+				*slash = '\\';
+			}
+			return;
+		}
 		if (wcschr (path, '/') && ! wcsstr (path, L"://")) {
 			wchar_t winPath [kMelder_MAXPATH+1];
 			wcscpy (winPath, path);
@@ -249,7 +260,6 @@ void Melder_relativePathToFile (const wchar_t *path, MelderFile file) {
 		if (wcschr (path, ':') || path [0] == '\\' && path [1] == '\\' || wcsequ (path, L"<stdout>")) {
 			wcscpy (file -> path, path);
 		} else {
-			structMelderDir dir = { { 0 } };
 			Melder_getDefaultDir (& dir);   /* BUG */
 			static MelderString buffer = { 0 };
 			MelderString_empty (& buffer);
diff --git a/sys/melder_ftoa.cpp b/sys/melder_ftoa.cpp
index ba7f153..c0cc95f 100644
--- a/sys/melder_ftoa.cpp
+++ b/sys/melder_ftoa.cpp
@@ -1,6 +1,6 @@
 /* melder_ftoa.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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
@@ -31,6 +31,7 @@
  * pb 2008/01/06 Mac: use strtod instead of wcstod for speed
  * pb 2010/10/16 Melder_naturalLogarithm
  * pb 2011/04/05 C++
+ * pb 2014/01/09 use fabs in the calculating minimum precision
  */
 
 #include "melder.h"
@@ -163,7 +164,7 @@ const wchar_t * Melder_fixed (double value, int precision) {
 	if (value == 0.0) return L"0";
 	if (++ ibuffer == NUMBER_OF_BUFFERS) ibuffer = 0;
 	if (precision > 60) precision = 60;
-	minimumPrecision = - (int) floor (log10 (value));
+	minimumPrecision = - (int) floor (log10 (fabs (value)));
 	swprintf (buffers [ibuffer], MAXIMUM_NUMERIC_STRING_LENGTH, L"%.*f",
 		minimumPrecision > precision ? minimumPrecision : precision, value);
 	return buffers [ibuffer];
@@ -177,7 +178,7 @@ const wchar_t * Melder_fixedExponent (double value, int exponent, int precision)
 	if (++ ibuffer == NUMBER_OF_BUFFERS) ibuffer = 0;
 	if (precision > 60) precision = 60;
 	value /= factor;
-	minimumPrecision = - (int) floor (log10 (value));
+	minimumPrecision = - (int) floor (log10 (fabs (value)));
 	swprintf (buffers [ibuffer], MAXIMUM_NUMERIC_STRING_LENGTH, L"%.*fE%d",
 		minimumPrecision > precision ? minimumPrecision : precision, value, exponent);
 	return buffers [ibuffer];
@@ -190,7 +191,7 @@ const wchar_t * Melder_percent (double value, int precision) {
 	if (++ ibuffer == NUMBER_OF_BUFFERS) ibuffer = 0;
 	if (precision > 60) precision = 60;
 	value *= 100.0;
-	minimumPrecision = - (int) floor (log10 (value));
+	minimumPrecision = - (int) floor (log10 (fabs (value)));
 	swprintf (buffers [ibuffer], MAXIMUM_NUMERIC_STRING_LENGTH, L"%.*f%%",
 		minimumPrecision > precision ? minimumPrecision : precision, value);
 	return buffers [ibuffer];
@@ -245,4 +246,4 @@ const wchar_t * Melder_naturalLogarithm (double lnNumber) {
 	return buffers [ibuffer];
 }
 
-/* End of file melder_ftoa.cpp */
\ No newline at end of file
+/* End of file melder_ftoa.cpp */
diff --git a/sys/melder_info.cpp b/sys/melder_info.cpp
index 7cd4e45..c6e5941 100644
--- a/sys/melder_info.cpp
+++ b/sys/melder_info.cpp
@@ -38,67 +38,202 @@ void MelderInfo_open (void) {
 
 void MelderInfo_write (const wchar_t *s1) {
 	MelderString_append (theInfos, s1);
+	if (Melder_batch && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2) {
 	MelderString_append (theInfos, s1, s2);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3) {
 	MelderString_append (theInfos, s1, s2, s3);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4) {
 	MelderString_append (theInfos, s1, s2, s3, s4);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7, const wchar_t *s8) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7, s8);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+		Melder_writeToConsole (s8, false);
+	}
 }
 void MelderInfo_write (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7, const wchar_t *s8, const wchar_t *s9) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7, s8, s9);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+		Melder_writeToConsole (s8, false);
+		Melder_writeToConsole (s9, false);
+	}
 }
 
 void MelderInfo_writeLine (const wchar_t *s1) {
 	MelderString_append (theInfos, s1);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2) {
 	MelderString_append (theInfos, s1, s2);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3) {
 	MelderString_append (theInfos, s1, s2, s3);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4) {
 	MelderString_append (theInfos, s1, s2, s3, s4);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7, const wchar_t *s8) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7, s8);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+		Melder_writeToConsole (s8, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 void MelderInfo_writeLine (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7, const wchar_t *s8, const wchar_t *s9) {
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7, s8, s9);
 	MelderString_appendCharacter (theInfos, '\n');
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+		Melder_writeToConsole (s8, false);
+		Melder_writeToConsole (s9, false);
+		Melder_writeToConsole (L"\n", false);
+	}
 }
 
 void MelderInfo_close (void) {
@@ -111,14 +246,21 @@ void MelderInfo_close (void) {
 		*/
 		if (theInfos -> length == 0 || theInfos -> string [theInfos -> length - 1] != '\n') {   // Only if no newline there yet.
 			MelderString_appendCharacter (theInfos, '\n');
+			if (theInformation == defaultInformation) {
+				Melder_writeToConsole (L"\n", false);
+			}
+		}
+		if (theInformation != defaultInformation) {
+			theInformation (theInfos -> string ? theInfos -> string : L"");
 		}
-		theInformation (theInfos -> string ? theInfos -> string : L"");
 	}
 }
 
 void MelderInfo_drain (void) {
 	if (theInfos == & theForegroundBuffer) {
-		theInformation (theInfos -> string ? theInfos -> string : L"");
+		if (theInformation != defaultInformation) {
+			theInformation (theInfos -> string ? theInfos -> string : L"");
+		}
 	}
 }
 
@@ -140,7 +282,9 @@ void Melder_divertInfo (MelderString *buffer) {
 void Melder_clearInfo (void) {
 	if (theInfos == & theForegroundBuffer) {
 		MelderString_empty (theInfos);
-		theInformation (L"");
+		if (theInformation != defaultInformation) {
+			theInformation (L"");
+		}
 	}
 }
 
@@ -171,54 +315,117 @@ void Melder_print (const wchar_t *s) {
 void Melder_information (const wchar_t *s1) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2, s3);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2, s3, s4);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2, s3, s4, s5);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7, const wchar_t *s8) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7, s8);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+		Melder_writeToConsole (s8, false);
+	}
 	MelderInfo_close ();
 }
 
 void Melder_information (const wchar_t *s1, const wchar_t *s2, const wchar_t *s3, const wchar_t *s4, const wchar_t *s5, const wchar_t *s6, const wchar_t *s7, const wchar_t *s8, const wchar_t *s9) {
 	MelderString_empty (theInfos);
 	MelderString_append (theInfos, s1, s2, s3, s4, s5, s6, s7, s8, s9);
+	if (theInformation == defaultInformation && theInfos == & theForegroundBuffer) {
+		Melder_writeToConsole (s1, false);
+		Melder_writeToConsole (s2, false);
+		Melder_writeToConsole (s3, false);
+		Melder_writeToConsole (s4, false);
+		Melder_writeToConsole (s5, false);
+		Melder_writeToConsole (s6, false);
+		Melder_writeToConsole (s7, false);
+		Melder_writeToConsole (s8, false);
+		Melder_writeToConsole (s9, false);
+	}
 	MelderInfo_close ();
 }
 
diff --git a/sys/melder_time.cpp b/sys/melder_time.cpp
index 268694a..c09b4bc 100644
--- a/sys/melder_time.cpp
+++ b/sys/melder_time.cpp
@@ -1,6 +1,6 @@
 /* melder_time.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2014 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,11 +27,66 @@
 #include "melder.h"
 
 #if defined (macintosh) || defined (UNIX)
+	#include <time.h>
 	#include <sys/time.h>
 #elif defined (_WIN32)
 	#include <windows.h>
 #endif
 
+/*
+#include <assert.h>
+#include <CoreServices/CoreServices.h>
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <unistd.h>
+
+uint64_t GetPIDTimeInNanoseconds(void)
+{
+    uint64_t        start;
+    uint64_t        end;
+    uint64_t        elapsed;
+    uint64_t        elapsedNano;
+    static mach_timebase_info_data_t    sTimebaseInfo;
+
+    // Start the clock.
+
+    start = mach_absolute_time();
+
+    // Call getpid. This will produce inaccurate results because 
+    // we're only making a single system call. For more accurate 
+    // results you should call getpid multiple times and average 
+    // the results.
+
+    (void) getpid();
+
+    // Stop the clock.
+
+    end = mach_absolute_time();
+
+    // Calculate the duration.
+
+    elapsed = end - start;
+
+    // Convert to nanoseconds.
+
+    // If this is the first time we've run, get the timebase.
+    // We can use denom == 0 to indicate that sTimebaseInfo is 
+    // uninitialised because it makes no sense to have a zero 
+    // denominator is a fraction.
+
+    if ( sTimebaseInfo.denom == 0 ) {
+        (void) mach_timebase_info(&sTimebaseInfo);
+    }
+
+    // Do the maths. We hope that the multiplication doesn't 
+    // overflow; the price you pay for working in fixed point.
+
+    elapsedNano = elapsed * sTimebaseInfo.numer / sTimebaseInfo.denom;
+
+    return elapsedNano;
+}
+*/
+
 double Melder_clock (void) {
 	#if defined (macintosh) || defined (UNIX)
 		/*
diff --git a/sys/motifEmulator.cpp b/sys/motifEmulator.cpp
index 22b0c99..c4b918e 100644
--- a/sys/motifEmulator.cpp
+++ b/sys/motifEmulator.cpp
@@ -2882,11 +2882,31 @@ void GuiInitialize (const char *name, unsigned int *argc, char **argv)
 				 * this is especially likely to happen if the path contains spaces,
 				 * which on Windows XP is very usual.
 				 */
-				wchar_t *s = Melder_peekUtf8ToWcs (argv [3]);
-				Melder_relativePathToFile (s [0] == ' ' && s [1] == '\"' ? s + 2 : s [0] == '\"' ? s + 1 : s, & file);
-				long l = wcslen (file. path);
-				if (l > 0 && file. path [l - 1] == '\"') file. path [l - 1] = '\0';
-				theOpenDocumentCallback (& file);
+				wchar_t *s = Melder_utf8ToWcs (argv [3]);
+				for (;;) {
+					bool endSeen = false;
+					while (*s == ' ' || *s == '\n') s ++;
+					if (*s == '\0') break;
+					wchar_t *path = s;
+					if (*s == '\"') {
+						path = ++ s;
+						while (*s != '\"' && *s != '\0') s ++;
+						if (*s == '\0') break;
+						Melder_assert (*s == '\"');
+						*s = '\0';
+					} else {
+						while (*s != ' ' && *s != '\n' && *s != '\0') s ++;
+						if (*s == ' ' || *s == '\n') {
+							*s = '\0';
+						} else {
+							endSeen = true;
+						}
+					}
+					swprintf (file. path, 500, L"%ls", path);
+					theOpenDocumentCallback (& file);
+					if (endSeen) break;
+					s ++;
+				}
 			}
 			exit (0);   // possible problem
 		}
diff --git a/sys/praat.cpp b/sys/praat.cpp
index 4ad108e..a4fa132 100644
--- a/sys/praat.cpp
+++ b/sys/praat.cpp
@@ -1,6 +1,6 @@
 /* praat.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -502,9 +502,9 @@ static void gui_cb_list (void *void_me, GuiListEvent event) {
 			long readableClassId = ((Thing) theCurrentPraatObjects -> list [IOBJECT]. object) -> classInfo -> sequentialUniqueIdOfReadableClass;
 			theCurrentPraatObjects -> numberOfSelected [readableClassId] ++;
 			Melder_assert (theCurrentPraatObjects -> numberOfSelected [readableClassId] > 0);
-			UiHistory_write (first ? L"\nselectObject (\"" : L"\nplusObject (\"");
+			UiHistory_write (first ? L"\nselectObject: \"" : L"\nplusObject: \"");
 			UiHistory_write_expandQuotes (FULL_NAME);
-			UiHistory_write (L"\")");
+			UiHistory_write (L"\"");
 			first = FALSE;
 			theCurrentPraatObjects -> totalSelection += 1;
 		}
@@ -870,45 +870,49 @@ void praat_dontUsePictureWindow (void) { praatP.dontUsePictureWindow = TRUE; }
 			gboolean retval;
 			g_signal_emit_by_name (GTK_OBJECT (theCurrentPraatApplication -> topShell -> d_gtkWindow), "client-event", NULL, & retval);
 		#else
-			GdkEventClient gevent;
-			gevent. type = GDK_CLIENT_EVENT;
-			gevent. window = GTK_WIDGET (theCurrentPraatApplication -> topShell -> d_gtkWindow) -> window;
-			gevent. send_event = 1;
-			gevent. message_type = gdk_atom_intern_static_string ("SENDPRAAT");
-			gevent. data_format = 8;
-			// Melder_casual ("event put");
-			gdk_event_put ((GdkEvent *) & gevent);
+			#if ALLOW_GDK_DRAWING && ! defined (NO_GRAPHICS)
+				GdkEventClient gevent;
+				gevent. type = GDK_CLIENT_EVENT;
+				gevent. window = GTK_WIDGET (theCurrentPraatApplication -> topShell -> d_gtkWindow) -> window;
+				gevent. send_event = 1;
+				gevent. message_type = gdk_atom_intern_static_string ("SENDPRAAT");
+				gevent. data_format = 8;
+				// Melder_casual ("event put");
+				gdk_event_put ((GdkEvent *) & gevent);
+			#endif
 		#endif
 	}
 #endif
 
 #if defined (UNIX)
-	static gboolean cb_userMessage (GtkWidget widget, GdkEventClient *event, gpointer user_data) {
-		(void) widget;
-		(void) user_data;
-		//Melder_casual ("client event called");
-		autofile f;
-		try {
-			f.reset (Melder_fopen (& messageFile, "r"));
-		} catch (MelderError) {
-			Melder_clearError ();
-			return true;   // OK
-		}
-		long pid = 0;
-		int narg = fscanf (f, "#%ld", & pid);
-		f.close (& messageFile);
-		{// scope
-			autoPraatBackground background;
+	#if ALLOW_GDK_DRAWING && ! defined (NO_GRAPHICS)
+		static gboolean cb_userMessage (GtkWidget widget, GdkEventClient *event, gpointer user_data) {
+			(void) widget;
+			(void) user_data;
+			//Melder_casual ("client event called");
+			autofile f;
 			try {
-				praat_executeScriptFromFile (& messageFile, NULL);
+				f.reset (Melder_fopen (& messageFile, "r"));
 			} catch (MelderError) {
-				Melder_error_ (Melder_peekUtf8ToWcs (praatP.title), L": message not completely handled.");
-				Melder_flushError (NULL);
+				Melder_clearError ();
+				return true;   // OK
 			}
+			long pid = 0;
+			int narg = fscanf (f, "#%ld", & pid);
+			f.close (& messageFile);
+			{// scope
+				autoPraatBackground background;
+				try {
+					praat_executeScriptFromFile (& messageFile, NULL);
+				} catch (MelderError) {
+					Melder_error_ (Melder_peekUtf8ToWcs (praatP.title), L": message not completely handled.");
+					Melder_flushError (NULL);
+				}
+			}
+			if (narg && pid) kill (pid, SIGUSR2);
+			return true;
 		}
-		if (narg && pid) kill (pid, SIGUSR2);
-		return true;
-	}
+	#endif
 #elif defined (_WIN32)
 	static int cb_userMessage (void) {
 		autoPraatBackground background;
@@ -924,12 +928,92 @@ void praat_dontUsePictureWindow (void) { praatP.dontUsePictureWindow = TRUE; }
 	extern "C" wchar_t *sendpraatW (void *display, const wchar_t *programName, long timeOut, const wchar_t *text);
 	static void cb_openDocument (MelderFile file) {
 		wchar_t text [500];
-		wchar_t *s = file -> path;
-		swprintf (text, 500, L"Read from file... %ls", s [0] == ' ' && s [1] == '\"' ? s + 2 : s [0] == '\"' ? s + 1 : s);
-		long l = wcslen (text);
-		if (l > 0 && text [l - 1] == '\"') text [l - 1] = '\0';
+		/*
+		 * The user dropped a file on the Praat icon, while Praat is already running.
+		 * Windows may have enclosed the path between quotes;
+		 * this is especially likely to happen for a path that contains spaces.
+		 */
+		swprintf (text, 500, L"Read from file... %ls", file -> path);
 		sendpraatW (NULL, Melder_peekUtf8ToWcs (praatP.title), 0, text);
 	}
+#elif cocoa
+	static int (*theUserMessageCallbackA) (char *message);
+	static int (*theUserMessageCallbackW) (wchar_t *message);
+	static void mac_setUserMessageCallbackA (int (*userMessageCallback) (char *message)) {
+		theUserMessageCallbackA = userMessageCallback;
+	}
+	static void mac_setUserMessageCallbackW (int (*userMessageCallback) (wchar_t *message)) {
+		theUserMessageCallbackW = userMessageCallback;
+	}
+	static pascal OSErr mac_processSignalA (const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefCon) {
+		static int duringAppleEvent = FALSE;
+		(void) reply;
+		(void) handlerRefCon;
+		if (! duringAppleEvent) {
+			char *buffer;
+			long actualSize;
+			duringAppleEvent = TRUE;
+			//AEInteractWithUser (kNoTimeOut, NULL, NULL);   // use time out of 0 to execute immediately (without bringing to foreground)
+			ProcessSerialNumber psn;
+			GetCurrentProcess (& psn);
+			SetFrontProcess (& psn);
+			AEGetParamPtr (theAppleEvent, 1, typeChar, NULL, NULL, 0, & actualSize);
+			buffer = (char *) malloc (actualSize);
+			AEGetParamPtr (theAppleEvent, 1, typeChar, NULL, & buffer [0], actualSize, NULL);
+			if (theUserMessageCallbackA)
+				theUserMessageCallbackA (buffer);
+			free (buffer);
+			duringAppleEvent = FALSE;
+		}
+		return noErr;
+	}
+	static pascal OSErr mac_processSignalW (const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefCon) {
+		static int duringAppleEvent = FALSE;
+		(void) reply;
+		(void) handlerRefCon;
+		if (! duringAppleEvent) {
+			wchar_t *buffer;
+			long actualSize;
+			duringAppleEvent = TRUE;
+			//AEInteractWithUser (kNoTimeOut, NULL, NULL);   // use time out of 0 to execute immediately (without bringing to foreground)
+			ProcessSerialNumber psn;
+			GetCurrentProcess (& psn);
+			SetFrontProcess (& psn);
+			AEGetParamPtr (theAppleEvent, 1, typeUnicodeText, NULL, NULL, 0, & actualSize);
+			buffer = (wchar_t *) malloc (actualSize);
+			AEGetParamPtr (theAppleEvent, 1, typeUnicodeText, NULL, & buffer [0], actualSize, NULL);
+			if (theUserMessageCallbackW)
+				theUserMessageCallbackW (buffer);
+			free (buffer);
+			duringAppleEvent = FALSE;
+		}
+		return noErr;
+	}
+	static int cb_userMessageA (char *messageA) {
+		autoPraatBackground background;
+		autostring message = Melder_8bitToWcs (messageA, 0);
+		try {
+			praat_executeScriptFromText (message.peek());
+		} catch (MelderError) {
+			Melder_error_ (praatP.title, ": message not completely handled.");
+			Melder_flushError (NULL);
+		}
+		return 0;
+	}
+	static int cb_userMessageW (wchar_t *message) {
+		autoPraatBackground background;
+		try {
+			praat_executeScriptFromText (message);
+		} catch (MelderError) {
+			Melder_error_ (praatP.title, ": message not completely handled.");
+			Melder_flushError (NULL);
+		}
+		return 0;
+	}
+	static int cb_quitApplication (void) {
+		DO_Quit (NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+		return 0;
+	}
 #elif defined (macintosh)
 	static int cb_userMessageA (char *messageA) {
 		autoPraatBackground background;
@@ -985,6 +1069,7 @@ void praat_init (const char *title, unsigned int argc, char **argv) {
 	NUMmachar ();
 	NUMinit ();
 	Melder_alloc_init ();
+	Melder_message_init ();
 	/*
 		Remember the current directory. Only useful for scripts run from batch.
 	*/
@@ -995,6 +1080,7 @@ void praat_init (const char *title, unsigned int argc, char **argv) {
 	 */
 	praat_statistics_prefs ();   // Number of sessions, memory used...
 	praat_picture_prefs ();   // Font...
+	Graphics_prefs ();
 	structEditor     :: f_preferences ();   // Erase picture first...
 	structHyperPage  :: f_preferences ();   // Font...
 	Site_prefs ();   // Print command...
@@ -1164,6 +1250,8 @@ void praat_init (const char *title, unsigned int argc, char **argv) {
 			}
 		#else
 			if (! Melder_batch) {
+				mac_setUserMessageCallbackA (cb_userMessageA);
+				mac_setUserMessageCallbackW (cb_userMessageW);
 				Gui_setQuitApplicationCallback (cb_quitApplication);
 			}
 		#endif
@@ -1202,7 +1290,8 @@ void praat_init (const char *title, unsigned int argc, char **argv) {
 			g_set_application_name (title);
 			trace ("locale %s", setlocale (LC_ALL, NULL));
 		#elif cocoa
-			[NSApplication sharedApplication];
+			//[NSApplication sharedApplication];
+			[GuiCocoaApplication sharedApplication];
 		#elif defined (_WIN32)
 			argv [0] = & praatP. title [0];   /* argc == 4 */
 			Gui_setOpenDocumentCallback (cb_openDocument);
@@ -1218,7 +1307,7 @@ void praat_init (const char *title, unsigned int argc, char **argv) {
 		trace ("locale %s", setlocale (LC_ALL, NULL));
 		Gui_getWindowPositioningBounds (& x, & y, NULL, NULL);
 		trace ("locale %s", setlocale (LC_ALL, NULL));
-		theCurrentPraatApplication -> topShell = raam = GuiWindow_create (x + 10, y, WINDOW_WIDTH, WINDOW_HEIGHT,
+		theCurrentPraatApplication -> topShell = raam = GuiWindow_create (x + 10, y, WINDOW_WIDTH, WINDOW_HEIGHT, 450, 250,
 			Melder_peekUtf8ToWcs (objectWindowTitle), gui_cb_quit, NULL, 0);
 		trace ("locale %s", setlocale (LC_ALL, NULL));
 		#if motif
@@ -1238,6 +1327,10 @@ void praat_init (const char *title, unsigned int argc, char **argv) {
 	} else {
 
 		#ifdef macintosh
+			#if ! useCarbon
+				AEInstallEventHandler (758934755, 0, (AEEventHandlerProcPtr) (mac_processSignalA), 0, false);
+				AEInstallEventHandler (758934756, 0, (AEEventHandlerProcPtr) (mac_processSignalW), 0, false);
+			#endif
 			MelderGui_create (raam);   /* BUG: default Melder_assert would call printf recursively!!! */
 		#endif
 		#if defined (macintosh) && useCarbon
@@ -1261,10 +1354,14 @@ void praat_init (const char *title, unsigned int argc, char **argv) {
 		trace ("showing the Objects window");
 		raam -> f_show ();
 	//Melder_fatal ("stop");
-		#ifdef UNIX
+		#if defined (UNIX) && ! defined (NO_GRAPHICS)
 			try {
 				autofile f = Melder_fopen (& pidFile, "a");
-				fprintf (f, " %ld", (long) GDK_WINDOW_XID (GDK_DRAWABLE (GTK_WIDGET (theCurrentPraatApplication -> topShell -> d_gtkWindow) -> window)));
+				#if ALLOW_GDK_DRAWING
+					fprintf (f, " %ld", (long) GDK_WINDOW_XID (GDK_DRAWABLE (GTK_WIDGET (theCurrentPraatApplication -> topShell -> d_gtkWindow) -> window)));
+				#else
+					fprintf (f, " %ld", (long) GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (theCurrentPraatApplication -> topShell -> d_gtkWindow))));
+				#endif
 				f.close (& pidFile);
 			} catch (MelderError) {
 				Melder_clearError ();
@@ -1307,39 +1404,41 @@ static void executeStartUpFile (MelderDir startUpDirectory, const wchar_t *fileN
 
 #if gtk
 	#include <gdk/gdkkeysyms.h>
-	static gint theKeySnooper (GtkWidget *widget, GdkEventKey *event, gpointer data) {
-		trace ("keyval %ld, type %ld", (long) event -> keyval, (long) event -> type);
-		if ((event -> keyval == GDK_Tab || event -> keyval == GDK_ISO_Left_Tab) && event -> type == GDK_KEY_PRESS) {
-			trace ("tab key pressed in window %p", widget);
-			if (event -> state == 0) {
-				if (GTK_IS_WINDOW (widget)) {
-					GtkWidget *shell = gtk_widget_get_toplevel (GTK_WIDGET (widget));
-					trace ("tab pressed in GTK window %p", shell);
-					void (*tabCallback) (GuiObject, gpointer) = (void (*) (GuiObject, gpointer)) g_object_get_data (G_OBJECT (widget), "tabCallback");
-					if (tabCallback) {
-						trace ("a tab callback exists");
-						void *tabClosure = g_object_get_data (G_OBJECT (widget), "tabClosure");
-						tabCallback (widget, tabClosure);
-						return TRUE;
+	#if ALLOW_GDK_DRAWING
+		static gint theKeySnooper (GtkWidget *widget, GdkEventKey *event, gpointer data) {
+			trace ("keyval %ld, type %ld", (long) event -> keyval, (long) event -> type);
+			if ((event -> keyval == GDK_Tab || event -> keyval == GDK_ISO_Left_Tab) && event -> type == GDK_KEY_PRESS) {
+				trace ("tab key pressed in window %p", widget);
+				if (event -> state == 0) {
+					if (GTK_IS_WINDOW (widget)) {
+						GtkWidget *shell = gtk_widget_get_toplevel (GTK_WIDGET (widget));
+						trace ("tab pressed in GTK window %p", shell);
+						void (*tabCallback) (GuiObject, gpointer) = (void (*) (GuiObject, gpointer)) g_object_get_data (G_OBJECT (widget), "tabCallback");
+						if (tabCallback) {
+							trace ("a tab callback exists");
+							void *tabClosure = g_object_get_data (G_OBJECT (widget), "tabClosure");
+							tabCallback (widget, tabClosure);
+							return TRUE;
+						}
 					}
-				}
-			} else if (event -> state == GDK_SHIFT_MASK) {   // BUG: 
-				if (GTK_IS_WINDOW (widget)) {
-					GtkWidget *shell = gtk_widget_get_toplevel (GTK_WIDGET (widget));
-					trace ("shift-tab pressed in GTK window %p", shell);
-					void (*tabCallback) (GuiObject, gpointer) = (void (*) (GuiObject, gpointer)) g_object_get_data (G_OBJECT (widget), "shiftTabCallback");
-					if (tabCallback) {
-						trace ("a shift tab callback exists");
-						void *tabClosure = g_object_get_data (G_OBJECT (widget), "shiftTabClosure");
-						tabCallback (widget, tabClosure);
-						return TRUE;
+				} else if (event -> state == GDK_SHIFT_MASK) {   // BUG: 
+					if (GTK_IS_WINDOW (widget)) {
+						GtkWidget *shell = gtk_widget_get_toplevel (GTK_WIDGET (widget));
+						trace ("shift-tab pressed in GTK window %p", shell);
+						void (*tabCallback) (GuiObject, gpointer) = (void (*) (GuiObject, gpointer)) g_object_get_data (G_OBJECT (widget), "shiftTabCallback");
+						if (tabCallback) {
+							trace ("a shift tab callback exists");
+							void *tabClosure = g_object_get_data (G_OBJECT (widget), "shiftTabClosure");
+							tabCallback (widget, tabClosure);
+							return TRUE;
+						}
 					}
 				}
 			}
+			trace ("end");
+			return FALSE;   // pass event on
 		}
-		trace ("end");
-		return FALSE;   // pass event on
-	}
+	#endif
 #endif
 
 void praat_run (void) {
@@ -1400,12 +1499,14 @@ void praat_run (void) {
 				MelderDir_getSubdir (& praatDir, directoryNames -> strings [i], & pluginDir);
 				MelderDir_getFile (& pluginDir, L"setup.praat", & plugin);
 				if (MelderFile_readable (& plugin)) {
+					Melder_backgrounding = true;
 					try {
 						praat_executeScriptFromFile (& plugin, NULL);
 					} catch (MelderError) {
 						Melder_error_ (praatP.title, ": plugin ", & plugin, " contains an error.");
 						Melder_flushError (NULL);
 					}
+					Melder_backgrounding = false;
 				}
 			}
 		}
@@ -1497,9 +1598,13 @@ void praat_run (void) {
 			//gtk_widget_add_events (G_OBJECT (theCurrentPraatApplication -> topShell), GDK_ALL_EVENTS_MASK);
 			trace ("install GTK key snooper");
 			trace ("locale is %s", setlocale (LC_ALL, NULL));
-			g_signal_connect (G_OBJECT (theCurrentPraatApplication -> topShell -> d_gtkWindow), "client-event", G_CALLBACK (cb_userMessage), NULL);
+			#if ALLOW_GDK_DRAWING
+				g_signal_connect (G_OBJECT (theCurrentPraatApplication -> topShell -> d_gtkWindow), "client-event", G_CALLBACK (cb_userMessage), NULL);
+			#endif
 			signal (SIGUSR1, cb_sigusr1);
-			gtk_key_snooper_install (theKeySnooper, 0);
+			#if ALLOW_GDK_DRAWING
+				gtk_key_snooper_install (theKeySnooper, 0);
+			#endif
 			trace ("start the GTK event loop");
 			trace ("locale is %s", setlocale (LC_ALL, NULL));
 			gtk_main ();
@@ -1510,24 +1615,39 @@ void praat_run (void) {
 				if (theCurrentPraatApplication -> batchName.string [0] != '\0') {
 					wchar_t text [500];
 					/*
-					 * The user dropped a file on the Praat icon, while Praat was not running yet.
-					 * Windows may have enclosed the path between quotes;
-					 * this is especially likely to happen if the path contains spaces (which is usual).
-					 * And sometimes, Windows prepends a space before the quote.
-					 * Peel all that off.
-					 *
-					 * BUG: this only works now with single files; it should work with multiple files as well.
+					 * The user dropped one or more files on the Praat icon, while Praat was not running yet.
+					 * Windows may have enclosed each path between quotes;
+					 * this is especially likely to happen for paths that contain spaces (which is usual).
 					 */
+
 					wchar_t *s = theCurrentPraatApplication -> batchName.string;
-					swprintf (text, 500, L"Read from file... %ls", s [0] == ' ' && s [1] == '\"' ? s + 2 : s [0] == '\"' ? s + 1 : s);
-					long l = wcslen (text);
-					if (l > 0 && text [l - 1] == '\"') text [l - 1] = '\0';
-					//Melder_error3 (L"command <<", text, L">>");
-					//Melder_flushError (NULL);
-					try {
-						praat_executeScriptFromText (text);
-					} catch (MelderError) {
-						Melder_flushError (NULL);
+					for (;;) {
+						bool endSeen = false;
+						while (*s == ' ' || *s == '\n') s ++;
+						if (*s == '\0') break;
+						wchar_t *path = s;
+						if (*s == '\"') {
+							path = ++ s;
+							while (*s != '\"' && *s != '\0') s ++;
+							if (*s == '\0') break;
+							Melder_assert (*s == '\"');
+							*s = '\0';
+						} else {
+							while (*s != ' ' && *s != '\n' && *s != '\0') s ++;
+							if (*s == ' ' || *s == '\n') {
+								*s = '\0';
+							} else {
+								endSeen = true;
+							}
+						}
+						swprintf (text, 500, L"Read from file... %ls", path);
+						try {
+							praat_executeScriptFromText (text);
+						} catch (MelderError) {
+							Melder_flushError (NULL);
+						}
+						if (endSeen) break;
+						s ++;
 					}
 				}
 			#endif
diff --git a/sys/praat.h b/sys/praat.h
index 5d5c1f1..3472193 100644
--- a/sys/praat.h
+++ b/sys/praat.h
@@ -1,6 +1,6 @@
 /* praat.h
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -157,7 +157,7 @@ typedef struct {   /* Readonly */
 	Graphics graphics;   /* The Graphics associated with the Picture window or HyperPage window or Demo window. */
 	int font, fontSize, lineType;
 	Graphics_Colour colour;
-	double lineWidth, arrowSize, x1NDC, x2NDC, y1NDC, y2NDC;
+	double lineWidth, arrowSize, speckleSize, x1NDC, x2NDC, y1NDC, y2NDC;
 } structPraatPicture, *PraatPicture;
 extern structPraatApplication theForegroundPraatApplication;
 extern PraatApplication theCurrentPraatApplication;
@@ -285,6 +285,7 @@ void praat_name2 (wchar_t *name, ClassInfo klas1, ClassInfo klas2);
 #define COLOUR(label,def)	UiForm_addColour (dia, label, def);
 #define CHANNEL(label,def)	UiForm_addChannel (dia, label, def);
 #define OK UiForm_finish (dia); } if (sendingForm == NULL && args == NULL && sendingString == NULL) {
+#define OK2 } UiForm_finish (dia); } if (sendingForm == NULL && args == NULL && sendingString == NULL) {
 #define SET_REAL(name,value)	UiForm_setReal (dia, name, value);
 #define SET_INTEGER(name,value)	UiForm_setInteger (dia, name, value);
 #define SET_STRING(name,value)	UiForm_setString (dia, name, value);
@@ -343,6 +344,14 @@ void praat_name2 (wchar_t *name, ClassInfo klas1, ClassInfo klas2);
 			praat_updateSelection (); \
 		} \
 	}
+#define END2 \
+				} \
+			} catch (MelderError) { \
+				praat_updateSelection (); \
+				throw; \
+			} \
+			praat_updateSelection (); \
+		}
 
 #define DIRECT(proc) \
 	static void DO_##proc (UiForm dummy1, int narg, Stackel args, const wchar_t *dummy2, Interpreter dummy3, const wchar_t *dummy4, bool dummy5, void *dummy6) { \
@@ -353,6 +362,14 @@ void praat_name2 (wchar_t *name, ClassInfo klas1, ClassInfo klas2);
 				(void) IOBJECT; \
 				{
 
+#define DIRECT2(proc) \
+	static void DO_##proc (UiForm dummy1, int narg, Stackel args, const wchar_t *dummy2, Interpreter dummy3, const wchar_t *dummy4, bool dummy5, void *dummy6) { \
+		(void) dummy1; (void) narg; (void) args; (void) dummy2; (void) dummy3; (void) dummy4; (void) dummy5; (void) dummy6; \
+		{ \
+			try { \
+				int IOBJECT = 0; \
+				(void) IOBJECT;
+
 #define FORM_READ(proc,title,help,allowMult) \
 	static void DO_##proc (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *okClosure) { \
 		static UiForm dia; \
@@ -378,6 +395,30 @@ void praat_name2 (wchar_t *name, ClassInfo klas1, ClassInfo klas2);
 				} \
 				{
 
+#define FORM_READ2(proc,title,help,allowMult) \
+	static void DO_##proc (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *okClosure) { \
+		static UiForm dia; \
+		(void) narg; \
+		(void) interpreter; \
+		(void) modified; \
+		(void) okClosure; \
+		if (dia == NULL) \
+			dia = UiInfile_create (theCurrentPraatApplication -> topShell, title, DO_##proc, okClosure, invokingButtonTitle, help, allowMult); \
+		if (sendingForm == NULL && args == NULL && sendingString == NULL) { \
+			UiInfile_do (dia); \
+		} else { \
+			try { \
+				MelderFile file; \
+				int IOBJECT = 0; \
+				structMelderFile file2 = { 0 }; \
+				(void) IOBJECT; \
+				if (args == NULL && sendingString == NULL) { \
+					file = UiFile_getFile (dia); \
+				} else { \
+					Melder_relativePathToFile (args ? args [1]. string : sendingString, & file2); \
+					file = & file2; \
+				} \
+
 #define FORM_WRITE(proc,title,help,ext) \
 	static void DO_##proc (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *okClosure) { \
 		static Any dia; \
@@ -403,6 +444,30 @@ void praat_name2 (wchar_t *name, ClassInfo klas1, ClassInfo klas2);
 				} \
 				{
 
+#define FORM_WRITE2(proc,title,help,ext) \
+	static void DO_##proc (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *okClosure) { \
+		static Any dia; \
+		(void) narg; \
+		(void) interpreter; \
+		(void) modified; \
+		(void) okClosure; \
+		if (dia == NULL) \
+			dia = UiOutfile_create (theCurrentPraatApplication -> topShell, title, DO_##proc, okClosure, invokingButtonTitle, help); \
+		if (sendingForm == NULL && args == NULL && sendingString == NULL) { \
+			praat_write_do (dia, ext); \
+		} else { \
+			try { \
+				MelderFile file; \
+				int IOBJECT = 0; \
+				structMelderFile file2 = { 0 }; \
+				(void) IOBJECT; \
+				if (args == NULL && sendingString == NULL) { \
+					file = UiFile_getFile (dia); \
+				} else { \
+					Melder_relativePathToFile (args ? args [1]. string : sendingString, & file2); \
+					file = & file2; \
+				}
+
 /*
 	Macros for DO_proc:
 	GET_REAL (name)
diff --git a/sys/praatP.h b/sys/praatP.h
index 2c223a8..fb453d7 100644
--- a/sys/praatP.h
+++ b/sys/praatP.h
@@ -1,6 +1,6 @@
 /* praatP.h
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -121,6 +121,7 @@ void praat_list_foreground ();   // updates the list of objects after background
 void praat_background ();
 void praat_foreground ();
 Editor praat_findEditorFromString (const wchar_t *string);
+Editor praat_findEditorById (long id);
 
 void praat_showLogo (int autoPopDown);
 
diff --git a/sys/praat_actions.cpp b/sys/praat_actions.cpp
index d0850a2..66e5191 100644
--- a/sys/praat_actions.cpp
+++ b/sys/praat_actions.cpp
@@ -1,6 +1,6 @@
 /* praat_actions.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -56,15 +56,15 @@ static void fixSelectionSpecification (ClassInfo *class1, int *n1, ClassInfo *cl
 	 * Bubble-sort the input by class name.
 	 */
 	if (*class2 && wcscmp ((*class1) -> className, (*class2) -> className) > 0) {
-		ClassInfo helpClass = *class1; *class1 = *class2; *class2 = helpClass;
-		int helpN = *n1; *n1 = *n2; *n2 = helpN;
+		ClassInfo helpClass1 = *class1; *class1 = *class2; *class2 = helpClass1;
+		int helpN1 = *n1; *n1 = *n2; *n2 = helpN1;
 	}
 	if (*class3 && wcscmp ((*class2) -> className, (*class3) -> className) > 0) {
-		ClassInfo helpClass = *class2; *class2 = *class3; *class3 = helpClass;
-		int helpN = *n2; *n2 = *n3; *n3 = helpN;
+		ClassInfo helpClass2 = *class2; *class2 = *class3; *class3 = helpClass2;
+		int helpN2 = *n2; *n2 = *n3; *n3 = helpN2;
 		if (wcscmp ((*class1) -> className, (*class2) -> className) > 0) {
-			ClassInfo helpClass = *class1; *class1 = *class2; *class2 = helpClass;
-			int helpN = *n1; *n1 = *n2; *n2 = helpN;
+			ClassInfo helpClass1 = *class1; *class1 = *class2; *class2 = helpClass1;
+			int helpN1 = *n1; *n1 = *n2; *n2 = helpN1;
 		}
 	}
 }
@@ -524,9 +524,8 @@ static void do_menu (I, bool modified) {
 		praat_Command me = & theActions [i];
 		if (my callback == callback) {
 			if (my title != NULL && ! wcsstr (my title, L"...")) {
-				UiHistory_write (L"\ndo (\"");
-				UiHistory_write_expandQuotes (my title);
-				UiHistory_write (L"\")");
+				UiHistory_write (L"\n");
+				UiHistory_write_colonize (my title);
 			}
 			Ui_setAllowExecutionHook (allowExecutionHook, (void *) callback);   // BUG: one shouldn't assign a function pointer to a void pointer
 			try {
diff --git a/sys/praat_logo.cpp b/sys/praat_logo.cpp
index d3069e2..7f8b75a 100644
--- a/sys/praat_logo.cpp
+++ b/sys/praat_logo.cpp
@@ -1,6 +1,6 @@
 /* praat_logo.cpp
  *
- * Copyright (C) 1996-2012 Paul Boersma, 2008 Stefan de Konink
+ * Copyright (C) 1996-2012,2013,2014 Paul Boersma, 2008 Stefan de Konink
  *
  * 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
@@ -64,19 +64,23 @@ void praat_setLogo (double width_mm, double height_mm, void (*draw) (Graphics g)
 static void gui_drawingarea_cb_expose (I, GuiDrawingAreaExposeEvent event) {
 	if (theLogo.graphics == NULL)
 		theLogo.graphics = Graphics_create_xmdrawingarea (theLogo.drawingArea);
-#if gtk
-	Graphics_x_setCR (theLogo.graphics, gdk_cairo_create (GDK_DRAWABLE (GTK_WIDGET (event -> widget -> d_widget) -> window)));
-	cairo_rectangle ((cairo_t *) Graphics_x_getCR (theLogo.graphics), (double) event->x, (double) event->y, (double) event->width, (double) event->height);
-	cairo_clip ((cairo_t *) Graphics_x_getCR (theLogo.graphics));
-	theLogo.draw (theLogo.graphics);
-	cairo_destroy ((cairo_t *) Graphics_x_getCR (theLogo.graphics));
-#elif motif
-	(void) void_me;
-	(void) event;
-	if (theLogo.graphics == NULL)
-		theLogo.graphics = Graphics_create_xmdrawingarea (theLogo.drawingArea);
-	theLogo.draw (theLogo.graphics);
-#endif
+	#if gtk
+		#if ALLOW_GDK_DRAWING
+			Graphics_x_setCR (theLogo.graphics, gdk_cairo_create (GDK_DRAWABLE (GTK_WIDGET (event -> widget -> d_widget) -> window)));
+		#else
+			Graphics_x_setCR (theLogo.graphics, gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (event -> widget -> d_widget))));
+		#endif
+		cairo_rectangle ((cairo_t *) Graphics_x_getCR (theLogo.graphics), (double) event->x, (double) event->y, (double) event->width, (double) event->height);
+		cairo_clip ((cairo_t *) Graphics_x_getCR (theLogo.graphics));
+		theLogo.draw (theLogo.graphics);
+		cairo_destroy ((cairo_t *) Graphics_x_getCR (theLogo.graphics));
+	#elif motif || cocoa
+		(void) void_me;
+		(void) event;
+		if (theLogo.graphics == NULL)
+			theLogo.graphics = Graphics_create_xmdrawingarea (theLogo.drawingArea);
+		theLogo.draw (theLogo.graphics);
+	#endif
 }
 
 static void gui_drawingarea_cb_click (I, GuiDrawingAreaClickEvent event) {
@@ -117,10 +121,8 @@ void praat_showLogo (int autoPopDown) {
 			theLogo.drawingArea = GuiDrawingArea_createShown (theLogo.form, 0, width, 0, height,
 				gui_drawingarea_cb_expose, gui_drawingarea_cb_click, NULL, NULL, NULL, 0);
 		}
-
 		theLogo.form -> f_show ();
 		theLogo.dia -> f_show ();
-		
 		#if motif
 			if (autoPopDown)
 				GuiAddTimeOut (2000, logo_timeOut, (XtPointer) NULL);
diff --git a/sys/praat_menuCommands.cpp b/sys/praat_menuCommands.cpp
index 7fc0de5..6716baf 100644
--- a/sys/praat_menuCommands.cpp
+++ b/sys/praat_menuCommands.cpp
@@ -1,6 +1,6 @@
 /* praat_menuCommands.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -74,9 +74,8 @@ static void do_menu (I, unsigned long modified) {
 		praat_Command me = & theCommands [i];
 		if (my callback == callback) {
 			if (my title != NULL && ! wcsstr (my title, L"...")) {
-				UiHistory_write (L"\ndo (\"");
+				UiHistory_write (L"\n");
 				UiHistory_write_expandQuotes (my title);
-				UiHistory_write (L"\")");
 			}
 			try {
 				callback (NULL, 0, NULL, NULL, NULL, my title, modified, NULL);
@@ -443,10 +442,7 @@ praat_Command praat_getMenuCommand (long i)
 void praat_addCommandsToEditor (Editor me) {
 	const wchar_t *windowName = my classInfo -> className;
 	for (long i = 1; i <= theNumberOfCommands; i ++) if (wcsequ (theCommands [i]. window, windowName)) {
-		if (! Editor_addCommandScript (me, theCommands [i]. menu, theCommands [i]. title, 0, theCommands [i]. script))
-			Melder_flushError ("To fix this, go to Praat->Preferences->Buttons->Editors, "
-				"and remove the script from this menu.\n"
-				"You may want to install the script in a different menu.");
+		Editor_addCommandScript (me, theCommands [i]. menu, theCommands [i]. title, 0, theCommands [i]. script);
 	}
 }
 
diff --git a/sys/praat_objectMenus.cpp b/sys/praat_objectMenus.cpp
index bf96979..68137d8 100644
--- a/sys/praat_objectMenus.cpp
+++ b/sys/praat_objectMenus.cpp
@@ -1,6 +1,6 @@
 /* praat_objectMenus.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -24,6 +24,7 @@
 #include "ButtonEditor.h"
 #include "DataEditor.h"
 #include "site.h"
+#include "GraphicsP.h"
 
 #undef iam
 #define iam iam_LOOP
@@ -104,13 +105,14 @@ END
 
 /********** The fixed menus. **********/
 
-static GuiMenu praatMenu, editMenu, newMenu, readMenu, goodiesMenu, preferencesMenu, technicalMenu, applicationHelpMenu, helpMenu;
+static GuiMenu praatMenu, editMenu, windowMenu, newMenu, readMenu, goodiesMenu, preferencesMenu, technicalMenu, applicationHelpMenu, helpMenu;
 
 GuiMenu praat_objects_resolveMenu (const wchar_t *menu) {
 	return
 		wcsequ (menu, L"Praat") || wcsequ (menu, L"Control") ? praatMenu :
 		#if cocoa
 			wcsequ (menu, L"Edit") ? editMenu :
+			wcsequ (menu, L"Window") ? windowMenu :
 		#endif
 		wcsequ (menu, L"New") || wcsequ (menu, L"Create") ? newMenu :
 		wcsequ (menu, L"Open") || wcsequ (menu, L"Read") ? readMenu :
@@ -251,6 +253,15 @@ DO
 	Melder_setOutputEncoding (GET_ENUM (kMelder_textOutputEncoding, L"Output encoding"));
 END
 
+FORM (GraphicsCjkFontStyleSettings, L"CJK font style preferences", 0)
+	OPTIONMENU_ENUM (L"CJK font style", kGraphics_cjkFontStyle, DEFAULT)
+	OK
+SET_ENUM (L"CJK font style", kGraphics_cjkFontStyle, theGraphicsCjkFontStyle)
+DO
+	theGraphicsCjkFontStyle = GET_ENUM (kGraphics_cjkFontStyle, L"CJK font style");
+END
+
+
 /********** Callbacks of the Goodies menu. **********/
 
 FORM (praat_calculator, L"Calculator", L"Calculator")
@@ -261,17 +272,29 @@ FORM (praat_calculator, L"Calculator", L"Calculator")
 	LABEL (L"", L"For details, click Help.")
 	OK
 DO
+	struct Formula_Result result;
 	if (interpreter == NULL) {
 		interpreter = Interpreter_create (NULL, NULL);
 		try {
-			Interpreter_anyExpression (interpreter, GET_STRING (L"expression"), NULL);
+			Interpreter_anyExpression (interpreter, GET_STRING (L"expression"), & result);
 			forget (interpreter);
 		} catch (MelderError) {
 			forget (interpreter);
 			throw;
 		}
 	} else {
-		Interpreter_anyExpression (interpreter, GET_STRING (L"expression"), NULL);
+		Interpreter_anyExpression (interpreter, GET_STRING (L"expression"), & result);
+	}
+	switch (result. expressionType) {
+		case kFormula_EXPRESSION_TYPE_NUMERIC: {
+			Melder_information (Melder_double (result. result.numericResult));
+		} break;
+		case kFormula_EXPRESSION_TYPE_STRING: {
+			Melder_information (result. result.stringResult);
+			Melder_free (result. result.stringResult);
+		} break;
+		case kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY: {
+		}
 	}
 END
 
@@ -559,6 +582,15 @@ END
 DIRECT (praat_paste)
 	[[[NSApp keyWindow] fieldEditor: YES forObject: nil] pasteAsPlainText: nil];
 END
+DIRECT (praat_minimize)
+	[[NSApp keyWindow] performMiniaturize: nil];
+END
+DIRECT (praat_zoom)
+	[[NSApp keyWindow] performZoom: nil];
+END
+DIRECT (praat_close)
+	[[NSApp keyWindow] performClose: nil];
+END
 #endif
 
 void praat_addMenus (GuiWindow window) {
@@ -574,6 +606,7 @@ void praat_addMenus (GuiWindow window) {
 			praatMenu = GuiMenu_createInWindow (NULL, L"\024", 0);
 			#if cocoa
 				editMenu = GuiMenu_createInWindow (NULL, L"Edit", 0);
+				windowMenu = GuiMenu_createInWindow (NULL, L"Window", 0);
 			#endif
 		#else
 			praatMenu = GuiMenu_createInWindow (window, L"Praat", 0);
@@ -591,16 +624,18 @@ void praat_addMenus (GuiWindow window) {
 	#ifdef macintosh
 		praat_addMenuCommand (L"Objects", L"Praat", itemTitle_about.string, 0, praat_UNHIDABLE, DO_About);
 		#if cocoa
-			praat_addMenuCommand (L"Objects", L"Edit", L"Cut", 0, 'X', DO_praat_cut);
-			praat_addMenuCommand (L"Objects", L"Edit", L"Copy", 0, 'C', DO_praat_copy);
-			praat_addMenuCommand (L"Objects", L"Edit", L"Paste", 0, 'V', DO_praat_paste);
+			praat_addMenuCommand (L"Objects", L"Edit", L"Cut", 0, praat_UNHIDABLE + 'X', DO_praat_cut);
+			praat_addMenuCommand (L"Objects", L"Edit", L"Copy", 0, praat_UNHIDABLE + 'C', DO_praat_copy);
+			praat_addMenuCommand (L"Objects", L"Edit", L"Paste", 0, praat_UNHIDABLE + 'V', DO_praat_paste);
+			praat_addMenuCommand (L"Objects", L"Window", L"Minimize", 0, praat_UNHIDABLE, DO_praat_minimize);
+			praat_addMenuCommand (L"Objects", L"Window", L"Zoom", 0, praat_UNHIDABLE, DO_praat_zoom);
+			praat_addMenuCommand (L"Objects", L"Window", L"Close", 0, 'W', DO_praat_close);
 		#endif
 	#endif
 	#ifdef UNIX
 		praat_addMenuCommand (L"Objects", L"Praat", itemTitle_about.string, 0, praat_UNHIDABLE, DO_About);
 	#endif
 	praat_addMenuCommand (L"Objects", L"Praat", L"-- script --", 0, 0, 0);
-	praat_addMenuCommand (L"Objects", L"Praat", L"Run script...", 0, praat_HIDDEN, DO_praat_runScript);
 	praat_addMenuCommand (L"Objects", L"Praat", L"New Praat script", 0, 0, DO_praat_newScript);
 	praat_addMenuCommand (L"Objects", L"Praat", L"Open Praat script...", 0, 0, DO_praat_openScript);
 	praat_addMenuCommand (L"Objects", L"Praat", L"-- buttons --", 0, 0, 0);
@@ -622,6 +657,7 @@ void praat_addMenus (GuiWindow window) {
 	praat_addMenuCommand (L"Objects", L"Preferences", L"-- encoding prefs --", 0, 0, 0);
 	praat_addMenuCommand (L"Objects", L"Preferences", L"Text reading preferences...", 0, 0, DO_TextInputEncodingSettings);
 	praat_addMenuCommand (L"Objects", L"Preferences", L"Text writing preferences...", 0, 0, DO_TextOutputEncodingSettings);
+	praat_addMenuCommand (L"Objects", L"Preferences", L"CJK font style preferences...", 0, 0, DO_GraphicsCjkFontStyleSettings);
 
 	menuItem = praat_addMenuCommand (L"Objects", L"Praat", L"Technical", 0, praat_UNHIDABLE, 0);
 	technicalMenu = menuItem ? menuItem -> d_menu : NULL;
diff --git a/sys/praat_picture.cpp b/sys/praat_picture.cpp
index b82c3cb..9201927 100644
--- a/sys/praat_picture.cpp
+++ b/sys/praat_picture.cpp
@@ -1,6 +1,6 @@
 /* praat_picture.cpp
  *
- * Copyright (C) 1992-2012,2013 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014 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
@@ -53,7 +53,7 @@ static void updateFontMenu (void) {
 	}
 }
 static void setFont (kGraphics_font font) {
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_setFont (GRAPHICS, font);
 	}
@@ -62,10 +62,10 @@ static void setFont (kGraphics_font font) {
 		updateFontMenu ();
 	}
 }
-DIRECT (Times) setFont (kGraphics_font_TIMES); END
-DIRECT (Helvetica) setFont (kGraphics_font_HELVETICA); END
-DIRECT (Palatino) setFont (kGraphics_font_PALATINO); END
-DIRECT (Courier) setFont (kGraphics_font_COURIER); END
+DIRECT (Times)     { setFont (kGraphics_font_TIMES);     } END
+DIRECT (Helvetica) { setFont (kGraphics_font_HELVETICA); } END
+DIRECT (Palatino)  { setFont (kGraphics_font_PALATINO);  } END
+DIRECT (Courier)   { setFont (kGraphics_font_COURIER);   } END
 
 /***** "Font" MENU: size part *****/
 
@@ -81,7 +81,7 @@ static void updateSizeMenu (void) {
 }
 static void setFontSize (int fontSize) {
 	//Melder_casual("Praat picture: set font size %d", fontSize);
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_setFontSize (GRAPHICS, fontSize);
 	}
@@ -91,18 +91,18 @@ static void setFontSize (int fontSize) {
 	}
 }
 
-DIRECT (10) setFontSize (10); END
-DIRECT (12) setFontSize (12); END
-DIRECT (14) setFontSize (14); END
-DIRECT (18) setFontSize (18); END
-DIRECT (24) setFontSize (24); END
-FORM (Font_size, L"Praat picture: Font size", L"Font menu")
+DIRECT (10) { setFontSize (10); } END
+DIRECT (12) { setFontSize (12); } END
+DIRECT (14) { setFontSize (14); } END
+DIRECT (18) { setFontSize (18); } END
+DIRECT (24) { setFontSize (24); } END
+FORM (Font_size, L"Praat picture: Font size", L"Font menu") {
 	NATURAL (L"Font size (points)", L"10")
-OK
+OK2
 	SET_INTEGER (L"Font size", (long) theCurrentPraatPicture -> fontSize);
 DO
 	setFontSize (GET_INTEGER (L"Font size"));
-END
+END2 }
 
 /*static void setFontSize_keepInnerViewport (int fontSize) {
 	double xmargin = praat_size * 4.2 / 72.0, ymargin = praat_size * 2.8 / 72.0;
@@ -137,7 +137,7 @@ static void updateViewportMenu (void) {
 	}
 }
 
-DIRECT (MouseSelectsInnerViewport)
+DIRECT (MouseSelectsInnerViewport) {
 	if (theCurrentPraatPicture != & theForegroundPraatPicture)
 		Melder_throw ("Mouse commands are not available inside pictures.");
 	{// scope
@@ -145,9 +145,9 @@ DIRECT (MouseSelectsInnerViewport)
 		Picture_setMouseSelectsInnerViewport (praat_picture, praat_mouseSelectsInnerViewport = true);
 	}
 	updateViewportMenu ();
-END
+} END
 
-DIRECT (MouseSelectsOuterViewport)
+DIRECT (MouseSelectsOuterViewport) {
 	if (theCurrentPraatPicture != & theForegroundPraatPicture)
 		Melder_throw ("Mouse commands are not available inside pictures.");
 	{// scope
@@ -155,9 +155,9 @@ DIRECT (MouseSelectsOuterViewport)
 		Picture_setMouseSelectsInnerViewport (praat_picture, praat_mouseSelectsInnerViewport = false);
 	}
 	updateViewportMenu ();
-END
+} END
 
-FORM (SelectInnerViewport, L"Praat picture: Select inner viewport", L"Select inner viewport...")
+FORM (SelectInnerViewport, L"Praat picture: Select inner viewport", L"Select inner viewport...") {
 	LABEL (L"", L"The viewport is the selected rectangle in the Picture window.")
 	LABEL (L"", L"It is where your next drawing will appear.")
 	LABEL (L"", L"The rectangle you select here will not include the margins.")
@@ -166,10 +166,12 @@ FORM (SelectInnerViewport, L"Praat picture: Select inner viewport", L"Select inn
 	REAL (L"right Horizontal range (inches)", L"6.0")
 	REAL (L"left Vertical range (inches)", L"0.0")
 	REAL (L"right Vertical range (inches)", L"6.0")
-OK
+OK2
 	double xmargin = theCurrentPraatPicture -> fontSize * 4.2 / 72.0, ymargin = theCurrentPraatPicture -> fontSize * 2.8 / 72.0;
-	if (ymargin > 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC)) ymargin = 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC);
-	if (xmargin > 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC)) xmargin = 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC);
+	if (ymargin > 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC))
+		ymargin = 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC);
+	if (xmargin > 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC))
+		xmargin = 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC);
 	SET_REAL (L"left Horizontal range", theCurrentPraatPicture -> x1NDC + xmargin);
 	SET_REAL (L"right Horizontal range", theCurrentPraatPicture -> x2NDC - xmargin);
 	SET_REAL (L"left Vertical range", 12 - theCurrentPraatPicture -> y2NDC + ymargin);
@@ -179,6 +181,7 @@ DO
 	double left = GET_REAL (L"left Horizontal range"), right = GET_REAL (L"right Horizontal range");
 	double top = GET_REAL (L"left Vertical range"), bottom = GET_REAL (L"right Vertical range");
 	double xmargin = theCurrentPraatPicture -> fontSize * 4.2 / 72.0, ymargin = theCurrentPraatPicture -> fontSize * 2.8 / 72.0;
+	trace ("1: xmargin %f ymargin %f", xmargin, ymargin);
 	if (theCurrentPraatPicture != & theForegroundPraatPicture) {
 		long x1DC, x2DC, y1DC, y2DC;
 		Graphics_inqWsViewport (GRAPHICS, & x1DC, & x2DC, & y1DC, & y2DC);
@@ -191,6 +194,7 @@ DO
 	}
 	if (xmargin > 2 * (right - left)) xmargin = 2 * (right - left);
 	if (ymargin > 2 * (bottom - top)) ymargin = 2 * (bottom - top);
+	trace ("2: xmargin %f ymargin %f", xmargin, ymargin);
 	if (left == right) {
 		Melder_throw ("The left and right edges of the viewport cannot be equal.\nPlease change the horizontal range.");
 	}
@@ -205,6 +209,7 @@ DO
 		theCurrentPraatPicture -> y1NDC = 12-bottom - ymargin;
 		theCurrentPraatPicture -> y2NDC = 12-top + ymargin;
 		Picture_setSelection (praat_picture, theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC, false);
+		Graphics_updateWs (GRAPHICS);
 	} else if (theCurrentPraatObjects != & theForegroundPraatObjects) {   // in manual?
 		if (top > bottom) { double temp; temp = top; top = bottom; bottom = temp; }
 		double x1wNDC, x2wNDC, y1wNDC, y2wNDC;
@@ -217,9 +222,10 @@ DO
 		theCurrentPraatPicture -> y1NDC = bottom - ymargin;
 		theCurrentPraatPicture -> y2NDC = top + ymargin;
 	}
-END
+	trace ("3: x1NDC %f x2NDC %f y1NDC %f y2NDC %f", theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC);
+END2 }
 
-FORM (SelectOuterViewport, L"Praat picture: Select outer viewport", L"Select outer viewport...")
+FORM (SelectOuterViewport, L"Praat picture: Select outer viewport", L"Select outer viewport...") {
 	LABEL (L"", L"The viewport is the selected rectangle in the Picture window.")
 	LABEL (L"", L"It is where your next drawing will appear.")
 	LABEL (L"", L"The rectangle you select here will include the margins.")
@@ -228,7 +234,7 @@ FORM (SelectOuterViewport, L"Praat picture: Select outer viewport", L"Select out
 	REAL (L"right Horizontal range (inches)", L"6.0")
 	REAL (L"left Vertical range (inches)", L"0.0")
 	REAL (L"right Vertical range (inches)", L"6.0")
-OK
+OK2
 	SET_REAL (L"left Horizontal range", theCurrentPraatPicture -> x1NDC);
 	SET_REAL (L"right Horizontal range", theCurrentPraatPicture -> x2NDC);
 	SET_REAL (L"left Vertical range", 12 - theCurrentPraatPicture -> y2NDC);
@@ -251,6 +257,7 @@ DO
 		theCurrentPraatPicture -> y1NDC = 12-bottom;
 		theCurrentPraatPicture -> y2NDC = 12-top;
 		Picture_setSelection (praat_picture, theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC, false);
+		Graphics_updateWs (GRAPHICS);   // BUG: needed on Cocoa, but why?
 	} else if (theCurrentPraatObjects != & theForegroundPraatObjects) {   // in manual?
 		if (top > bottom) { double temp; temp = top; top = bottom; bottom = temp; }
 		double x1wNDC, x2wNDC, y1wNDC, y2wNDC;
@@ -263,9 +270,9 @@ DO
 		theCurrentPraatPicture -> y1NDC = bottom;
 		theCurrentPraatPicture -> y2NDC = top;
 	}
-END
+END2 }
 
-FORM (ViewportText, L"Praat picture: Viewport text", L"Viewport text...")
+FORM (ViewportText, L"Praat picture: Viewport text", L"Viewport text...") {
 	RADIO (L"Horizontal alignment", 2)
 		RADIOBUTTON (L"Left")
 		RADIOBUTTON (L"Centre")
@@ -276,7 +283,7 @@ FORM (ViewportText, L"Praat picture: Viewport text", L"Viewport text...")
 		RADIOBUTTON (L"Top")
 	REAL (L"Rotation (degrees)", L"0")
 	TEXTFIELD (L"text", L"")
-OK
+OK2
 DO
 	double x1WC, x2WC, y1WC, y2WC;
 	int hor = GET_INTEGER (L"Horizontal alignment") - 1;
@@ -290,7 +297,7 @@ DO
 		vert == 0 ? 0 : vert == 1 ? 0.5 : 1, GET_STRING (L"text"));
 	Graphics_setTextRotation (GRAPHICS, 0.0);
 	Graphics_setWindow (GRAPHICS, x1WC, x2WC, y1WC, y2WC);
-END
+END2 }
 
 /***** "Pen" MENU *****/
 
@@ -325,7 +332,7 @@ static void updatePenMenu (void) {
 	}
 }
 static void setLineType (int lineType) {
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_setLineType (GRAPHICS, lineType);
 	}
@@ -334,39 +341,54 @@ static void setLineType (int lineType) {
 		updatePenMenu ();
 	}
 }
-DIRECT (Solid_line) setLineType (Graphics_DRAWN); END
-DIRECT (Dotted_line) setLineType (Graphics_DOTTED); END
-DIRECT (Dashed_line) setLineType (Graphics_DASHED); END
-DIRECT (Dashed_dotted_line) setLineType (Graphics_DASHED_DOTTED); END
+DIRECT (Solid_line)         { setLineType (Graphics_DRAWN);         } END
+DIRECT (Dotted_line)        { setLineType (Graphics_DOTTED);        } END
+DIRECT (Dashed_line)        { setLineType (Graphics_DASHED);        } END
+DIRECT (Dashed_dotted_line) { setLineType (Graphics_DASHED_DOTTED); } END
 
-FORM (Line_width, L"Praat picture: Line width", 0)
+FORM (Line_width, L"Praat picture: Line width", 0) {
 	POSITIVE (L"Line width", L"1.0")
-OK
+OK2
 	SET_REAL (L"Line width", theCurrentPraatPicture -> lineWidth);
 DO
 	double lineWidth = GET_REAL (L"Line width");
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_setLineWidth (GRAPHICS, lineWidth);
 	}
 	theCurrentPraatPicture -> lineWidth = lineWidth;
-END
+END2 }
 
-FORM (Arrow_size, L"Praat picture: Arrow size", 0)
+FORM (Arrow_size, L"Praat picture: Arrow size", 0) {
 	POSITIVE (L"Arrow size", L"1.0")
-OK
+OK2
 	SET_REAL (L"Arrow size", theCurrentPraatPicture -> arrowSize);
 DO
 	double arrowSize = GET_REAL (L"Arrow size");
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_setArrowSize (GRAPHICS, arrowSize);
 	}
 	theCurrentPraatPicture -> arrowSize = arrowSize;
-END
+END2 }
+
+FORM (Speckle_size, L"Praat picture: Speckle size", 0) {
+	LABEL (L"", L"Here you determine the diameter (in millimetres)")
+	LABEL (L"", L"of the dots that are drawn by \"speckle\" commands.")
+	POSITIVE (L"Speckle size (mm)", L"1.0")
+OK2
+	SET_REAL (L"Speckle size", theCurrentPraatPicture -> speckleSize);
+DO
+	double speckleSize = GET_REAL (L"Speckle size");
+	{// scope
+		autoPraatPicture picture;
+		Graphics_setSpeckleSize (GRAPHICS, speckleSize);
+	}
+	theCurrentPraatPicture -> speckleSize = speckleSize;
+END2 }
 
 static void setColour (Graphics_Colour colour) {
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_setColour (GRAPHICS, colour);
 	}
@@ -375,30 +397,30 @@ static void setColour (Graphics_Colour colour) {
 		updatePenMenu ();
 	}
 }
-DIRECT (Black) setColour (Graphics_BLACK); END
-DIRECT (White) setColour (Graphics_WHITE); END
-DIRECT (Red) setColour (Graphics_RED); END
-DIRECT (Green) setColour (Graphics_GREEN); END
-DIRECT (Blue) setColour (Graphics_BLUE); END
-DIRECT (Yellow) setColour (Graphics_YELLOW); END
-DIRECT (Cyan) setColour (Graphics_CYAN); END
-DIRECT (Magenta) setColour (Graphics_MAGENTA); END
-DIRECT (Maroon) setColour (Graphics_MAROON); END
-DIRECT (Lime) setColour (Graphics_LIME); END
-DIRECT (Navy) setColour (Graphics_NAVY); END
-DIRECT (Teal) setColour (Graphics_TEAL); END
-DIRECT (Purple) setColour (Graphics_PURPLE); END
-DIRECT (Olive) setColour (Graphics_OLIVE); END
-DIRECT (Pink) setColour (Graphics_PINK); END
-DIRECT (Silver) setColour (Graphics_SILVER); END
-DIRECT (Grey) setColour (Graphics_GREY); END
-
-FORM (Colour, L"Praat picture: Colour", 0)
+DIRECT (Black)   { setColour (Graphics_BLACK);   } END
+DIRECT (White)   { setColour (Graphics_WHITE);   } END
+DIRECT (Red)     { setColour (Graphics_RED);     } END
+DIRECT (Green)   { setColour (Graphics_GREEN);   } END
+DIRECT (Blue)    { setColour (Graphics_BLUE);    } END
+DIRECT (Yellow)  { setColour (Graphics_YELLOW);  } END
+DIRECT (Cyan)    { setColour (Graphics_CYAN);    } END
+DIRECT (Magenta) { setColour (Graphics_MAGENTA); } END
+DIRECT (Maroon)  { setColour (Graphics_MAROON);  } END
+DIRECT (Lime)    { setColour (Graphics_LIME);    } END
+DIRECT (Navy)    { setColour (Graphics_NAVY);    } END
+DIRECT (Teal)    { setColour (Graphics_TEAL);    } END
+DIRECT (Purple)  { setColour (Graphics_PURPLE);  } END
+DIRECT (Olive)   { setColour (Graphics_OLIVE);   } END
+DIRECT (Pink)    { setColour (Graphics_PINK);    } END
+DIRECT (Silver)  { setColour (Graphics_SILVER);  } END
+DIRECT (Grey)    { setColour (Graphics_GREY);    } END
+
+FORM (Colour, L"Praat picture: Colour", 0) {
 	COLOUR (L"Colour (0-1, name, or {r,g,b})", L"0.0")
-OK
+OK2
 DO
 	Graphics_Colour colour = GET_COLOUR (L"Colour");
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_setColour (GRAPHICS, colour);
 	}
@@ -406,13 +428,13 @@ DO
 	if (theCurrentPraatPicture == & theForegroundPraatPicture) {
 		updatePenMenu ();
 	}
-END
+END2 }
 
 /***** "File" MENU *****/
 
-FORM_READ (Picture_readFromPraatPictureFile, L"Read picture from praat picture file", 0, false)
+FORM_READ (Picture_readFromPraatPictureFile, L"Read picture from praat picture file", 0, false) {
 	Picture_readFromPraatPictureFile (praat_picture, file);
-END
+} END
 
 static void DO_Picture_writeToEpsFile (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *dummy) {
 	static Any dia;
@@ -485,6 +507,40 @@ static void DO_Picture_writeToPdfFile (UiForm sendingForm, int narg, Stackel arg
 	}
 }
 
+static void DO_Picture_writeToPngFile_300 (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *dummy) {
+	static Any dia;
+	(void) narg;
+	(void) interpreter;
+	(void) modified;
+	(void) dummy;
+	if (! dia) dia = UiOutfile_create (theCurrentPraatApplication -> topShell, L"Save as PNG file",
+		DO_Picture_writeToPngFile_300, NULL, invokingButtonTitle, NULL);
+	if (sendingForm == NULL && args == NULL && sendingString == NULL) {
+		UiOutfile_do (dia, L"praat.png");
+	} else { MelderFile file; structMelderFile file2 = { 0 };
+		if (args == NULL && sendingString == NULL) file = UiFile_getFile (dia);
+		else { Melder_relativePathToFile (args ? args [1]. string : sendingString, & file2); file = & file2; }
+		Picture_writeToPngFile_300 (praat_picture, file);
+	}
+}
+
+static void DO_Picture_writeToPngFile_600 (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *dummy) {
+	static Any dia;
+	(void) narg;
+	(void) interpreter;
+	(void) modified;
+	(void) dummy;
+	if (! dia) dia = UiOutfile_create (theCurrentPraatApplication -> topShell, L"Save as PNG file",
+		DO_Picture_writeToPngFile_600, NULL, invokingButtonTitle, NULL);
+	if (sendingForm == NULL && args == NULL && sendingString == NULL) {
+		UiOutfile_do (dia, L"praat.png");
+	} else { MelderFile file; structMelderFile file2 = { 0 };
+		if (args == NULL && sendingString == NULL) file = UiFile_getFile (dia);
+		else { Melder_relativePathToFile (args ? args [1]. string : sendingString, & file2); file = & file2; }
+		Picture_writeToPngFile_600 (praat_picture, file);
+	}
+}
+
 static void DO_Picture_writeToPraatPictureFile (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *dummy) {
 	static Any dia;
 	(void) narg;
@@ -503,18 +559,18 @@ static void DO_Picture_writeToPraatPictureFile (UiForm sendingForm, int narg, St
 }
 
 #ifdef macintosh
-DIRECT (Page_setup)
+DIRECT (Page_setup) {
 	Printer_pageSetup ();
-END
+} END
 #endif
 
-DIRECT (PostScript_settings)
+DIRECT (PostScript_settings) {
 	Printer_postScriptSettings ();
-END
+} END
 
-DIRECT (Print)
+DIRECT (Print) {
 	Picture_print (praat_picture);
-END
+} END
 
 #ifdef _WIN32
 	static void DO_Picture_writeToWindowsMetafile (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *dummy) {
@@ -536,33 +592,33 @@ END
 #endif
 
 #if defined (_WIN32) || defined (macintosh)
-	DIRECT (Copy_picture_to_clipboard)
+	DIRECT (Copy_picture_to_clipboard) {
 		Picture_copyToClipboard (praat_picture);
-	END
+	} END
 #endif
 
 /***** "Edit" MENU *****/
 
-DIRECT (Undo)
+DIRECT (Undo) {
 	Graphics_undoGroup (GRAPHICS);
 	if (theCurrentPraatPicture != & theForegroundPraatPicture) {
 		Graphics_play (GRAPHICS, GRAPHICS);
 	}
 	Graphics_updateWs (GRAPHICS);
-END
+} END
 
-DIRECT (Erase_all)
+DIRECT (Erase_all) {
 	if (theCurrentPraatPicture == & theForegroundPraatPicture) {
 		Picture_erase (praat_picture);   /* This kills the recording. */
 	} else {
 		Graphics_clearRecording (GRAPHICS);
 		Graphics_clearWs (GRAPHICS);
 	}
-END
+} END
 
 /***** "World" MENU *****/
 
-FORM (Text, L"Praat picture: Text", L"Text...")
+FORM (Text, L"Praat picture: Text", L"Text...") {
 	REAL (L"Horizontal position", L"0.0")
 	OPTIONMENU (L"Horizontal alignment", 2)
 		OPTION (L"Left")
@@ -575,7 +631,7 @@ FORM (Text, L"Praat picture: Text", L"Text...")
 		OPTION (L"Top")
 	LABEL (L"", L"Text:")
 	TEXTFIELD (L"text", L"")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setTextAlignment (GRAPHICS,
@@ -584,9 +640,9 @@ DO
 	Graphics_text (GRAPHICS, GET_REAL (L"Horizontal position"),
 		GET_REAL (L"Vertical position"), GET_STRING (L"text"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (Text_special, L"Praat picture: Text special", 0)
+FORM (Text_special, L"Praat picture: Text special", 0) {
 	REAL (L"Horizontal position", L"0.0")
 	OPTIONMENU (L"Horizontal alignment", 2)
 		OPTION (L"left")
@@ -602,7 +658,7 @@ FORM (Text_special, L"Praat picture: Text special", 0)
 	SENTENCE (L"Rotation (degrees or dx;dy)", L"0")
 	LABEL (L"", L"Text:")
 	TEXTFIELD (L"text", L"")
-OK
+OK2
 DO
 	kGraphics_font currentFont = Graphics_inqFont (GRAPHICS);
 	int currentSize = Graphics_inqFontSize (GRAPHICS);
@@ -621,7 +677,7 @@ DO
 	Graphics_setFontSize (GRAPHICS, currentSize);
 	Graphics_setTextRotation (GRAPHICS, 0.0);
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
 static void dia_line (Any dia) {
 	REAL (L"From x", L"0.0")
@@ -629,38 +685,38 @@ static void dia_line (Any dia) {
 	REAL (L"To x", L"1.0")
 	REAL (L"To y", L"1.0")
 }
-FORM (DrawLine, L"Praat picture: Draw line", 0)
+FORM (DrawLine, L"Praat picture: Draw line", 0) {
 	dia_line (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_line (GRAPHICS, GET_REAL (L"From x"), GET_REAL (L"From y"), GET_REAL (L"To x"),
 		GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (DrawArrow, L"Praat picture: Draw arrow", 0)
+FORM (DrawArrow, L"Praat picture: Draw arrow", 0) {
 	dia_line (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_arrow (GRAPHICS, GET_REAL (L"From x"), GET_REAL (L"From y"), GET_REAL (L"To x"),
 		GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (DrawDoubleArrow, L"Praat picture: Draw double arrow", 0)
+FORM (DrawDoubleArrow, L"Praat picture: Draw double arrow", 0) {
 	dia_line (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_doubleArrow (GRAPHICS, GET_REAL (L"From x"), GET_REAL (L"From y"), GET_REAL (L"To x"),
 		GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
 Thing_define (PraatPictureFunction, Data) {
 	// new data:
@@ -676,7 +732,7 @@ Thing_define (PraatPictureFunction, Data) {
 };
 Thing_implement (PraatPictureFunction, Data, 0);
 
-FORM (DrawFunction, L"Praat picture: Draw function", 0)
+FORM (DrawFunction, L"Praat picture: Draw function", 0) {
 	LABEL (L"", L"This command assumes that the x and y axes")
 	LABEL (L"", L"have been set by a Draw command or by \"Axes...\".")
 	REAL (L"From x", L"0.0")
@@ -684,7 +740,7 @@ FORM (DrawFunction, L"Praat picture: Draw function", 0)
 	NATURAL (L"Number of horizontal steps", L"1000")
 	LABEL (L"", L"Formula:")
 	TEXTFIELD (L"formula", L"x^2 - x^4")
-OK
+OK2
 DO
 	double x1WC, x2WC, y1WC, y2WC;
 	double fromX = GET_REAL (L"From x"), toX = GET_REAL (L"To x");
@@ -710,7 +766,7 @@ DO
 	Graphics_setInner (GRAPHICS);
 	Graphics_function (GRAPHICS, y.peek(), 1, n, fromX, toX);
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
 static void dia_rectangle (Any dia) {
 	REAL (L"From x", L"0.0")
@@ -718,163 +774,163 @@ static void dia_rectangle (Any dia) {
 	REAL (L"From y", L"0.0")
 	REAL (L"To y", L"1.0")
 }
-FORM (DrawRectangle, L"Praat picture: Draw rectangle", 0)
+FORM (DrawRectangle, L"Praat picture: Draw rectangle", 0) {
 	dia_rectangle (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_rectangle (GRAPHICS,
 		GET_REAL (L"From x"), GET_REAL (L"To x"), GET_REAL (L"From y"), GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (PaintRectangle, L"Praat picture: Paint rectangle", 0)
+FORM (PaintRectangle, L"Praat picture: Paint rectangle", 0) {
 	COLOUR (L"Colour (0-1, name, or {r,g,b})", L"0.5")
 	dia_rectangle (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_setColour (GRAPHICS, GET_COLOUR (L"Colour"));
 	Graphics_fillRectangle (GRAPHICS, GET_REAL (L"From x"), GET_REAL (L"To x"), GET_REAL (L"From y"), GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (DrawRoundedRectangle, L"Praat picture: Draw rounded rectangle", 0)
+FORM (DrawRoundedRectangle, L"Praat picture: Draw rounded rectangle", 0) {
 	dia_rectangle (dia);
 	POSITIVE (L"Radius (mm)", L"3.0")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_roundedRectangle (GRAPHICS,
 		GET_REAL (L"From x"), GET_REAL (L"To x"), GET_REAL (L"From y"), GET_REAL (L"To y"), GET_REAL (L"Radius"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (PaintRoundedRectangle, L"Praat picture: Paint rounded rectangle", 0)
+FORM (PaintRoundedRectangle, L"Praat picture: Paint rounded rectangle", 0) {
 	COLOUR (L"Colour (0-1, name, or {r,g,b})", L"0.5")
 	dia_rectangle (dia);
 	POSITIVE (L"Radius (mm)", L"3.0")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_setColour (GRAPHICS, GET_COLOUR (L"Colour"));
 	Graphics_fillRoundedRectangle (GRAPHICS, GET_REAL (L"From x"), GET_REAL (L"To x"), GET_REAL (L"From y"), GET_REAL (L"To y"), GET_REAL (L"Radius"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (DrawArc, L"Praat picture: Draw arc", 0)
+FORM (DrawArc, L"Praat picture: Draw arc", 0) {
 	REAL (L"Centre x", L"0.0")
 	REAL (L"Centre y", L"0.0")
 	POSITIVE (L"Radius (along x)", L"1.0")
 	REAL (L"From angle (degrees)", L"0.0")
 	REAL (L"To angle (degrees)", L"90.0")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_arc (GRAPHICS, GET_REAL (L"Centre x"), GET_REAL (L"Centre y"), GET_REAL (L"Radius"),
 		GET_REAL (L"From angle"), GET_REAL (L"To angle"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (DrawEllipse, L"Praat picture: Draw ellipse", 0)
+FORM (DrawEllipse, L"Praat picture: Draw ellipse", 0) {
 	dia_rectangle (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_ellipse (GRAPHICS,
 		GET_REAL (L"From x"), GET_REAL (L"To x"), GET_REAL (L"From y"), GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (PaintEllipse, L"Praat picture: Paint ellipse", 0)
+FORM (PaintEllipse, L"Praat picture: Paint ellipse", 0) {
 	COLOUR (L"Colour (0-1, name, or {r,g,b})", L"0.5")
 	dia_rectangle (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_setColour (GRAPHICS, GET_COLOUR (L"Colour"));
 	Graphics_fillEllipse (GRAPHICS, GET_REAL (L"From x"), GET_REAL (L"To x"), GET_REAL (L"From y"), GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (DrawCircle, L"Praat picture: Draw circle", 0)
+FORM (DrawCircle, L"Praat picture: Draw circle", 0) {
 	REAL (L"Centre x", L"0.0")
 	REAL (L"Centre y", L"0.0")
 	POSITIVE (L"Radius (along x)", L"1.0")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_circle (GRAPHICS, GET_REAL (L"Centre x"), GET_REAL (L"Centre y"), GET_REAL (L"Radius"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (PaintCircle, L"Praat picture: Paint circle", 0)
+FORM (PaintCircle, L"Praat picture: Paint circle", 0) {
 	COLOUR (L"Colour (0-1, name, or {r,g,b})", L"0.5")
 	REAL (L"Centre x", L"0")
 	REAL (L"Centre y", L"0")
 	POSITIVE (L"Radius (along x)", L"1.0")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_setColour (GRAPHICS, GET_COLOUR (L"Colour"));
 	Graphics_fillCircle (GRAPHICS, GET_REAL (L"Centre x"), GET_REAL (L"Centre y"), GET_REAL (L"Radius"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (DrawCircle_mm, L"Praat picture: Draw circle (mm)", 0)
+FORM (DrawCircle_mm, L"Praat picture: Draw circle (mm)", 0) {
 	REAL (L"Centre x", L"0.0")
 	REAL (L"Centre y", L"0.0")
 	POSITIVE (L"Diameter (mm)", L"5.0")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_circle_mm (GRAPHICS, GET_REAL (L"Centre x"), GET_REAL (L"Centre y"), GET_REAL (L"Diameter"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (PaintCircle_mm, L"Praat picture: Paint circle (mm)", 0)
+FORM (PaintCircle_mm, L"Praat picture: Paint circle (mm)", 0) {
 	COLOUR (L"Colour (0-1, name, or {r,g,b})", L"0.5")
 	REAL (L"Centre x", L"0.0")
 	REAL (L"Centre y", L"0.0")
 	POSITIVE (L"Diameter (mm)", L"5.0")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_setColour (GRAPHICS, GET_COLOUR (L"Colour"));
 	Graphics_fillCircle_mm (GRAPHICS, GET_REAL (L"Centre x"), GET_REAL (L"Centre y"), GET_REAL (L"Diameter"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
-FORM (InsertPictureFromFile, L"Praat picture: Insert picture from file", L"Insert picture from file...")
+FORM (InsertPictureFromFile, L"Praat picture: Insert picture from file", L"Insert picture from file...") {
 	LABEL (L"", L"File name:")
 	TEXTFIELD (L"fileName", L"~/Desktop/paul.jpg")
 	dia_rectangle (dia);
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_setInner (GRAPHICS);
 	Graphics_imageFromFile (GRAPHICS, GET_STRING (L"fileName"), GET_REAL (L"From x"), GET_REAL (L"To x"), GET_REAL (L"From y"), GET_REAL (L"To y"));
 	Graphics_unsetInner (GRAPHICS);
-END
+END2 }
 
 
-FORM (Axes, L"Praat picture: Axes", L"Axes...")
+FORM (Axes, L"Praat picture: Axes", L"Axes...") {
 	REAL (L"left Left and right", L"0.0")
 	REAL (L"right Left and right", L"1.0")
 	REAL (L"left Bottom and top", L"0.0")
 	REAL (L"right Bottom and top", L"1.0")
-OK
+OK2
 	double x1WC, x2WC, y1WC, y2WC;
 	Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	SET_REAL (L"left Left and right", x1WC);
@@ -888,50 +944,50 @@ DO
 	REQUIRE (top != bottom, L"Top and bottom must not be equal.")
 	autoPraatPicture picture;
 	Graphics_setWindow (GRAPHICS, left, right, bottom, top);
-END
+END2 }
 
 /***** "Margins" MENU *****/
 
-DIRECT (DrawInnerBox)
+DIRECT (DrawInnerBox) {
 	autoPraatPicture picture;
 	Graphics_drawInnerBox (GRAPHICS);
-END
+} END
 
-FORM (Text_left, L"Praat picture: Text left", L"Text left/right/top/bottom...")
+FORM (Text_left, L"Praat picture: Text left", L"Text left/right/top/bottom...") {
 	BOOLEAN (L"Far", 1)
 	TEXTFIELD (L"text", L"")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_textLeft (GRAPHICS, GET_INTEGER (L"Far"), GET_STRING (L"text"));
-END
+END2 }
 
-FORM (Text_right, L"Praat picture: Text right", L"Text left/right/top/bottom...")
+FORM (Text_right, L"Praat picture: Text right", L"Text left/right/top/bottom...") {
 	BOOLEAN (L"Far", 1)
 	TEXTFIELD (L"text", L"")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_textRight (GRAPHICS, GET_INTEGER (L"Far"), GET_STRING (L"text"));
-END
+END2 }
 
-FORM (Text_top, L"Praat picture: Text top", L"Text left/right/top/bottom...")
+FORM (Text_top, L"Praat picture: Text top", L"Text left/right/top/bottom...") {
 	BOOLEAN (L"Far", 0)
 	TEXTFIELD (L"text", L"")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_textTop (GRAPHICS, GET_INTEGER (L"Far"), GET_STRING (L"text"));
-END
+END2 }
 
-FORM (Text_bottom, L"Praat picture: Text bottom", L"Text left/right/top/bottom...")
+FORM (Text_bottom, L"Praat picture: Text bottom", L"Text left/right/top/bottom...") {
 	BOOLEAN (L"Far", 1)
 	TEXTFIELD (L"text", L"")
-OK
+OK2
 DO
 	autoPraatPicture picture;
 	Graphics_textBottom (GRAPHICS, GET_INTEGER (L"Far"), GET_STRING (L"text"));
-END
+END2 }
 
 static void dia_marksEvery (Any dia) {
 	POSITIVE (L"Units", L"1.0")
@@ -946,14 +1002,14 @@ static void do_marksEvery (Any dia, void (*Graphics_marksEvery) (Graphics, doubl
 		GET_INTEGER (L"Write numbers"),
 		GET_INTEGER (L"Draw ticks"), GET_INTEGER (L"Draw dotted lines"));
 }
-FORM (Marks_left_every, L"Praat picture: Marks left every...", L"Marks left/right/top/bottom every...")
-	dia_marksEvery (dia); OK DO do_marksEvery (dia, Graphics_marksLeftEvery); END
-FORM (Marks_right_every, L"Praat picture: Marks right every...", L"Marks left/right/top/bottom every...")
-	dia_marksEvery (dia); OK DO do_marksEvery (dia, Graphics_marksRightEvery); END
-FORM (Marks_bottom_every, L"Praat picture: Marks bottom every...", L"Marks left/right/top/bottom every...")
-	dia_marksEvery (dia); OK DO do_marksEvery (dia, Graphics_marksBottomEvery); END
-FORM (Marks_top_every, L"Praat picture: Marks top every...", L"Marks left/right/top/bottom every...")
-	dia_marksEvery (dia); OK DO do_marksEvery (dia, Graphics_marksTopEvery); END
+FORM (Marks_left_every, L"Praat picture: Marks left every...", L"Marks left/right/top/bottom every...") {
+	dia_marksEvery (dia); OK2 DO do_marksEvery (dia, Graphics_marksLeftEvery); END2 }
+FORM (Marks_right_every, L"Praat picture: Marks right every...", L"Marks left/right/top/bottom every...") {
+	dia_marksEvery (dia); OK2 DO do_marksEvery (dia, Graphics_marksRightEvery); END2 }
+FORM (Marks_bottom_every, L"Praat picture: Marks bottom every...", L"Marks left/right/top/bottom every...") {
+	dia_marksEvery (dia); OK2 DO do_marksEvery (dia, Graphics_marksBottomEvery); END2 }
+FORM (Marks_top_every, L"Praat picture: Marks top every...", L"Marks left/right/top/bottom every...") {
+	dia_marksEvery (dia); OK2 DO do_marksEvery (dia, Graphics_marksTopEvery); END2 }
 
 static void dia_marks (Any dia) {
 	NATURAL (L"Number of marks", L"6")
@@ -968,14 +1024,14 @@ static void do_marks (Any dia, void (*Graphics_marks) (Graphics, int, bool, bool
 	Graphics_marks (GRAPHICS, numberOfMarks, GET_INTEGER (L"Write numbers"),
 		GET_INTEGER (L"Draw ticks"), GET_INTEGER (L"Draw dotted lines"));
 }
-FORM (Marks_left, L"Praat picture: Marks left", L"Marks left/right/top/bottom...")
-	dia_marks (dia); OK DO do_marks (dia, Graphics_marksLeft); END
-FORM (Marks_right, L"Praat picture: Marks right", L"Marks left/right/top/bottom...")
-	dia_marks (dia); OK DO do_marks (dia, Graphics_marksRight); END
-FORM (Marks_bottom, L"Praat picture: Marks bottom", L"Marks left/right/top/bottom...")
-	dia_marks (dia); OK DO do_marks (dia, Graphics_marksBottom); END
-FORM (Marks_top, L"Praat picture: Marks top", L"Marks left/right/top/bottom...")
-	dia_marks (dia); OK DO do_marks (dia, Graphics_marksTop); END
+FORM (Marks_left, L"Praat picture: Marks left", L"Marks left/right/top/bottom...") {
+	dia_marks (dia); OK2 DO do_marks (dia, Graphics_marksLeft); END2 }
+FORM (Marks_right, L"Praat picture: Marks right", L"Marks left/right/top/bottom...") {
+	dia_marks (dia); OK2 DO do_marks (dia, Graphics_marksRight); END2 }
+FORM (Marks_bottom, L"Praat picture: Marks bottom", L"Marks left/right/top/bottom...") {
+	dia_marks (dia); OK2 DO do_marks (dia, Graphics_marksBottom); END2 }
+FORM (Marks_top, L"Praat picture: Marks top", L"Marks left/right/top/bottom...") {
+	dia_marks (dia); OK2 DO do_marks (dia, Graphics_marksTop); END2 }
 
 static void dia_marksLogarithmic (Any dia) {
 	NATURAL (L"Marks per decade", L"3")
@@ -989,14 +1045,14 @@ static void do_marksLogarithmic (Any dia, void (*Graphics_marksLogarithmic) (Gra
 	Graphics_marksLogarithmic (GRAPHICS, numberOfMarksPerDecade, GET_INTEGER (L"Write numbers"),
 		GET_INTEGER (L"Draw ticks"), GET_INTEGER (L"Draw dotted lines"));
 }
-FORM (marksLeftLogarithmic, L"Praat picture: Logarithmic marks left", L"Logarithmic marks left/right/top/bottom...")
-	dia_marksLogarithmic (dia); OK DO do_marksLogarithmic (dia, Graphics_marksLeftLogarithmic); END
-FORM (marksRightLogarithmic, L"Praat picture: Logarithmic marks right", L"Logarithmic marks left/right/top/bottom...")
-	dia_marksLogarithmic (dia); OK DO do_marksLogarithmic (dia, Graphics_marksRightLogarithmic); END
-FORM (marksBottomLogarithmic, L"Praat picture: Logarithmic marks bottom", L"Logarithmic marks left/right/top/bottom...")
-	dia_marksLogarithmic (dia); OK DO do_marksLogarithmic (dia, Graphics_marksBottomLogarithmic); END
-FORM (marksTopLogarithmic, L"Praat picture: Logarithmic marks top", L"Logarithmic marks left/right/top/bottom...")
-	dia_marksLogarithmic (dia); OK DO do_marksLogarithmic (dia, Graphics_marksTopLogarithmic); END
+FORM (marksLeftLogarithmic, L"Praat picture: Logarithmic marks left", L"Logarithmic marks left/right/top/bottom...") {
+	dia_marksLogarithmic (dia); OK2 DO do_marksLogarithmic (dia, Graphics_marksLeftLogarithmic); END2 }
+FORM (marksRightLogarithmic, L"Praat picture: Logarithmic marks right", L"Logarithmic marks left/right/top/bottom...") {
+	dia_marksLogarithmic (dia); OK2 DO do_marksLogarithmic (dia, Graphics_marksRightLogarithmic); END2 }
+FORM (marksBottomLogarithmic, L"Praat picture: Logarithmic marks bottom", L"Logarithmic marks left/right/top/bottom...") {
+	dia_marksLogarithmic (dia); OK2 DO do_marksLogarithmic (dia, Graphics_marksBottomLogarithmic); END2 }
+FORM (marksTopLogarithmic, L"Praat picture: Logarithmic marks top", L"Logarithmic marks left/right/top/bottom...") {
+	dia_marksLogarithmic (dia); OK2 DO do_marksLogarithmic (dia, Graphics_marksTopLogarithmic); END2 }
 
 static void sortBoundingBox (double *x1WC, double *x2WC, double *y1WC, double *y2WC) {
 	double temp;
@@ -1012,13 +1068,13 @@ static void dia_oneMark (Any dia) {
 	LABEL (L"", L"Draw text:")
 	TEXTFIELD (L"text", L"")
 }
-FORM (Mark_left, L"Praat picture: One mark left", L"One mark left/right/top/bottom...")
+FORM (Mark_left, L"Praat picture: One mark left", L"One mark left/right/top/bottom...") {
 	dia_oneMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dy;
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1030,15 +1086,15 @@ DO
 	Graphics_markLeft (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
-FORM (Mark_right, L"Praat picture: One mark right", L"One mark left/right/top/bottom...")
+FORM (Mark_right, L"Praat picture: One mark right", L"One mark left/right/top/bottom...") {
 	dia_oneMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dy;
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1050,15 +1106,15 @@ DO
 	Graphics_markRight (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
-FORM (Mark_top, L"Praat picture: One mark top", L"One mark left/right/top/bottom...")
+FORM (Mark_top, L"Praat picture: One mark top", L"One mark left/right/top/bottom...") {
 	dia_oneMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dx;
-	{
+	{// scope
 		autoPraatPicture picture;   // WHY?
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1070,15 +1126,15 @@ DO
 	Graphics_markTop (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
-FORM (Mark_bottom, L"Praat picture: One mark bottom", L"One mark left/right/top/bottom...")
+FORM (Mark_bottom, L"Praat picture: One mark bottom", L"One mark left/right/top/bottom...") {
 	dia_oneMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dx;
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1090,7 +1146,7 @@ DO
 	Graphics_markBottom (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
 static void dia_oneLogarithmicMark (Any dia) {
 	REAL (L"Position", L"1.0")
@@ -1100,13 +1156,13 @@ static void dia_oneLogarithmicMark (Any dia) {
 	LABEL (L"", L"Draw text:")
 	TEXTFIELD (L"text", L"")
 }
-FORM (LogarithmicMark_left, L"Praat picture: One logarithmic mark left", L"One logarithmic mark left/right/top/bottom...")
+FORM (LogarithmicMark_left, L"Praat picture: One logarithmic mark left", L"One logarithmic mark left/right/top/bottom...") {
 	dia_oneLogarithmicMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dy;
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1118,15 +1174,15 @@ DO
 	Graphics_markLeftLogarithmic (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
-FORM (LogarithmicMark_right, L"Praat picture: One logarithmic mark right", L"One logarithmic mark left/right/top/bottom...")
+FORM (LogarithmicMark_right, L"Praat picture: One logarithmic mark right", L"One logarithmic mark left/right/top/bottom...") {
 	dia_oneLogarithmicMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dy;
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1138,15 +1194,15 @@ DO
 	Graphics_markRightLogarithmic (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
-FORM (LogarithmicMark_top, L"Praat picture: One logarithmic mark top", L"One logarithmic mark left/right/top/bottom...")
+FORM (LogarithmicMark_top, L"Praat picture: One logarithmic mark top", L"One logarithmic mark left/right/top/bottom...") {
 	dia_oneLogarithmicMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dx;
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1158,15 +1214,15 @@ DO
 	Graphics_markTopLogarithmic (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
-FORM (LogarithmicMark_bottom, L"Praat picture: One logarithmic mark bottom", L"One logarithmic mark left/right/top/bottom...")
+FORM (LogarithmicMark_bottom, L"Praat picture: One logarithmic mark bottom", L"One logarithmic mark left/right/top/bottom...") {
 	dia_oneLogarithmicMark (dia);
-OK
+OK2
 DO
 	double position = GET_REAL (L"Position");
 	double x1WC, x2WC, y1WC, y2WC, dx;
-	{
+	{// scope
 		autoPraatPicture picture;
 		Graphics_inqWindow (GRAPHICS, & x1WC, & x2WC, & y1WC, & y2WC);
 	}
@@ -1178,11 +1234,11 @@ DO
 	Graphics_markBottomLogarithmic (GRAPHICS, position, GET_INTEGER (L"Write number"),
 		GET_INTEGER (L"Draw tick"), GET_INTEGER (L"Draw dotted line"),
 		GET_STRING (L"text"));
-END
+END2 }
 
-FORM (dxMMtoWC, L"Compute horizontal distance in world coordinates", 0)
+FORM (dxMMtoWC, L"Compute horizontal distance in world coordinates", 0) {
 	REAL (L"Distance (mm)", L"10.0")
-OK
+OK2
 DO
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
 	Graphics_setViewport (GRAPHICS, theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC);
@@ -1190,11 +1246,11 @@ DO
 	double wc = Graphics_dxMMtoWC (GRAPHICS, GET_REAL (L"Distance"));
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (wc, L"(world coordinates)");
-END
+END2 }
 
-FORM (dxWCtoMM, L"Compute horizontal distance in millimetres", 0)
+FORM (dxWCtoMM, L"Compute horizontal distance in millimetres", 0) {
 	REAL (L"Distance (wc)", L"0.1")
-OK
+OK2
 DO
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
 	Graphics_setViewport (GRAPHICS, theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC);
@@ -1202,11 +1258,11 @@ DO
 	double mm = Graphics_dxWCtoMM (GRAPHICS, GET_REAL (L"Distance"));
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (mm, L"mm");
-END
+END2 }
 
-FORM (dyMMtoWC, L"Compute vertical distance in world coordinates", 0)
+FORM (dyMMtoWC, L"Compute vertical distance in world coordinates", 0) {
 	REAL (L"Distance (mm)", L"10.0")
-OK
+OK2
 DO
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
 	Graphics_setViewport (GRAPHICS, theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC);
@@ -1214,11 +1270,11 @@ DO
 	double wc = Graphics_dyMMtoWC (GRAPHICS, GET_REAL (L"Distance"));
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (wc, L"(world coordinates)");
-END
+END2 }
 
-FORM (dyWCtoMM, L"Compute vertical distance in millimetres", 0)
+FORM (dyWCtoMM, L"Compute vertical distance in millimetres", 0) {
 	REAL (L"Distance (wc)", L"1.0")
-OK
+OK2
 DO
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
 	Graphics_setViewport (GRAPHICS, theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC);
@@ -1226,11 +1282,11 @@ DO
 	double mm = Graphics_dyWCtoMM (GRAPHICS, GET_REAL (L"Distance"));
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (mm, L"mm");
-END
+END2 }
 
-FORM (textWidth_wc, L"Text width in world coordinates", 0)
+FORM (textWidth_wc, L"Text width in world coordinates", 0) {
 	TEXTFIELD (L"text", L"Hello world")
-OK
+OK2
 DO
 	Graphics_setFont (GRAPHICS, static_cast<kGraphics_font> (theCurrentPraatPicture -> font));
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
@@ -1239,11 +1295,11 @@ DO
 	double wc = Graphics_textWidth (GRAPHICS, GET_STRING (L"text"));
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (wc, L"(world coordinates)");
-END
+END2 }
 
-FORM (textWidth_mm, L"Text width in millimetres", 0)
+FORM (textWidth_mm, L"Text width in millimetres", 0) {
 	TEXTFIELD (L"text", L"Hello world")
-OK
+OK2
 DO
 	Graphics_setFont (GRAPHICS, static_cast<kGraphics_font> (theCurrentPraatPicture -> font));
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
@@ -1252,14 +1308,14 @@ DO
 	double mm = Graphics_dxWCtoMM (GRAPHICS, Graphics_textWidth (GRAPHICS, GET_STRING (L"text")));
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (mm, L"mm");
-END
+END2 }
 
-FORM (textWidth_ps_wc, L"PostScript text width in world coordinates", 0)
+FORM (textWidth_ps_wc, L"PostScript text width in world coordinates", 0) {
 	RADIO (L"Phonetic font", 1)
 		RADIOBUTTON (L"XIPA")
 		RADIOBUTTON (L"SILIPA")
 	TEXTFIELD (L"text", L"Hello world")
-OK
+OK2
 DO
 	Graphics_setFont (GRAPHICS, static_cast<kGraphics_font> (theCurrentPraatPicture -> font));
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
@@ -1268,14 +1324,14 @@ DO
 	double wc = Graphics_textWidth_ps (GRAPHICS, GET_STRING (L"text"), GET_INTEGER (L"Phonetic font") - 1);
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (wc, L"(world coordinates)");
-END
+END2 }
 
-FORM (textWidth_ps_mm, L"PostScript text width in millimetres", 0)
+FORM (textWidth_ps_mm, L"PostScript text width in millimetres", 0) {
 	RADIO (L"Phonetic font", 1)
 		RADIOBUTTON (L"XIPA")
 		RADIOBUTTON (L"SILIPA")
 	TEXTFIELD (L"text", L"Hello world")
-OK
+OK2
 DO
 	Graphics_setFont (GRAPHICS, static_cast<kGraphics_font> (theCurrentPraatPicture -> font));
 	Graphics_setFontSize (GRAPHICS, theCurrentPraatPicture -> fontSize);
@@ -1284,27 +1340,52 @@ DO
 	double mm = Graphics_textWidth_ps_mm (GRAPHICS, GET_STRING (L"text"), GET_INTEGER (L"Phonetic font") - 1);
 	Graphics_unsetInner (GRAPHICS);
 	Melder_informationReal (mm, L"mm");
-END
-
-DIRECT (SearchManual) Melder_search (); END
-DIRECT (PictureWindowHelp) Melder_help (L"Picture window"); END
-DIRECT (AboutSpecialSymbols) Melder_help (L"Special symbols"); END
-DIRECT (AboutTextStyles) Melder_help (L"Text styles"); END
-DIRECT (PhoneticSymbols) Melder_help (L"Phonetic symbols"); END
-DIRECT (Picture_settings_report)
+END2 }
+
+DIRECT (SearchManual) { Melder_search (); } END
+DIRECT (PictureWindowHelp) { Melder_help (L"Picture window"); } END
+DIRECT (AboutSpecialSymbols) { Melder_help (L"Special symbols"); } END
+DIRECT (AboutTextStyles) { Melder_help (L"Text styles"); } END
+DIRECT (PhoneticSymbols) { Melder_help (L"Phonetic symbols"); } END
+DIRECT (Picture_settings_report) {
 	MelderInfo_open ();
-	MelderInfo_writeLine (L"Outer viewport left: ", Melder_double (theCurrentPraatPicture -> x1NDC), L" inches");
-	MelderInfo_writeLine (L"Outer viewport right: ", Melder_double (theCurrentPraatPicture -> x2NDC), L" inches");
-	MelderInfo_writeLine (L"Outer viewport top: ", Melder_double (12 - theCurrentPraatPicture -> y2NDC), L" inches");
-	MelderInfo_writeLine (L"Outer viewport bottom: ", Melder_double (12 - theCurrentPraatPicture -> y1NDC), L" inches");
+	const wchar_t *units = theCurrentPraatPicture == & theForegroundPraatPicture ? L" inches" : L"";
+	MelderInfo_writeLine (L"Outer viewport left: ", Melder_double (theCurrentPraatPicture -> x1NDC), units);
+	MelderInfo_writeLine (L"Outer viewport right: ", Melder_double (theCurrentPraatPicture -> x2NDC), units);
+	MelderInfo_writeLine (L"Outer viewport top: ", Melder_double (
+		theCurrentPraatPicture != & theForegroundPraatPicture ?
+			theCurrentPraatPicture -> y1NDC :
+			12 - theCurrentPraatPicture -> y2NDC), units);
+	MelderInfo_writeLine (L"Outer viewport bottom: ", Melder_double (
+		theCurrentPraatPicture != & theForegroundPraatPicture ?
+			theCurrentPraatPicture -> y2NDC :
+			12 - theCurrentPraatPicture -> y1NDC), units);
 	MelderInfo_writeLine (L"Font size: ", Melder_double (theCurrentPraatPicture -> fontSize), L" points");
 	double xmargin = theCurrentPraatPicture -> fontSize * 4.2 / 72.0, ymargin = theCurrentPraatPicture -> fontSize * 2.8 / 72.0;
-	if (ymargin > 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC)) ymargin = 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC);
-	if (xmargin > 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC)) xmargin = 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC);
-	MelderInfo_writeLine (L"Inner viewport left: ", Melder_double (theCurrentPraatPicture -> x1NDC + xmargin), L" inches");
-	MelderInfo_writeLine (L"Inner viewport right: ", Melder_double (theCurrentPraatPicture -> x2NDC - xmargin), L" inches");
-	MelderInfo_writeLine (L"Inner viewport top: ", Melder_double (12 - theCurrentPraatPicture -> y2NDC + ymargin), L" inches");
-	MelderInfo_writeLine (L"Inner viewport bottom: ", Melder_double (12 - theCurrentPraatPicture -> y1NDC - ymargin), L" inches");
+	if (theCurrentPraatPicture != & theForegroundPraatPicture) {
+		long x1DC, x2DC, y1DC, y2DC;
+		Graphics_inqWsViewport (GRAPHICS, & x1DC, & x2DC, & y1DC, & y2DC);
+		double x1wNDC, x2wNDC, y1wNDC, y2wNDC;
+		Graphics_inqWsWindow (GRAPHICS, & x1wNDC, & x2wNDC, & y1wNDC, & y2wNDC);
+		double wDC = (x2DC - x1DC) / (x2wNDC - x1wNDC);
+		double hDC = abs (y2DC - y1DC) / (y2wNDC - y1wNDC);
+		xmargin *= Graphics_getResolution (GRAPHICS) / wDC;
+		ymargin *= Graphics_getResolution (GRAPHICS) / hDC;
+	}
+	if (ymargin > 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC))
+		ymargin = 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC);
+	if (xmargin > 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC))
+		xmargin = 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC);
+	MelderInfo_writeLine (L"Inner viewport left: ", Melder_double (theCurrentPraatPicture -> x1NDC + xmargin), units);
+	MelderInfo_writeLine (L"Inner viewport right: ", Melder_double (theCurrentPraatPicture -> x2NDC - xmargin), units);
+	MelderInfo_writeLine (L"Inner viewport top: ", Melder_double (
+		theCurrentPraatPicture != & theForegroundPraatPicture ?
+			theCurrentPraatPicture -> y1NDC + ymargin :
+			12 - theCurrentPraatPicture -> y2NDC + ymargin), units);
+	MelderInfo_writeLine (L"Inner viewport bottom: ", Melder_double (
+		theCurrentPraatPicture != & theForegroundPraatPicture ?
+			theCurrentPraatPicture -> y2NDC - ymargin :
+			12 - theCurrentPraatPicture -> y1NDC - ymargin), units);
 	MelderInfo_writeLine (L"Font: ", kGraphics_font_getText (theCurrentPraatPicture -> font));
 	MelderInfo_writeLine (L"Line type: ",
 		theCurrentPraatPicture -> lineType == Graphics_DRAWN ? L"Solid" :
@@ -1314,6 +1395,7 @@ DIRECT (Picture_settings_report)
 		L"(unknown)");
 	MelderInfo_writeLine (L"Line width: ", Melder_double (theCurrentPraatPicture -> lineWidth));
 	MelderInfo_writeLine (L"Arrow size: ", Melder_double (theCurrentPraatPicture -> arrowSize));
+	MelderInfo_writeLine (L"Speckle size: ", Melder_double (theCurrentPraatPicture -> speckleSize));
 	MelderInfo_writeLine (L"Colour: ", Graphics_Colour_name (theCurrentPraatPicture -> colour));
 	MelderInfo_writeLine (L"Red: ", Melder_double (theCurrentPraatPicture -> colour. red));
 	MelderInfo_writeLine (L"Green: ", Melder_double (theCurrentPraatPicture -> colour. green));
@@ -1325,7 +1407,7 @@ DIRECT (Picture_settings_report)
 	MelderInfo_writeLine (L"Axis bottom: ", Melder_double (y1WC));
 	MelderInfo_writeLine (L"Axis top: ", Melder_double (y2WC));
 	MelderInfo_close ();
-END
+} END
 
 
 /**********   **********/
@@ -1345,7 +1427,7 @@ static void cb_selectionChanged (Picture p, void *closure,
 		double xmargin = fontSize * 4.2 / 72.0, ymargin = fontSize * 2.8 / 72.0;
 		if (ymargin > 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC)) ymargin = 0.4 * (theCurrentPraatPicture -> y2NDC - theCurrentPraatPicture -> y1NDC);
 		if (xmargin > 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC)) xmargin = 0.4 * (theCurrentPraatPicture -> x2NDC - theCurrentPraatPicture -> x1NDC);
-		UiHistory_write (L"\ndo (\"Select inner viewport...\", ");
+		UiHistory_write (L"\nSelect inner viewport: ");
 		UiHistory_write (Melder_single (theCurrentPraatPicture -> x1NDC + xmargin));
 		UiHistory_write (L", ");
 		UiHistory_write (Melder_single (theCurrentPraatPicture -> x2NDC - xmargin));
@@ -1353,9 +1435,8 @@ static void cb_selectionChanged (Picture p, void *closure,
 		UiHistory_write (Melder_single (12 - theCurrentPraatPicture -> y2NDC + ymargin));
 		UiHistory_write (L", ");
 		UiHistory_write (Melder_single (12 - theCurrentPraatPicture -> y1NDC - ymargin));
-		UiHistory_write (L")");
 	} else {
-		UiHistory_write (L"\ndo (\"Select outer viewport...\", ");
+		UiHistory_write (L"\nSelect outer viewport: ");
 		UiHistory_write (Melder_single (theCurrentPraatPicture -> x1NDC));
 		UiHistory_write (L", ");
 		UiHistory_write (Melder_single (theCurrentPraatPicture -> x2NDC));
@@ -1363,7 +1444,6 @@ static void cb_selectionChanged (Picture p, void *closure,
 		UiHistory_write (Melder_single (12 - theCurrentPraatPicture -> y2NDC));
 		UiHistory_write (L", ");
 		UiHistory_write (Melder_single (12 - theCurrentPraatPicture -> y1NDC));
-		UiHistory_write (L")");
 	}
 }
 
@@ -1414,6 +1494,7 @@ void praat_picture_open (void) {
 	Graphics_setLineType (GRAPHICS, theCurrentPraatPicture -> lineType);
 	Graphics_setLineWidth (GRAPHICS, theCurrentPraatPicture -> lineWidth);
 	Graphics_setArrowSize (GRAPHICS, theCurrentPraatPicture -> arrowSize);
+	Graphics_setSpeckleSize (GRAPHICS, theCurrentPraatPicture -> speckleSize);
 	Graphics_setColour (GRAPHICS, theCurrentPraatPicture -> colour);
 
 	Graphics_setViewport (GRAPHICS, theCurrentPraatPicture -> x1NDC, theCurrentPraatPicture -> x2NDC, theCurrentPraatPicture -> y1NDC, theCurrentPraatPicture -> y2NDC);
@@ -1427,6 +1508,9 @@ void praat_picture_close (void) {
 	if (theCurrentPraatPicture != & theForegroundPraatPicture) return;
 	if (! theCurrentPraatApplication -> batch) {
 		Picture_highlight (praat_picture);
+		#ifdef macintosh
+			//dialog -> f_drain ();
+		#endif
 	}
 }
 
@@ -1458,6 +1542,7 @@ void praat_picture_init (void) {
 	theCurrentPraatPicture -> colour = Graphics_BLACK;
 	theCurrentPraatPicture -> lineWidth = 1.0;
 	theCurrentPraatPicture -> arrowSize = 1.0;
+	theCurrentPraatPicture -> speckleSize = 1.0;
 	theCurrentPraatPicture -> x1NDC = 0.0;
 	theCurrentPraatPicture -> x2NDC = 6.0;
 	theCurrentPraatPicture -> y1NDC = 8.0;
@@ -1490,7 +1575,7 @@ void praat_picture_init (void) {
 			width += margin * 2;
 		#endif
 		sprintf (pictureWindowTitle, "%s Picture", praatP.title);
-		dialog = GuiWindow_create (x, y, width, height, Melder_peekUtf8ToWcs (pictureWindowTitle), NULL, NULL, 0);
+		dialog = GuiWindow_create (x, y, width, height, 400, 200, Melder_peekUtf8ToWcs (pictureWindowTitle), NULL, NULL, 0);
 		dialog -> f_addMenuBar ();
 	}
 	if (! theCurrentPraatApplication -> batch) {
@@ -1506,35 +1591,34 @@ void praat_picture_init (void) {
 
 	praat_addMenuCommand (L"Picture", L"File", L"Picture info", 0, 0, DO_Picture_settings_report);
 	praat_addMenuCommand (L"Picture", L"File", L"Picture settings report", 0, praat_HIDDEN, DO_Picture_settings_report);
-	praat_addMenuCommand (L"Picture", L"File", L"-- read --", 0, 0, 0);
-	praat_addMenuCommand (L"Picture", L"File", L"Read from praat picture file...", 0, 0, DO_Picture_readFromPraatPictureFile);
-	praat_addMenuCommand (L"Picture", L"File", L"-- write --", 0, 0, 0);
-	praat_addMenuCommand (L"Picture", L"File", L"Save as praat picture file...", 0, 0, DO_Picture_writeToPraatPictureFile);
-	praat_addMenuCommand (L"Picture", L"File", L"Write to praat picture file...", 0, praat_HIDDEN, DO_Picture_writeToPraatPictureFile);
-	#ifdef _WIN32
-	praat_addMenuCommand (L"Picture", L"File", L"Save as Windows metafile...", 0, 0, DO_Picture_writeToWindowsMetafile);
-	praat_addMenuCommand (L"Picture", L"File", L"Write to Windows metafile...", 0, praat_HIDDEN, DO_Picture_writeToWindowsMetafile);
-	#endif
-	#if defined (macintosh)
+	praat_addMenuCommand (L"Picture", L"File", L"-- save --", 0, 0, 0);
+	#if defined (macintosh) || defined (UNIX)
 		praat_addMenuCommand (L"Picture", L"File", L"Save as PDF file...", 0, 'S', DO_Picture_writeToPdfFile);
 		praat_addMenuCommand (L"Picture", L"File", L"Write to PDF file...", 0, praat_HIDDEN, DO_Picture_writeToPdfFile);
-		praat_addMenuCommand (L"Picture", L"File", L"Save EPS file", 0, 0, NULL);
-			praat_addMenuCommand (L"Picture", L"File", L"PostScript settings...", 0, 1, DO_PostScript_settings);
-			praat_addMenuCommand (L"Picture", L"File", L"Save as EPS file...", 0, 1, DO_Picture_writeToEpsFile);
-			praat_addMenuCommand (L"Picture", L"File", L"Write to EPS file...", 0, praat_HIDDEN + praat_DEPTH_1, DO_Picture_writeToEpsFile);
-			praat_addMenuCommand (L"Picture", L"File", L"Save as fontless EPS file (XIPA)...", 0, 1, DO_Picture_writeToFontlessEpsFile_xipa);
-			praat_addMenuCommand (L"Picture", L"File", L"Write to fontless EPS file (XIPA)...", 0, praat_HIDDEN + praat_DEPTH_1, DO_Picture_writeToFontlessEpsFile_xipa);
-			praat_addMenuCommand (L"Picture", L"File", L"Save as fontless EPS file (SILIPA)...", 0, 1, DO_Picture_writeToFontlessEpsFile_silipa);
-			praat_addMenuCommand (L"Picture", L"File", L"Write to fontless EPS file (SILIPA)...", 0, praat_HIDDEN + praat_DEPTH_1, DO_Picture_writeToFontlessEpsFile_silipa);
-	#else
-		praat_addMenuCommand (L"Picture", L"File", L"PostScript settings...", 0, 0, DO_PostScript_settings);
-		praat_addMenuCommand (L"Picture", L"File", L"Save as EPS file...", 0, 'S', DO_Picture_writeToEpsFile);
-		praat_addMenuCommand (L"Picture", L"File", L"Write to EPS file...", 0, praat_HIDDEN, DO_Picture_writeToEpsFile);
-		praat_addMenuCommand (L"Picture", L"File", L"Save as fontless EPS file (XIPA)...", 0, 0, DO_Picture_writeToFontlessEpsFile_xipa);
-		praat_addMenuCommand (L"Picture", L"File", L"Write to fontless EPS file (XIPA)...", 0, praat_HIDDEN, DO_Picture_writeToFontlessEpsFile_xipa);
-		praat_addMenuCommand (L"Picture", L"File", L"Save as fontless EPS file (SILIPA)...", 0, 0, DO_Picture_writeToFontlessEpsFile_silipa);
-		praat_addMenuCommand (L"Picture", L"File", L"Write to fontless EPS file (SILIPA)...", 0, praat_HIDDEN, DO_Picture_writeToFontlessEpsFile_silipa);
 	#endif
+	praat_addMenuCommand (L"Picture", L"File", L"Save as 300-dpi PNG file...", 0, 0, DO_Picture_writeToPngFile_300);
+	#if defined (_WIN32)
+		praat_addMenuCommand (L"Picture", L"File", L"Save as 600-dpi PNG file...", 0, 'S', DO_Picture_writeToPngFile_600);
+	#endif
+	#if defined (macintosh) || defined (UNIX)
+		praat_addMenuCommand (L"Picture", L"File", L"Save as 600-dpi PNG file...", 0, 0, DO_Picture_writeToPngFile_600);
+	#endif
+	praat_addMenuCommand (L"Picture", L"File", L"Save as EPS file", 0, 0, NULL);
+		praat_addMenuCommand (L"Picture", L"File", L"PostScript settings...", 0, 1, DO_PostScript_settings);
+		praat_addMenuCommand (L"Picture", L"File", L"Save as EPS file...", 0, 1, DO_Picture_writeToEpsFile);
+		praat_addMenuCommand (L"Picture", L"File", L"Write to EPS file...", 0, praat_HIDDEN + praat_DEPTH_1, DO_Picture_writeToEpsFile);
+		praat_addMenuCommand (L"Picture", L"File", L"Save as fontless EPS file (XIPA)...", 0, 1, DO_Picture_writeToFontlessEpsFile_xipa);
+		praat_addMenuCommand (L"Picture", L"File", L"Write to fontless EPS file (XIPA)...", 0, praat_HIDDEN + praat_DEPTH_1, DO_Picture_writeToFontlessEpsFile_xipa);
+		praat_addMenuCommand (L"Picture", L"File", L"Save as fontless EPS file (SILIPA)...", 0, 1, DO_Picture_writeToFontlessEpsFile_silipa);
+		praat_addMenuCommand (L"Picture", L"File", L"Write to fontless EPS file (SILIPA)...", 0, praat_HIDDEN + praat_DEPTH_1, DO_Picture_writeToFontlessEpsFile_silipa);
+	#ifdef _WIN32
+		praat_addMenuCommand (L"Picture", L"File", L"Save as Windows metafile...", 0, 0, DO_Picture_writeToWindowsMetafile);
+		praat_addMenuCommand (L"Picture", L"File", L"Write to Windows metafile...", 0, praat_HIDDEN, DO_Picture_writeToWindowsMetafile);
+	#endif
+	praat_addMenuCommand (L"Picture", L"File", L"-- praat picture file --", 0, 0, 0);
+	praat_addMenuCommand (L"Picture", L"File", L"Read from praat picture file...", 0, 0, DO_Picture_readFromPraatPictureFile);
+	praat_addMenuCommand (L"Picture", L"File", L"Save as praat picture file...", 0, 0, DO_Picture_writeToPraatPictureFile);
+	praat_addMenuCommand (L"Picture", L"File", L"Write to praat picture file...", 0, praat_HIDDEN, DO_Picture_writeToPraatPictureFile);
 	praat_addMenuCommand (L"Picture", L"File", L"-- print --", 0, 0, 0);
 	#if defined (macintosh)
 		praat_addMenuCommand (L"Picture", L"File", L"Page setup...", 0, 0, DO_Page_setup);
@@ -1605,10 +1689,8 @@ void praat_picture_init (void) {
 	praat_addMenuCommand (L"Picture", L"World", L"Paint circle...", 0, 0, DO_PaintCircle);
 	praat_addMenuCommand (L"Picture", L"World", L"Draw circle (mm)...", 0, 0, DO_DrawCircle_mm);
 	praat_addMenuCommand (L"Picture", L"World", L"Paint circle (mm)...", 0, 0, DO_PaintCircle_mm);
-	#if defined (macintosh) || defined (_WIN32)
-		praat_addMenuCommand (L"Picture", L"World", L"-- picture --", 0, 0, 0);
-		praat_addMenuCommand (L"Picture", L"World", L"Insert picture from file...", 0, 0, DO_InsertPictureFromFile);
-	#endif
+	praat_addMenuCommand (L"Picture", L"World", L"-- picture --", 0, 0, 0);
+	praat_addMenuCommand (L"Picture", L"World", L"Insert picture from file...", 0, 0, DO_InsertPictureFromFile);
 	praat_addMenuCommand (L"Picture", L"World", L"-- axes --", 0, 0, 0);
 	praat_addMenuCommand (L"Picture", L"World", L"Axes...", 0, 0, DO_Axes);
 	praat_addMenuCommand (L"Picture", L"World", L"Measure", 0, 0, 0);
@@ -1639,6 +1721,7 @@ void praat_picture_init (void) {
 	praat_addMenuCommand (L"Picture", L"Pen", L"-- line width --", 0, 0, 0);
 	praat_addMenuCommand (L"Picture", L"Pen", L"Line width...", 0, 0, DO_Line_width);
 	praat_addMenuCommand (L"Picture", L"Pen", L"Arrow size...", 0, 0, DO_Arrow_size);
+	praat_addMenuCommand (L"Picture", L"Pen", L"Speckle size...", 0, 0, DO_Speckle_size);
 	praat_addMenuCommand (L"Picture", L"Pen", L"-- colour --", 0, 0, 0);
 	praat_addMenuCommand (L"Picture", L"Pen", L"Colour...", 0, 0, DO_Colour);
 	praatButton_black = praat_addMenuCommand (L"Picture", L"Pen", L"Black", 0, praat_CHECKBUTTON, DO_Black);
@@ -1708,13 +1791,23 @@ void praat_picture_prefsChanged (void) {
 }
 
 void praat_picture_background (void) {
-	/*praat_picture_open ();
-	Picture_background (praat_picture);*/
+	if (theCurrentPraatPicture != & theForegroundPraatPicture) return;   // Demo window and pictures ignore this
+	if (! theCurrentPraatApplication -> batch) {
+		//Picture_unhighlight (praat_picture);
+		#if cocoa
+			Picture_background (praat_picture);   // prevent Cocoa's very slow highlighting until woken up by Picture_foreground()
+		#endif
+	}
 }
 
 void praat_picture_foreground (void) {
-	/*praat_picture_close ();
-	Picture_foreground (praat_picture);*/
+	if (theCurrentPraatPicture != & theForegroundPraatPicture) return;   // Demo window and pictures ignore this
+	if (! theCurrentPraatApplication -> batch) {
+		#if cocoa
+			Picture_foreground (praat_picture);   // wake up from the highlighting sleep caused by Picture_background()
+		#endif
+		//Picture_highlight (praat_picture);
+	}
 }
 
 /* End of file praat_picture.cpp */
diff --git a/sys/praat_script.cpp b/sys/praat_script.cpp
index 37873d4..61611db 100644
--- a/sys/praat_script.cpp
+++ b/sys/praat_script.cpp
@@ -1,6 +1,6 @@
 /* praat_script.cpp
  *
- * Copyright (C) 1993-2012,2013 Paul Boersma
+ * Copyright (C) 1993-2012,2013,2014 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,28 +17,6 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-/*
- * pb 2002/03/07 GPL
- * pb 2002/10/02 system -> Melder_system
- * pb 2003/03/09 set UiInterpreter back to NULL
- * pb 2004/02/22 allow numeric expressions after select/plus/minus
- * pb 2004/10/27 warning off
- * pb 2004/12/04 support for multiple open script dialogs with Apply buttons, both from "Run script..." and from added buttons
- * pb 2005/02/10 corrected bug in nowarn
- * pb 2005/08/22 renamed Control menu to "Praat"
- * pb 2006/01/11 local variables
- * pb 2006/12/28 theCurrentPraat
- * pb 2007/02/17 corrected the messages about trailing spaces
- * pb 2007/06/11 wchar_t
- * pb 2007/10/04 removed swscanf
- * pb 2009/01/04 allow proc(args) syntax
- * pb 2009/01/17 arguments to UiForm callbacks
- * pb 2009/01/20 pause uses a pause form
- * pb 2011/03/20 C++
- * pb 2011/03/24 command no longer const
- * pb 2011/07/05 C++
- */
-
 #include <ctype.h>
 #include "praatP.h"
 #include "praat_script.h"
@@ -108,7 +86,75 @@ Editor praat_findEditorFromString (const wchar_t *string) {
 	Melder_throw ("Editor \"", string, "\" does not exist.");
 }
 
-void praat_executeCommand (Interpreter interpreter, wchar_t *command) {
+Editor praat_findEditorById (long id) {
+	int IOBJECT;
+	WHERE (1) {
+		if (ID == id) {
+			for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
+				Editor editor = (Editor) theCurrentPraatObjects -> list [IOBJECT]. editors [ieditor];
+				if (editor) return editor;
+			}
+		}
+	}
+	Melder_throw ("Editor ", id, " does not exist.");
+}
+
+static int parseCommaSeparatedArguments (Interpreter interpreter, wchar_t *arguments, structStackel args []) {
+	int narg = 0, depth = 0;
+	for (wchar_t *p = arguments; ; p ++) {
+		bool endOfArguments = *p == '\0';
+		if (endOfArguments || (*p == ',' && depth == 0)) {
+			if (narg == MAXIMUM_NUMBER_OF_FIELDS)
+				Melder_throw ("Cannot have more than ", MAXIMUM_NUMBER_OF_FIELDS, " arguments");
+			*p = '\0';
+			struct Formula_Result result;
+			Interpreter_anyExpression (interpreter, arguments, & result);
+			narg ++;
+			/*
+			 * First remove the old contents.
+			 */
+			switch (args [narg]. which) {
+				case Stackel_NUMBER: {
+					// do nothing
+				} break;
+				case Stackel_STRING: {
+					Melder_free (args [narg].string);
+				} break;
+			}
+			/*
+			 * Then copy in the new contents.
+			 */
+			switch (result. expressionType) {
+				case kFormula_EXPRESSION_TYPE_NUMERIC: {
+					args [narg]. which = Stackel_NUMBER;
+					args [narg]. number = result. result. numericResult;
+				} break;
+				case kFormula_EXPRESSION_TYPE_STRING: {
+					args [narg]. which = Stackel_STRING;
+					args [narg]. string = result. result. stringResult;
+				} break;
+			}
+			arguments = p + 1;
+		} else if (*p == '(' || *p == '[' || *p == '{') {
+			depth ++;
+		} else if (*p == ')' || *p == ']' || *p == '}') {
+			depth --;
+		} else if (*p == '\"') {
+			for (;;) {
+				p ++;
+				if (*p == '\"') {
+					if (p [1] == '\"') p ++;
+					else break;
+				}
+			}
+		}
+		if (endOfArguments) break;
+	}
+	return narg;
+}
+
+int praat_executeCommand (Interpreter interpreter, wchar_t *command) {
+	static struct structStackel args [1 + MAXIMUM_NUMBER_OF_FIELDS];
 	//Melder_casual ("praat_executeCommand: %ld: %ls", interpreter, command);
 	if (command [0] == '\0' || command [0] == '#' || command [0] == '!' || command [0] == ';')
 		/* Skip empty lines and comments. */;
@@ -200,13 +246,17 @@ void praat_executeCommand (Interpreter interpreter, wchar_t *command) {
 				praat_executeCommand (interpreter, command + 8);
 			} catch (MelderError) {
 				Melder_clearError ();
+				return 0;
 			}
 		} else if (wcsnequ (command, L"demo ", 5)) {
 			autoDemoOpen demo;
 			praat_executeCommand (interpreter, command + 5);
+		} else if (wcsnequ (command, L"asynchronous ", 13)) {
+			autoMelderAsynchronous asynchronous;
+			praat_executeCommand (interpreter, command + 13);
 		} else if (wcsnequ (command, L"pause ", 6) || wcsequ (command, L"pause")) {
 			if (theCurrentPraatApplication -> batch)
-				return;   // in batch we ignore pause statements
+				return 1;   // in batch we ignore pause statements
 			UiPause_begin (theCurrentPraatApplication -> topShell, L"stop or continue", interpreter);
 			UiPause_comment (wcsequ (command, L"pause") ? L"..." : command + 6);
 			UiPause_end (1, 1, 0, L"Continue", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, interpreter);
@@ -217,10 +267,14 @@ void praat_executeCommand (Interpreter interpreter, wchar_t *command) {
 				Melder_throw ("The script command \"editor\" is not available inside manuals.");
 			if (command [6] == ' ' && isalpha (command [7])) {
 				praatP. editor = praat_findEditorFromString (command + 7);
-			} else if (interpreter && interpreter -> editorClass) {
-				praatP. editor = praat_findEditorFromString (interpreter -> environmentName);
+			} else if (command [6] == '\0') {
+				if (interpreter && interpreter -> editorClass) {
+					praatP. editor = praat_findEditorFromString (interpreter -> environmentName);
+				} else {
+					Melder_throw ("The function \"editor\" requires an argument when called from outside an editor.");
+				}
 			} else {
-				Melder_throw ("No editor specified.");
+				Interpreter_voidExpression (interpreter, command);
 			}
 		} else if (wcsnequ (command, L"endeditor", 9)) {
 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
@@ -313,27 +367,60 @@ void praat_executeCommand (Interpreter interpreter, wchar_t *command) {
 			Interpreter_voidExpression (interpreter, command);
 		}
 	} else {   /* Simulate menu choice. */
-		wchar_t *arguments;
+		bool hasDots = false, hasColon = false;
 
  		/* Parse command line into command and arguments. */
-		/* The separation is formed by the three dots. */
+		/* The separation is formed by the three dots or a colon. */
 
-		if ((arguments = wcsstr (command, L"...")) == NULL || wcslen (arguments) < 4) {
-			static wchar_t dummy = { 0 };
-			arguments = & dummy;
-		} else {
-			arguments += 4;
-			if (arguments [-1] != ' ' && arguments [-1] != '0') {
-				Melder_throw ("There should be a space after the three dots.");
+		wchar_t *arguments = & command [0];
+		for (arguments = & command [0]; *arguments != '\0'; arguments ++) {
+			if (*arguments == ':') {
+				hasColon = true;
+				if (arguments [1] == '\0') {
+					arguments = & arguments [1];   // empty string
+				} else {
+					if (arguments [1] != ' ') {
+						Melder_throw ("There should be a space after the colon.");
+					}
+					arguments [1] = '\0';   // new end of "command"
+					arguments += 2;   // the arguments start after the space
+				}
+				break;
+			}
+			if (*arguments == '.' && arguments [1] == '.' && arguments [2] == '.') {
+				hasDots = true;
+				arguments += 3;
+				if (*arguments == '\0') {
+					// empty string
+				} else {
+					if (*arguments != ' ') {
+						Melder_throw ("There should be a space after the three dots.");
+					}
+					*arguments = '\0';   // new end of "command"
+					arguments ++;   // the arguments start after the space
+				}
+				break;
 			}
-			arguments [-1] = '\0'; // new end of "command"
 		}
 
 		/* See if command exists and is available; ignore separators. */
 		/* First try loose commands, then fixed commands. */
 
+		int narg;
+		wchar_t command2 [200];
+		if (hasColon) {
+			narg = parseCommaSeparatedArguments (interpreter, arguments, args);
+			wcscpy (command2, command);
+			wchar_t *colon = wcschr (command2, ':');
+			colon [0] = colon [1] = colon [2] = '.';
+			colon [3] = '\0';
+		}
 		if (theCurrentPraatObjects == & theForegroundPraatObjects && praatP. editor != NULL) {
-			Editor_doMenuCommand ((Editor) praatP. editor, command, 0, NULL, arguments, interpreter);
+			if (hasColon) {
+				Editor_doMenuCommand ((Editor) praatP. editor, command2, narg, args, NULL, interpreter);
+			} else {
+				Editor_doMenuCommand ((Editor) praatP. editor, command, 0, NULL, arguments, interpreter);
+			}
 		} else if (theCurrentPraatObjects != & theForegroundPraatObjects &&
 		    (wcsnequ (command, L"Save ", 5) ||
 			 wcsnequ (command, L"Write ", 6) ||
@@ -344,14 +431,18 @@ void praat_executeCommand (Interpreter interpreter, wchar_t *command) {
 		} else {
 			bool theCommandIsAnExistingAction = false;
 			try {
-				theCommandIsAnExistingAction = praat_doAction (command, arguments, interpreter);
+				if (hasColon) {
+					theCommandIsAnExistingAction = praat_doAction (command2, narg, args, interpreter);
+				} else {
+					theCommandIsAnExistingAction = praat_doAction (command, arguments, interpreter);
+				}
 			} catch (MelderError) {
 				/*
 				 * We only get here if the command *was* an existing action.
 				 * Anything could have gone wrong in its execution,
 				 * but one invisible problem can be checked here.
 				 */
-				if (arguments [0] != '\0' && arguments [wcslen (arguments) - 1] == ' ') {
+				if (hasDots && arguments [0] != '\0' && arguments [wcslen (arguments) - 1] == ' ') {
 					Melder_throw ("It may be helpful to remove the trailing spaces in \"", arguments, "\".");
 				} else {
 					throw;
@@ -360,14 +451,18 @@ void praat_executeCommand (Interpreter interpreter, wchar_t *command) {
 			if (! theCommandIsAnExistingAction) {
 				bool theCommandIsAnExistingMenuCommand = false;
 				try {
-					theCommandIsAnExistingMenuCommand = praat_doMenuCommand (command, arguments, interpreter);
+					if (hasColon) {
+						theCommandIsAnExistingMenuCommand = praat_doMenuCommand (command2, narg, args, interpreter);
+					} else {
+						theCommandIsAnExistingMenuCommand = praat_doMenuCommand (command, arguments, interpreter);
+					}
 				} catch (MelderError) {
 					/*
 					 * We only get here if the command *was* an existing menu command.
 					 * Anything could have gone wrong in its execution,
 					 * but one invisible problem can be checked here.
 					 */
-					if (arguments [0] != '\0' && arguments [wcslen (arguments) - 1] == ' ') {
+					if (hasDots && arguments [0] != '\0' && arguments [wcslen (arguments) - 1] == ' ') {
 						Melder_throw ("It may be helpful to remove the trailing spaces in \"", arguments, L"\".");
 					} else {
 						throw;
@@ -390,6 +485,7 @@ void praat_executeCommand (Interpreter interpreter, wchar_t *command) {
 		}
 		praat_updateSelection ();
 	}
+	return 1;
 }
 
 void praat_executeCommandFromStandardInput (const char *programName) {
@@ -423,7 +519,26 @@ void praat_executeScriptFromFile (MelderFile file, const wchar_t *arguments) {
 		}
 		Interpreter_run (interpreter.peek(), text.peek());
 	} catch (MelderError) {
-		Melder_throw ("Script ", file, " not completed.");
+		Melder_throw (L"Script ", file, L" not completed.");
+	}
+}
+
+void praat_executeScriptFromFileName (const wchar_t *fileName, int narg, Stackel args) {
+	/*
+	 * The argument 'fileName' is unsafe. Duplicate its contents.
+	 */
+	structMelderFile file = { 0 };
+	Melder_relativePathToFile (fileName, & file);
+	try {
+		autostring text = MelderFile_readText (& file);
+		autoMelderFileSetDefaultDir dir (& file);   // so that relative file names can be used inside the script
+		Melder_includeIncludeFiles (& text);
+		autoInterpreter interpreter = Interpreter_createFromEnvironment (praatP.editor);
+		Interpreter_readParameters (interpreter.peek(), text.peek());
+		Interpreter_getArgumentsFromArgs (interpreter.peek(), narg, args);
+		Interpreter_run (interpreter.peek(), text.peek());
+	} catch (MelderError) {
+		Melder_throw (L"Script ", & file, L" not completed.");   // don't refer to 'fileName', because its contents may have changed
 	}
 }
 
@@ -498,7 +613,7 @@ static void firstPassThroughScript (MelderFile file) {
 		if (Interpreter_readParameters (interpreter.peek(), text.peek()) > 0) {
 			Any form = Interpreter_createForm (interpreter.peek(),
 				praatP.editor ? ((Editor) praatP.editor) -> d_windowForm : theCurrentPraatApplication -> topShell,
-				Melder_fileToPath (file), secondPassThroughScript, NULL);
+				Melder_fileToPath (file), secondPassThroughScript, NULL, false);
 			UiForm_destroyWhenUnmanaged (form);
 			UiForm_do (form, false);
 		} else {
@@ -519,30 +634,6 @@ static void fileSelectorOkCallback (UiForm dia, int narg, Stackel args, const wc
 	firstPassThroughScript (UiFile_getFile (dia));
 }
 
-/*
- * DO_praat_runScript () is the command callback for "Run script...", which is a bit obsolete command,
- * hidden in the Praat menu, and otherwise replaced by "execute".
- */
-void DO_praat_runScript (UiForm sendingForm, int narg, Stackel args, const wchar_t *sendingString, Interpreter interpreter_dummy, const wchar_t *invokingButtonTitle, bool modified, void *dummy) {
-	(void) interpreter_dummy;
-	(void) modified;
-	(void) dummy;
-	if (sendingForm == NULL && sendingString == NULL) {
-		/*
-		 * User clicked the "Run script..." button in the Praat menu.
-		 */
-		static Any file_dialog;
-		if (! file_dialog)
-			file_dialog = UiInfile_create (theCurrentPraatApplication -> topShell, L"Praat: run script", fileSelectorOkCallback, NULL, invokingButtonTitle, NULL, false);
-		UiInfile_do (file_dialog);
-	} else {
-		/*
-		 * A script called "Run script..."
-		 */
-		praat_executeScriptFromFileNameWithArguments (sendingString);
-	}
-}
-
 void DO_RunTheScriptFromAnyAddedMenuCommand (UiForm sendingForm_dummy, int narg, Stackel args, const wchar_t *scriptPath, Interpreter interpreter, const wchar_t *invokingButtonTitle, bool modified, void *dummy) {
 	structMelderFile file = { 0 };
 	(void) sendingForm_dummy;
diff --git a/sys/praat_script.h b/sys/praat_script.h
index 8b688dd..6208bf6 100644
--- a/sys/praat_script.h
+++ b/sys/praat_script.h
@@ -2,7 +2,7 @@
 #define _praat_script_h_
 /* praat_script.h
  *
- * Copyright (C) 1992-2011,2013 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014 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
@@ -21,9 +21,10 @@
 
 #include "Interpreter.h"
 
-void praat_executeCommand (Interpreter me, wchar_t *command);
+int praat_executeCommand (Interpreter me, wchar_t *command);
 void praat_executeCommandFromStandardInput (const char *programName);
 void praat_executeScriptFromFile (MelderFile file, const wchar_t *arguments);
+void praat_executeScriptFromFileName (const wchar_t *fileName, int narg, Stackel args);
 void praat_executeScriptFromFileNameWithArguments (const wchar_t *nameAndArguments);
 void praat_executeScriptFromText (wchar_t *text);
 void praat_executeScriptFromDialog (Any dia);
diff --git a/sys/praat_version.h b/sys/praat_version.h
index ca91d7d..125d38f 100644
--- a/sys/praat_version.h
+++ b/sys/praat_version.h
@@ -1,5 +1,5 @@
-#define PRAAT_VERSION_STR 5.3.57
-#define PRAAT_VERSION_NUM 5357
-#define PRAAT_YEAR 2013
-#define PRAAT_MONTH October
-#define PRAAT_DAY 27
+#define PRAAT_VERSION_STR 5.3.82
+#define PRAAT_VERSION_NUM 5382
+#define PRAAT_YEAR 2014
+#define PRAAT_MONTH July
+#define PRAAT_DAY 26
diff --git a/sys/sendpraat.c b/sys/sendpraat.c
index 5a254fd..c389a8d 100644
--- a/sys/sendpraat.c
+++ b/sys/sendpraat.c
@@ -1,6 +1,6 @@
 /* sendpraat.c */
 /* by Paul Boersma */
-/* 25 April 2013 */
+/* 12 April 2014 */
 
 /*
  * The sendpraat subroutine (Unix with GTK; Windows; Macintosh) sends a message
@@ -34,7 +34,7 @@
 	#define gtk 0
 	#define win 1
 	#define mac 0
-#elif (defined (macintosh) || defined (__MACH__)) && useCarbon
+#elif (defined (macintosh) || defined (__MACH__))
     #include <Carbon/Carbon.h>
     #include <wchar.h>
 	#define gtk 0
@@ -49,8 +49,12 @@
 	#include <unistd.h>
 	#include <ctype.h>
 	#include <wchar.h>
-	#include <gtk/gtk.h>
-	#define gtk 1
+	#if defined (NO_GRAPHICS)
+		#define gtk 0
+	#else
+		#include <gtk/gtk.h>
+		#define gtk 1
+	#endif
 	#define win 0
 	#define mac 0
 #else
diff --git a/test/fon/TextGrid.praat b/test/fon/TextGrid.praat
new file mode 100644
index 0000000..02bf382
--- /dev/null
+++ b/test/fon/TextGrid.praat
@@ -0,0 +1,139 @@
+# test/fon/TextGrid.praat
+# Paul Boersma, 19 November 2013
+
+do ("Create TextGrid...", 0.0, 3.0, "A B C", "")
+
+i = Get interval at time... 2 -1
+assert i = 0
+i = Get interval at time... 2 0
+assert i = 1
+i = Get interval at time... 2 1
+assert i = 1
+i = Get interval at time... 2 2
+assert i = 1
+i = Get interval at time... 2 3
+assert i = 1
+i = Get interval at time... 2 4
+assert i = 0
+
+i = Get low interval at time... 2 -1
+assert i = 0
+i = Get low interval at time... 2 0
+assert i = 0
+i = Get low interval at time... 2 1
+assert i = 1
+i = Get low interval at time... 2 2
+assert i = 1
+i = Get low interval at time... 2 3
+assert i = 1
+i = Get low interval at time... 2 4
+assert i = 0
+
+i = Get high interval at time... 2 -1
+assert i = 0
+i = Get high interval at time... 2 0
+assert i = 1
+i = Get high interval at time... 2 1
+assert i = 1
+i = Get high interval at time... 2 2
+assert i = 1
+i = Get high interval at time... 2 3
+assert i = 0
+i = Get high interval at time... 2 4
+assert i = 0
+
+i = Get interval edge from time... 2 -1
+assert i = 0
+i = Get interval edge from time... 2 0
+assert i = 1
+i = Get interval edge from time... 2 1
+assert i = 0
+i = Get interval edge from time... 2 2
+assert i = 0
+i = Get interval edge from time... 2 3
+assert i = 1
+i = Get interval edge from time... 2 4
+assert i = 0
+
+i = Get interval boundary from time... 2 -1
+assert i = 0
+i = Get interval boundary from time... 2 0
+assert i = 0
+i = Get interval boundary from time... 2 1
+assert i = 0
+i = Get interval boundary from time... 2 2
+assert i = 0
+i = Get interval boundary from time... 2 3
+assert i = 0
+i = Get interval boundary from time... 2 4
+assert i = 0
+
+do ("Insert boundary...", 2, 1.0)
+do ("Insert boundary...", 2, 2.0)
+
+i = Get interval at time... 2 -1
+assert i = 0
+i = Get interval at time... 2 0
+assert i = 1
+i = Get interval at time... 2 1
+assert i = 2
+i = Get interval at time... 2 2
+assert i = 3
+i = Get interval at time... 2 3
+assert i = 3
+i = Get interval at time... 2 4
+assert i = 0
+
+i = Get low interval at time... 2 -1
+assert i = 0
+i = Get low interval at time... 2 0
+assert i = 0
+i = Get low interval at time... 2 1
+assert i = 1
+i = Get low interval at time... 2 2
+assert i = 2
+i = Get low interval at time... 2 3
+assert i = 3
+i = Get low interval at time... 2 4
+assert i = 0
+
+i = Get high interval at time... 2 -1
+assert i = 0
+i = Get high interval at time... 2 0
+assert i = 1
+i = Get high interval at time... 2 1
+assert i = 2
+i = Get high interval at time... 2 2
+assert i = 3
+i = Get high interval at time... 2 3
+assert i = 0
+i = Get high interval at time... 2 4
+assert i = 0
+
+i = Get interval edge from time... 2 -1
+assert i = 0
+i = Get interval edge from time... 2 0
+assert i = 1
+i = Get interval edge from time... 2 1
+assert i = 2
+i = Get interval edge from time... 2 2
+assert i = 3
+i = Get interval edge from time... 2 3
+assert i = 3
+i = Get interval edge from time... 2 4
+assert i = 0
+
+i = Get interval boundary from time... 2 -1
+assert i = 0
+i = Get interval boundary from time... 2 0
+assert i = 0
+i = Get interval boundary from time... 2 1
+assert i = 2
+i = Get interval boundary from time... 2 2
+assert i = 3
+i = Get interval boundary from time... 2 3
+assert i = 0
+i = Get interval boundary from time... 2 4
+assert i = 0
+
+Remove
diff --git a/test/fon/data.praat b/test/fon/data.praat
index 02016db..a03c850 100644
--- a/test/fon/data.praat
+++ b/test/fon/data.praat
@@ -1,254 +1,208 @@
 # data.praat
 # Paul Boersma, 22 January 2009
-# Checks Copy, Equal, Read, Write.
+# Checks Copy, Equal, Read, Save.
 # 10 October 2012
+# 9 January 2014
 
-echo Data test
+writeInfoLine: "Data test"
 stopwatch
 
-printline Sound
-sound = Create Sound from formula... kanweg Mono 0 1.2345 44100 1/2 * sin(2*pi*377*x) + randomGauss(0,0.1)
-call test sound
+appendInfoLine: "Sound"
+sound = Create Sound from formula: "kanweg", 1, 0, 1.2345, 44100, "1/2 * sin(2*pi*377*x) + randomGauss: 0, 0.1"
+ at test (sound)
 Remove
 
-printline Pitch
-sound = Read from file... test.wav
-pitch = To Pitch... 0 75 600
-call test pitch
+appendInfoLine: "Pitch"
+sound = Read from file: "test.wav"
+pitch = To Pitch: 0, 75, 600
+ at test (pitch)
 
-printline Formant
-select sound
-formant = To Formant (burg)... 0 5 5000 0.025 50
-call test formant
+appendInfoLine: "Formant"
+selectObject: sound
+formant = To Formant (burg): 0, 5, 5000, 0.025, 50
+ at test (formant)
 
-printline PointProcess
-select sound
-plus pitch
+appendInfoLine: "PointProcess"
+selectObject: sound, pitch
 pulses = To PointProcess (cc)
-call test pulses
-
-printline PitchTier
-pitchTier = To PitchTier... 0.02
-call test pitchTier
-
-printline Manipulation
-select sound
-manipulation = To Manipulation... 0.01 75 600
-call test manipulation
-
-printline Matrix
-matrix = Create simple Matrix... xy2 10 20 x*y^2
-call test matrix
-Write to matrix text file... kanweg.txt
-matrix2 = Read from file... kanweg.txt
-assert objectsAreIdentical (matrix, matrix2)
+ at test (pulses)
+
+appendInfoLine: "PitchTier"
+pitchTier = To PitchTier: 0.02
+ at test (pitchTier)
+
+appendInfoLine: "Manipulation"
+selectObject: sound
+manipulation = To Manipulation: 0.01, 75, 600
+ at test (manipulation)
+
+appendInfoLine: "Matrix"
+matrix = Create simple Matrix: "xy2", 10, 20, "x*y^2"
+ at test (matrix)
+Save as matrix text file: "kanweg.txt"
+matrix2 = Read from file: "kanweg.txt"
+assert objectsAreIdentical: matrix, matrix2
 Remove
-select matrix
-Write to headerless spreadsheet file... kanweg.txt
-matrix3 = Read Matrix from raw text file... kanweg.txt
-assert objectsAreIdentical (matrix, matrix3)
+selectObject: matrix
+Save as headerless spreadsheet file: "kanweg.txt"
+matrix3 = Read Matrix from raw text file: "kanweg.txt"
+assert objectsAreIdentical: matrix, matrix3
 Remove
 
-printline Speaker
-speaker = Create Speaker... man male 2
-call test speaker
+appendInfoLine: "Speaker"
+speaker = Create Speaker: "man", "male", "2"
+ at test (speaker)
 
-printline OTGrammar
-grammar = Create metrics grammar... equal FtNonfinal no no no Nonfinal yes no no
-call test grammar
+appendInfoLine: "OTGrammar"
+grammar = Create metrics grammar: "equal", "FtNonfinal", "no", "no", "no", "Nonfinal", "yes", "no", "no"
+ at test (grammar)
 
-printline Table
+appendInfoLine: "Table"
 table = Create formant table (Peterson & Barney 1952)
-call test table
-Write to table file... kanweg.txt
-table2 = Read Table from tab-separated file... kanweg.txt
-assert objectsAreIdentical (table, table2)
+ at test (table)
+Save as tab-separated file: "kanweg.txt"
+table2 = Read Table from tab-separated file: "kanweg.txt"
+assert objectsAreIdentical: table, table2
 Remove
-table3 = Read Table from table file... kanweg.txt
-assert objectsAreIdentical (table, table3)
+table3 = Read Table from tab-separated file: "kanweg.txt"
+assert objectsAreIdentical: table, table3
 Remove
 
-printline TableOfReal
-tableOfReal = Create TableOfReal (Pols 1973)... no
-call test tableOfReal
-Write to headerless spreadsheet file... kanweg.txt
-tableOfReal2 = Read TableOfReal from headerless spreadsheet file... kanweg.txt
-assert objectsAreIdentical (tableOfReal, tableOfReal2)
+appendInfoLine: "TableOfReal"
+tableOfReal = Create TableOfReal (Pols 1973): "no"
+ at test (tableOfReal)
+Save as headerless spreadsheet file: "kanweg.txt"
+tableOfReal2 = Read TableOfReal from headerless spreadsheet file: "kanweg.txt"
+assert objectsAreIdentical: tableOfReal, tableOfReal2
 Remove
 
-printline FFNet
-Create iris example... 0 0
-ffnet = selected ("FFNet")
-pattern = selected ("Pattern")
-categories = selected ("Categories")
-call test ffnet
-call test pattern
-call test categories
-
-printline KNN
-select pattern
-plus categories
-knn = To KNN Classifier... Classifier random
-call test knn
-
-printline Discriminant
-select pattern
-plus categories
+appendInfoLine: "FFNet"
+Create iris example: 0, 0
+ffnet = selected: "FFNet"
+pattern = selected: "Pattern"
+categories = selected: "Categories"
+ at test (ffnet)
+ at test (pattern)
+ at test (categories)
+
+appendInfoLine: "KNN"
+selectObject: pattern, categories
+knn = To KNN Classifier: "Classifier", "random"
+ at test (knn)
+
+appendInfoLine: "Discriminant"
+selectObject: pattern, categories
 discriminant = To Discriminant
-call test discriminant
-
-procedure selectAll
-	select sound
-	plus pitch
-	plus formant
-	plus pulses
-	plus pitchTier
-	plus manipulation
-	plus matrix
-	plus speaker
-	plus grammar
-	plus table
-	plus tableOfReal
-	plus ffnet
-	plus pattern
-	plus categories
-	plus knn
-	plus discriminant
+ at test (discriminant)
+
+procedure selectAll ( )
+	selectObject: sound, pitch, formant, pulses, pitchTier, manipulation, matrix, speaker, grammar, table, tableOfReal,
+	... ffnet, pattern, categories, knn, discriminant
 endproc
-procedure readCheckCollectionFile
-	Read from file... kanweg.Collection
-	sound2 = selected ("Sound")
-	assert objectsAreIdentical (sound, sound2)
-	pitch2 = selected ("Pitch")
-	assert objectsAreIdentical (pitch, pitch2)
-	formant2 = selected ("Formant")
-	assert objectsAreIdentical (formant, formant2)
-	pulses2 = selected ("PointProcess")
-	assert objectsAreIdentical (pulses, pulses2)
-	pitchTier2 = selected ("PitchTier")
-	assert objectsAreIdentical (pitchTier, pitchTier2)
-	manipulation2 = selected ("Manipulation")
-	assert objectsAreIdentical (manipulation, manipulation2)
-	matrix2 = selected ("Matrix")
-	assert objectsAreIdentical (matrix, matrix2)
-	speaker2 = selected ("Speaker")
-	assert objectsAreIdentical (speaker, speaker2)
-	grammar2 = selected ("OTGrammar")
-	assert objectsAreIdentical (grammar, grammar2)
-	table2 = selected ("Table")
-	assert objectsAreIdentical (table, table2)
-	tableOfReal2 = selected ("TableOfReal")
-	assert objectsAreIdentical (tableOfReal, tableOfReal2)
-	ffnet2 = selected ("FFNet")
-	assert objectsAreIdentical (ffnet, ffnet2)
-	pattern2 = selected ("Pattern")
-	assert objectsAreIdentical (pattern, pattern2)
-	categories2 = selected ("Categories")
-	assert objectsAreIdentical (categories, categories2)
-	knn2 = selected ("KNN")
-	assert objectsAreIdentical (knn, knn2)
-	discriminant2 = selected ("Discriminant")
-	assert objectsAreIdentical (discriminant, discriminant2)
-	select sound2
-	plus pitch2
-	plus formant2
-	plus pulses2
-	plus pitchTier2
-	plus manipulation2
-	plus matrix2
-	plus speaker2
-	plus grammar2
-	plus table2
-	plus tableOfReal2
-	plus ffnet2
-	plus pattern2
-	plus categories2
-	plus knn2
-	plus discriminant2
-	Remove
+procedure readCheckCollectionFile ( )
+	Read from file: "kanweg.Collection"
+	sound2 = selected: "Sound"
+	assert objectsAreIdentical: sound, sound2
+	pitch2 = selected: "Pitch"
+	assert objectsAreIdentical: pitch, pitch2
+	formant2 = selected: "Formant"
+	assert objectsAreIdentical: formant, formant2
+	pulses2 = selected: "PointProcess"
+	assert objectsAreIdentical: pulses, pulses2
+	pitchTier2 = selected: "PitchTier"
+	assert objectsAreIdentical: pitchTier, pitchTier2
+	manipulation2 = selected: "Manipulation"
+	assert objectsAreIdentical: manipulation, manipulation2
+	matrix2 = selected: "Matrix"
+	assert objectsAreIdentical: matrix, matrix2
+	speaker2 = selected: "Speaker"
+	assert objectsAreIdentical: speaker, speaker2
+	grammar2 = selected: "OTGrammar"
+	assert objectsAreIdentical: grammar, grammar2
+	table2 = selected: "Table"
+	assert objectsAreIdentical: table, table2
+	tableOfReal2 = selected: "TableOfReal"
+	assert objectsAreIdentical: tableOfReal, tableOfReal2
+	ffnet2 = selected: "FFNet"
+	assert objectsAreIdentical: ffnet, ffnet2
+	pattern2 = selected: "Pattern"
+	assert objectsAreIdentical: pattern, pattern2
+	categories2 = selected: "Categories"
+	assert objectsAreIdentical: categories, categories2
+	knn2 = selected: "KNN"
+	assert objectsAreIdentical: knn, knn2
+	discriminant2 = selected: "Discriminant"
+	assert objectsAreIdentical: discriminant, discriminant2
+	removeObject: sound2, pitch2, formant2, pulses2, pitchTier2, manipulation2, matrix2, speaker2, grammar2, table2, tableOfReal2,
+	... ffnet2, pattern2, categories2, knn2, discriminant2
 endproc
 
-printline text Collection
-call selectAll
-Write to text file... kanweg.Collection
-call readCheckCollectionFile
-
-printline short text Collection
-call selectAll
-Write to short text file... kanweg.Collection
-call readCheckCollectionFile
-
-printline binary Collection
-call selectAll
-Write to binary file... kanweg.Collection
-call readCheckCollectionFile
-
-select sound
-plus pitch
-plus formant
-plus pulses
-plus pitchTier
-plus manipulation
-plus matrix
-plus speaker
-plus grammar
-plus table
-plus tableOfReal
-plus ffnet
-plus pattern
-plus categories
-plus knn
-plus discriminant
-Remove
-deleteFile ("kanweg.Object")
-deleteFile ("kanweg.Collection")
+appendInfoLine: "text Collection"
+ at selectAll ( )
+Save as text file: "kanweg.Collection"
+ at readCheckCollectionFile ( )
+
+appendInfoLine: "short text Collection"
+ at selectAll ( )
+Save as short text file: "kanweg.Collection"
+ at readCheckCollectionFile ( )
+
+appendInfoLine: "binary Collection"
+ at selectAll ( )
+Save as binary file: "kanweg.Collection"
+ at readCheckCollectionFile ( )
+
+removeObject: sound, pitch, formant, pulses, pitchTier, manipulation, matrix, speaker, grammar, table, tableOfReal,
+... ffnet, pattern, categories, knn, discriminant
+deleteFile: "kanweg.Object"
+deleteFile: "kanweg.Collection"
 
 t = stopwatch
-printline OK ('t:3' seconds)
+appendInfoLine: "OK (", fixed$ (t, 3), " seconds)"
 
-procedure test .object1
-	select .object1
-	.object2 = Copy... kanweg2
-	assert objectsAreIdentical (.object1, .object2)
+procedure test (.object1)
+	selectObject: .object1
+	.object2 = Copy: "kanweg2"
+	assert objectsAreIdentical: .object1, .object2
 	Remove
-	select .object1
-	.object2 = Copy... kanweg2
-	assert objectsAreIdentical (.object1, .object2)
+	selectObject: .object1
+	.object2 = Copy: "kanweg2"
+	assert objectsAreIdentical: .object1, .object2
 	Remove
 	# Test verbose ASCII text writing (for correct data).
-	select .object1
-	Write to text file... kanweg.Object
-	.object2 = Read from file... kanweg.Object
-	assert objectsAreIdentical (.object1, .object2)   ; verbose ASCII write and read
+	selectObject: .object1
+	Save as text file: "kanweg.Object"
+	.object2 = Read from file: "kanweg.Object"
+	assert objectsAreIdentical: .object1, .object2   ; verbose ASCII write and read
 	Remove
 	# Test concise ASCII text writing (for correct data).
-	select .object1
-	Write to short text file... kanweg.Object
-	.object2 = Read from file... kanweg.Object
-	assert objectsAreIdentical (.object1, .object2)   ; concise ASCII write and read
+	selectObject: .object1
+	Save as short text file: "kanweg.Object"
+	.object2 = Read from file: "kanweg.Object"
+	assert objectsAreIdentical: .object1, .object2   ; concise ASCII write and read
 	Remove
 	# Test binary writing.
-	select .object1
-	Write to binary file... kanweg.Object
-	.object2 = Read from file... kanweg.Object
-	assert objectsAreIdentical (.object1, .object2)   ; binary write and read
+	selectObject: .object1
+	Save as binary file: "kanweg.Object"
+	.object2 = Read from file: "kanweg.Object"
+	assert objectsAreIdentical: .object1, .object2   ; binary write and read
 	Remove
 	# Test binary writing.
-	select .object1
-	Write to binary file... kanweg.Object
-	Debug... no 18
-	.object2 = Read from file... kanweg.Object
-	Debug... no 0
-	assert objectsAreIdentical (.object1, .object2)   ; binary write and read
+	selectObject: .object1
+	Save as binary file: "kanweg.Object"
+	Debug: "no", 18
+	.object2 = Read from file: "kanweg.Object"
+	Debug: "no", 0
+	assert objectsAreIdentical: .object1, .object2   ; binary write and read
 	Remove
 	# Test binary writing.
-	select .object1
-	Debug... no 18
-	Write to binary file... kanweg.Object
-	Debug... no 0
-	.object2 = Read from file... kanweg.Object
-	assert objectsAreIdentical (.object1, .object2)   ; binary write and read
+	selectObject: .object1
+	Debug: "no", 18
+	Save as binary file: "kanweg.Object"
+	Debug: "no", 0
+	.object2 = Read from file: "kanweg.Object"
+	assert objectsAreIdentical: .object1, .object2   ; binary write and read
 	Remove
 	# Good neighbour.
-	select .object1
+	selectObject: .object1
 endproc
diff --git a/test/script/procedures.praat b/test/script/procedures.praat
new file mode 100644
index 0000000..ec4bedc
--- /dev/null
+++ b/test/script/procedures.praat
@@ -0,0 +1,62 @@
+writeInfoLine: "Procedures"
+
+ at colon (33,"ho,p"",""kkk", 5, "h")
+assert colon.result$ = "colon33ho,p"",""kkk5h"
+
+call old 1 a 2 b
+assert old.result$ = "old1a2b"
+ at old (3, "a", 4, "b")
+assert old.result$ = "old3a4b"
+ at old: 5, "a", 6, "b"
+assert old.result$ = "old5a6b"
+
+call new 1 a 2 b
+assert new.result$ = "new1a2b"
+ at new (3, "a", 4, "b")
+assert new.result$ = "new3a4b"
+ at new: 5, "a", 6, "b"
+assert new.result$ = "new5a6b"
+
+call colon 1 a 2 b
+assert colon.result$ = "colon1a2b"
+ at colon (3, "a", 4, "b")
+assert colon.result$ = "colon3a4b"   ; 'colon.result$'
+ at colon: 5, "a", 6, "b"
+assert colon.result$ = "colon5a6b"
+
+ at noarg1()
+ at noarg1 ( )
+ at noarg1:
+ at noarg1
+ at noarg1   
+call noarg1
+
+ at noarg2()
+ at noarg2 ( )
+ at noarg2:
+ at noarg2
+ at noarg2   
+;call noarg2
+
+;asserterror The procedure "colon" expects 4 arguments but got only 3.
+;@colon (1, "a", 2)
+
+procedure old .a .b$ .c .d$
+	.result$ = "old" + string$ (.a) + .b$ + string$ (old.c) + .d$
+endproc
+
+procedure new (.a, .b$, .c, .d$)
+	.result$ = "new" + string$ (.a) + .b$ + string$ (new.c) + .d$
+endproc
+
+procedure colon: .a, .b$, .c, .d$
+	.result$ = "colon" + string$ (.a) + .b$ + string$ (colon.c) + .d$
+endproc
+
+procedure noarg1
+	appendInfoLine ("hoi1")
+endproc
+
+procedure noarg2 ( )
+	appendInfoLine ("hoi2")
+endproc
diff --git a/test/sys/graphics.praat b/test/sys/graphics.praat
index e10615a..85d26a6 100644
Binary files a/test/sys/graphics.praat and b/test/sys/graphics.praat differ
diff --git a/test/sys/paul.png b/test/sys/paul.png
new file mode 100644
index 0000000..6e56c0c
Binary files /dev/null and b/test/sys/paul.png differ
diff --git a/test/sys/procedures2.praat b/test/sys/procedures2.praat
deleted file mode 100644
index 4b7973b..0000000
--- a/test/sys/procedures2.praat
+++ /dev/null
@@ -1,10 +0,0 @@
-;@doe (33,"ho,p"",""kkk", 5)
- at do2 (66, "GH")
-
-procedure doe (.a, .b$, .c, .d$)
-	writeInfoLine ("hallo", .a, .b$, doe.c, .d$)
-endproc
-
-procedure do2 ()
-	appendInfoLine ("hoi")
-endproc
\ 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