changeset 1051:c02c51ae5238 3.0-plus-imaf

Merge branches 3.0-integration and imaf_enc to 3.0-plus-imaf
author Chris Cannam
date Wed, 20 Apr 2016 12:06:28 +0100
parents 3691af49291c (diff) 282f4be8f058 (current diff)
children
files layer/Layer.h layer/TextLayer.cpp layer/TimeInstantLayer.cpp layer/WaveformLayer.cpp view/Pane.cpp widgets/InteractiveFileFinder.cpp widgets/ModelDataTableDialog.cpp widgets/PropertyBox.cpp
diffstat 115 files changed, 10584 insertions(+), 5146 deletions(-) [+]
line wrap: on
line diff
--- a/acinclude.m4	Tue Jul 14 15:04:46 2015 +0100
+++ b/acinclude.m4	Wed Apr 20 12:06:28 2016 +0100
@@ -69,6 +69,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, $QTDIR/bin/qmake-qt5,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, $QTDIR/bin/qt5-qmake,,$QTDIR/bin/)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, $QTDIR/bin/qmake,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
@@ -78,6 +81,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, qmake-qt5,,$PATH)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, qt5-qmake,,$PATH)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, qmake,,$PATH)
 fi
 if test x$QMAKE = x ; then
@@ -112,3 +118,146 @@
 
 ])
 
+# From autoconf archive:
+
+# ============================================================================
+#  http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html
+# ============================================================================
+#
+# SYNOPSIS
+#
+#   AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional])
+#
+# DESCRIPTION
+#
+#   Check for baseline language coverage in the compiler for the C++11
+#   standard; if necessary, add switches to CXXFLAGS to enable support.
+#
+#   The first argument, if specified, indicates whether you insist on an
+#   extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
+#   -std=c++11).  If neither is specified, you get whatever works, with
+#   preference for an extended mode.
+#
+#   The second argument, if specified 'mandatory' or if left unspecified,
+#   indicates that baseline C++11 support is required and that the macro
+#   should error out if no mode with that support is found.  If specified
+#   'optional', then configuration proceeds regardless, after defining
+#   HAVE_CXX11 if and only if a supporting mode is found.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+#   Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
+#   Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+#   Copyright (c) 2014 Alexey Sokolov <sokolov@google.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+]])
+
+AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl
+  m4_if([$1], [], [],
+        [$1], [ext], [],
+        [$1], [noext], [],
+        [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl
+  m4_if([$2], [], [ax_cxx_compile_cxx11_required=true],
+        [$2], [mandatory], [ax_cxx_compile_cxx11_required=true],
+        [$2], [optional], [ax_cxx_compile_cxx11_required=false],
+        [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])])
+  AC_LANG_PUSH([C++])dnl
+  ac_success=no
+  AC_CACHE_CHECK(whether $CXX supports C++11 features by default,
+  ax_cv_cxx_compile_cxx11,
+  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+    [ax_cv_cxx_compile_cxx11=yes],
+    [ax_cv_cxx_compile_cxx11=no])])
+  if test x$ax_cv_cxx_compile_cxx11 = xyes; then
+    ac_success=yes
+  fi
+
+  m4_if([$1], [noext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=gnu++11 -std=gnu++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+
+  m4_if([$1], [ext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=c++11 -std=c++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+  AC_LANG_POP([C++])
+  if test x$ax_cxx_compile_cxx11_required = xtrue; then
+    if test x$ac_success = xno; then
+      AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.])
+    fi
+  else
+    if test x$ac_success = xno; then
+      HAVE_CXX11=0
+      AC_MSG_NOTICE([No compiler with C++11 support was found])
+    else
+      HAVE_CXX11=1
+      AC_DEFINE(HAVE_CXX11,1,
+                [define if the compiler supports basic C++11 syntax])
+    fi
+
+    AC_SUBST(HAVE_CXX11)
+  fi
+])
+
--- a/configure	Tue Jul 14 15:04:46 2015 +0100
+++ b/configure	Wed Apr 20 12:06:28 2016 +0100
@@ -646,16 +646,12 @@
 libpulse_CFLAGS
 JACK_LIBS
 JACK_CFLAGS
-portaudio_2_0_LIBS
-portaudio_2_0_CFLAGS
+portaudio_LIBS
+portaudio_CFLAGS
 liblo_LIBS
 liblo_CFLAGS
 rubberband_LIBS
 rubberband_CFLAGS
-vamphostsdk_LIBS
-vamphostsdk_CFLAGS
-vamp_LIBS
-vamp_CFLAGS
 samplerate_LIBS
 samplerate_CFLAGS
 sndfile_LIBS
@@ -673,6 +669,7 @@
 EGREP
 GREP
 CXXCPP
+HAVE_CXX11
 MKDIR_P
 INSTALL_DATA
 INSTALL_SCRIPT
@@ -755,16 +752,12 @@
 sndfile_LIBS
 samplerate_CFLAGS
 samplerate_LIBS
-vamp_CFLAGS
-vamp_LIBS
-vamphostsdk_CFLAGS
-vamphostsdk_LIBS
 rubberband_CFLAGS
 rubberband_LIBS
 liblo_CFLAGS
 liblo_LIBS
-portaudio_2_0_CFLAGS
-portaudio_2_0_LIBS
+portaudio_CFLAGS
+portaudio_LIBS
 JACK_CFLAGS
 JACK_LIBS
 libpulse_CFLAGS
@@ -1422,12 +1415,6 @@
               C compiler flags for samplerate, overriding pkg-config
   samplerate_LIBS
               linker flags for samplerate, overriding pkg-config
-  vamp_CFLAGS C compiler flags for vamp, overriding pkg-config
-  vamp_LIBS   linker flags for vamp, overriding pkg-config
-  vamphostsdk_CFLAGS
-              C compiler flags for vamphostsdk, overriding pkg-config
-  vamphostsdk_LIBS
-              linker flags for vamphostsdk, overriding pkg-config
   rubberband_CFLAGS
               C compiler flags for rubberband, overriding pkg-config
   rubberband_LIBS
@@ -1435,10 +1422,10 @@
   liblo_CFLAGS
               C compiler flags for liblo, overriding pkg-config
   liblo_LIBS  linker flags for liblo, overriding pkg-config
-  portaudio_2_0_CFLAGS
-              C compiler flags for portaudio_2_0, overriding pkg-config
-  portaudio_2_0_LIBS
-              linker flags for portaudio_2_0, overriding pkg-config
+  portaudio_CFLAGS
+              C compiler flags for portaudio, overriding pkg-config
+  portaudio_LIBS
+              linker flags for portaudio, overriding pkg-config
   JACK_CFLAGS C compiler flags for JACK, overriding pkg-config
   JACK_LIBS   linker flags for JACK, overriding pkg-config
   libpulse_CFLAGS
@@ -3449,6 +3436,146 @@
 $as_echo "$MKDIR_P" >&6; }
 
 
+# We are daringly making use of C++11 now
+
+    ax_cxx_compile_cxx11_required=true
+  ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+  ac_success=no
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features by default" >&5
+$as_echo_n "checking whether $CXX supports C++11 features by default... " >&6; }
+if ${ax_cv_cxx_compile_cxx11+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ax_cv_cxx_compile_cxx11=yes
+else
+  ax_cv_cxx_compile_cxx11=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx11" >&5
+$as_echo "$ax_cv_cxx_compile_cxx11" >&6; }
+  if test x$ax_cv_cxx_compile_cxx11 = xyes; then
+    ac_success=yes
+  fi
+
+
+
+    if test x$ac_success = xno; then
+    for switch in -std=c++11 -std=c++0x; do
+      cachevar=`$as_echo "ax_cv_cxx_compile_cxx11_$switch" | $as_tr_sh`
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features with $switch" >&5
+$as_echo_n "checking whether $CXX supports C++11 features with $switch... " >&6; }
+if eval \${$cachevar+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  eval $cachevar=yes
+else
+  eval $cachevar=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+         CXXFLAGS="$ac_save_CXXFLAGS"
+fi
+eval ac_res=\$$cachevar
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi
+  ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+  if test x$ax_cxx_compile_cxx11_required = xtrue; then
+    if test x$ac_success = xno; then
+      as_fn_error $? "*** A compiler with support for C++11 language features is required." "$LINENO" 5
+    fi
+  else
+    if test x$ac_success = xno; then
+      HAVE_CXX11=0
+      { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++11 support was found" >&5
+$as_echo "$as_me: No compiler with C++11 support was found" >&6;}
+    else
+      HAVE_CXX11=1
+
+$as_echo "#define HAVE_CXX11 1" >>confdefs.h
+
+    fi
+
+
+  fi
+
 
 ac_ext=cpp
 ac_cpp='$CXXCPP $CPPFLAGS'
@@ -3996,6 +4123,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $QTDIR/bin/
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="$QTDIR/bin/qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -4113,6 +4279,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -4202,9 +4407,10 @@
 CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS"
 
 if test "x$GCC" = "xyes"; then
-        CXXFLAGS_DEBUG="-Wall -Woverloaded-virtual -Wextra -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -g -pipe"
-   	CXXFLAGS_RELEASE="-g0 -O2 -Wall -pipe"
-   	CXXFLAGS_MINIMAL="-g0 -O0"
+   	CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
+        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g"
+   	CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2"
+   	CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0"
 fi
 
 CXXFLAGS_BUILD="$CXXFLAGS_RELEASE"
@@ -5006,18 +5212,18 @@
 fi
 
 
-SV_MODULE_MODULE=vamp
-SV_MODULE_VERSION_TEST="vamp >= 2.1"
-SV_MODULE_HEADER=vamp/vamp.h
-SV_MODULE_LIB=
-SV_MODULE_FUNC=
-SV_MODULE_HAVE=HAVE_$(echo vamp | tr 'a-z' 'A-Z')
+SV_MODULE_MODULE=rubberband
+SV_MODULE_VERSION_TEST="rubberband"
+SV_MODULE_HEADER=rubberband/RubberBandStretcher.h
+SV_MODULE_LIB=rubberband
+SV_MODULE_FUNC=rubberband_new
+SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z')
 SV_MODULE_FAILED=1
-if test -n "$vamp_LIBS" ; then
+if test -n "$rubberband_LIBS" ; then
    { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $vamp_CFLAGS"
-   LIBS="$LIBS $vamp_LIBS"
+   CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS"
+   LIBS="$LIBS $rubberband_LIBS"
    SV_MODULE_FAILED=""
 fi
 if test -z "$SV_MODULE_VERSION_TEST" ; then
@@ -5026,11 +5232,11 @@
 if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamp" >&5
-$as_echo_n "checking for vamp... " >&6; }
-
-if test -n "$vamp_CFLAGS"; then
-    pkg_cv_vamp_CFLAGS="$vamp_CFLAGS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5
+$as_echo_n "checking for rubberband... " >&6; }
+
+if test -n "$rubberband_CFLAGS"; then
+    pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5038,7 +5244,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamp_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5046,8 +5252,8 @@
  else
     pkg_failed=untried
 fi
-if test -n "$vamp_LIBS"; then
-    pkg_cv_vamp_LIBS="$vamp_LIBS"
+if test -n "$rubberband_LIBS"; then
+    pkg_cv_rubberband_LIBS="$rubberband_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5055,7 +5261,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamp_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5076,12 +5282,12 @@
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        vamp_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         else
-	        vamp_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
-	echo "$vamp_PKG_ERRORS" >&5
+	echo "$rubberband_PKG_ERRORS" >&5
 
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
@@ -5091,11 +5297,11 @@
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
 else
-	vamp_CFLAGS=$pkg_cv_vamp_CFLAGS
-	vamp_LIBS=$pkg_cv_vamp_LIBS
+	rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS
+	rubberband_LIBS=$pkg_cv_rubberband_LIBS
         { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamp_CFLAGS";LIBS="$LIBS $vamp_LIBS";SV_MODULE_FAILED=""
+	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED=""
 fi
 fi
 if test -n "$SV_MODULE_FAILED"; then
@@ -5157,18 +5363,19 @@
 fi
 
 
-SV_MODULE_MODULE=vamphostsdk
-SV_MODULE_VERSION_TEST="vamp-hostsdk >= 2.5"
-SV_MODULE_HEADER=vamp-hostsdk/PluginLoader.h
-SV_MODULE_LIB=vamp-hostsdk
-SV_MODULE_FUNC=libvamphostsdk_v_2_5_present
-SV_MODULE_HAVE=HAVE_$(echo vamphostsdk | tr 'a-z' 'A-Z')
+
+SV_MODULE_MODULE=liblo
+SV_MODULE_VERSION_TEST=""
+SV_MODULE_HEADER=lo/lo.h
+SV_MODULE_LIB=lo
+SV_MODULE_FUNC=lo_address_new
+SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z')
 SV_MODULE_FAILED=1
-if test -n "$vamphostsdk_LIBS" ; then
+if test -n "$liblo_LIBS" ; then
    { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS"
-   LIBS="$LIBS $vamphostsdk_LIBS"
+   CXXFLAGS="$CXXFLAGS $liblo_CFLAGS"
+   LIBS="$LIBS $liblo_LIBS"
    SV_MODULE_FAILED=""
 fi
 if test -z "$SV_MODULE_VERSION_TEST" ; then
@@ -5177,11 +5384,11 @@
 if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamphostsdk" >&5
-$as_echo_n "checking for vamphostsdk... " >&6; }
-
-if test -n "$vamphostsdk_CFLAGS"; then
-    pkg_cv_vamphostsdk_CFLAGS="$vamphostsdk_CFLAGS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5
+$as_echo_n "checking for liblo... " >&6; }
+
+if test -n "$liblo_CFLAGS"; then
+    pkg_cv_liblo_CFLAGS="$liblo_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5189,7 +5396,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamphostsdk_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5197,8 +5404,8 @@
  else
     pkg_failed=untried
 fi
-if test -n "$vamphostsdk_LIBS"; then
-    pkg_cv_vamphostsdk_LIBS="$vamphostsdk_LIBS"
+if test -n "$liblo_LIBS"; then
+    pkg_cv_liblo_LIBS="$liblo_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5206,7 +5413,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_vamphostsdk_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5227,40 +5434,42 @@
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         else
-	        vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
-	echo "$vamphostsdk_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
+	echo "$liblo_PKG_ERRORS" >&5
+
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
+$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
 elif test $pkg_failed = untried; then
      	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
 $as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	vamphostsdk_CFLAGS=$pkg_cv_vamphostsdk_CFLAGS
-	vamphostsdk_LIBS=$pkg_cv_vamphostsdk_LIBS
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
+$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
+else
+	liblo_CFLAGS=$pkg_cv_liblo_CFLAGS
+	liblo_LIBS=$pkg_cv_liblo_LIBS
         { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS";LIBS="$LIBS $vamphostsdk_LIBS";SV_MODULE_FAILED=""
+	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED=""
 fi
 fi
 if test -n "$SV_MODULE_FAILED"; then
    as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
 ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
 if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE"
-else
-  as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-
-   if test -n "$SV_MODULE_LIB"; then
-     as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
+  HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED=""
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5
+$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;}
+fi
+
+
+   if test -z "$SV_MODULE_FAILED"; then
+      if test -n "$SV_MODULE_LIB"; then
+           as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
 $as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
 if eval \${$as_ac_Lib+:} false; then :
@@ -5301,25 +5510,27 @@
 if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
   LIBS="$LIBS -l$SV_MODULE_LIB"
 else
-  as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
+  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5
+$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;}
+fi
+
+      fi
    fi
 fi
 
 
-SV_MODULE_MODULE=rubberband
-SV_MODULE_VERSION_TEST="rubberband"
-SV_MODULE_HEADER=rubberband/RubberBandStretcher.h
-SV_MODULE_LIB=rubberband
-SV_MODULE_FUNC=rubberband_new
-SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z')
+SV_MODULE_MODULE=portaudio
+SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19"
+SV_MODULE_HEADER=portaudio.h
+SV_MODULE_LIB=portaudio
+SV_MODULE_FUNC=Pa_IsFormatSupported
+SV_MODULE_HAVE=HAVE_$(echo portaudio | tr 'a-z' 'A-Z')
 SV_MODULE_FAILED=1
-if test -n "$rubberband_LIBS" ; then
+if test -n "$portaudio_LIBS" ; then
    { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS"
-   LIBS="$LIBS $rubberband_LIBS"
+   CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS"
+   LIBS="$LIBS $portaudio_LIBS"
    SV_MODULE_FAILED=""
 fi
 if test -z "$SV_MODULE_VERSION_TEST" ; then
@@ -5328,11 +5539,11 @@
 if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5
-$as_echo_n "checking for rubberband... " >&6; }
-
-if test -n "$rubberband_CFLAGS"; then
-    pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio" >&5
+$as_echo_n "checking for portaudio... " >&6; }
+
+if test -n "$portaudio_CFLAGS"; then
+    pkg_cv_portaudio_CFLAGS="$portaudio_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5340,7 +5551,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_portaudio_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5348,8 +5559,8 @@
  else
     pkg_failed=untried
 fi
-if test -n "$rubberband_LIBS"; then
-    pkg_cv_rubberband_LIBS="$rubberband_LIBS"
+if test -n "$portaudio_LIBS"; then
+    pkg_cv_portaudio_LIBS="$portaudio_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
     { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
@@ -5357,7 +5568,7 @@
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
+  pkg_cv_portaudio_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -5378,164 +5589,12 @@
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        portaudio_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         else
-	        rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
+	        portaudio_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
-	echo "$rubberband_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS
-	rubberband_LIBS=$pkg_cv_rubberband_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED=""
-fi
-fi
-if test -n "$SV_MODULE_FAILED"; then
-   as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
-ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE"
-else
-  as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-
-   if test -n "$SV_MODULE_LIB"; then
-     as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
-$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
-if eval \${$as_ac_Lib+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-l$SV_MODULE_LIB  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char $SV_MODULE_FUNC ();
-int
-main ()
-{
-return $SV_MODULE_FUNC ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_cxx_try_link "$LINENO"; then :
-  eval "$as_ac_Lib=yes"
-else
-  eval "$as_ac_Lib=no"
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-eval ac_res=\$$as_ac_Lib
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
-  LIBS="$LIBS -l$SV_MODULE_LIB"
-else
-  as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-   fi
-fi
-
-
-
-SV_MODULE_MODULE=liblo
-SV_MODULE_VERSION_TEST=""
-SV_MODULE_HEADER=lo/lo.h
-SV_MODULE_LIB=lo
-SV_MODULE_FUNC=lo_address_new
-SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z')
-SV_MODULE_FAILED=1
-if test -n "$liblo_LIBS" ; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $liblo_CFLAGS"
-   LIBS="$LIBS $liblo_LIBS"
-   SV_MODULE_FAILED=""
-fi
-if test -z "$SV_MODULE_VERSION_TEST" ; then
-   SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE
-fi
-if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
-
-pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5
-$as_echo_n "checking for liblo... " >&6; }
-
-if test -n "$liblo_CFLAGS"; then
-    pkg_cv_liblo_CFLAGS="$liblo_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$liblo_LIBS"; then
-    pkg_cv_liblo_LIBS="$liblo_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-	        liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        else
-	        liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        fi
-	# Put the nasty error message in config.log where it belongs
-	echo "$liblo_PKG_ERRORS" >&5
+	echo "$portaudio_PKG_ERRORS" >&5
 
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
@@ -5545,166 +5604,11 @@
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
 else
-	liblo_CFLAGS=$pkg_cv_liblo_CFLAGS
-	liblo_LIBS=$pkg_cv_liblo_LIBS
+	portaudio_CFLAGS=$pkg_cv_portaudio_CFLAGS
+	portaudio_LIBS=$pkg_cv_portaudio_LIBS
         { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED=""
-fi
-fi
-if test -n "$SV_MODULE_FAILED"; then
-   as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
-ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED=""
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;}
-fi
-
-
-   if test -z "$SV_MODULE_FAILED"; then
-      if test -n "$SV_MODULE_LIB"; then
-           as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
-$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
-if eval \${$as_ac_Lib+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-l$SV_MODULE_LIB  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char $SV_MODULE_FUNC ();
-int
-main ()
-{
-return $SV_MODULE_FUNC ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_cxx_try_link "$LINENO"; then :
-  eval "$as_ac_Lib=yes"
-else
-  eval "$as_ac_Lib=no"
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-eval ac_res=\$$as_ac_Lib
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
-  LIBS="$LIBS -l$SV_MODULE_LIB"
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;}
-fi
-
-      fi
-   fi
-fi
-
-
-SV_MODULE_MODULE=portaudio_2_0
-SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19"
-SV_MODULE_HEADER=portaudio.h
-SV_MODULE_LIB=portaudio
-SV_MODULE_FUNC=Pa_IsFormatSupported
-SV_MODULE_HAVE=HAVE_$(echo portaudio_2_0 | tr 'a-z' 'A-Z')
-SV_MODULE_FAILED=1
-if test -n "$portaudio_2_0_LIBS" ; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS"
-   LIBS="$LIBS $portaudio_2_0_LIBS"
-   SV_MODULE_FAILED=""
-fi
-if test -z "$SV_MODULE_VERSION_TEST" ; then
-   SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE
-fi
-if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
-
-pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio_2_0" >&5
-$as_echo_n "checking for portaudio_2_0... " >&6; }
-
-if test -n "$portaudio_2_0_CFLAGS"; then
-    pkg_cv_portaudio_2_0_CFLAGS="$portaudio_2_0_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_portaudio_2_0_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$portaudio_2_0_LIBS"; then
-    pkg_cv_portaudio_2_0_LIBS="$portaudio_2_0_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_portaudio_2_0_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-	        portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        else
-	        portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        fi
-	# Put the nasty error message in config.log where it belongs
-	echo "$portaudio_2_0_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	portaudio_2_0_CFLAGS=$pkg_cv_portaudio_2_0_CFLAGS
-	portaudio_2_0_LIBS=$pkg_cv_portaudio_2_0_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS";LIBS="$LIBS $portaudio_2_0_LIBS";SV_MODULE_FAILED=""
+	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS";LIBS="$LIBS $portaudio_LIBS";SV_MODULE_FAILED=""
 fi
 fi
 if test -n "$SV_MODULE_FAILED"; then
--- a/configure.ac	Tue Jul 14 15:04:46 2015 +0100
+++ b/configure.ac	Wed Apr 20 12:06:28 2016 +0100
@@ -25,6 +25,9 @@
 AC_PROG_INSTALL
 AC_PROG_MKDIR_P
 
+# We are daringly making use of C++11 now
+AX_CXX_COMPILE_STDCXX_11(noext)
+
 AC_HEADER_STDC
 
 # These are the flags Autoconf guesses for us; we use them later if
@@ -50,9 +53,10 @@
 CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS"
 
 if test "x$GCC" = "xyes"; then
-        CXXFLAGS_DEBUG="-Wall -Woverloaded-virtual -Wextra -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -g -pipe"
-   	CXXFLAGS_RELEASE="-g0 -O2 -Wall -pipe"
-   	CXXFLAGS_MINIMAL="-g0 -O0"
+   	CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
+        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g"
+   	CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2"
+   	CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0"
 fi
 
 CXXFLAGS_BUILD="$CXXFLAGS_RELEASE"
@@ -79,12 +83,10 @@
 SV_MODULE_REQUIRED([fftw3f],[fftw3f >= 3.0.0],[fftw3.h],[fftw3f],[fftwf_execute])
 SV_MODULE_REQUIRED([sndfile],[sndfile >= 1.0.16],[sndfile.h],[sndfile],[sf_open])
 SV_MODULE_REQUIRED([samplerate],[samplerate >= 0.1.2],[samplerate.h],[samplerate],[src_new])
-SV_MODULE_REQUIRED([vamp],[vamp >= 2.1],[vamp/vamp.h],[],[])
-SV_MODULE_REQUIRED([vamphostsdk],[vamp-hostsdk >= 2.5],[vamp-hostsdk/PluginLoader.h],[vamp-hostsdk],[libvamphostsdk_v_2_5_present])
 SV_MODULE_REQUIRED([rubberband],[rubberband],[rubberband/RubberBandStretcher.h],[rubberband],[rubberband_new])
 
 SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new])
-SV_MODULE_OPTIONAL([portaudio_2_0],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported])
+SV_MODULE_OPTIONAL([portaudio],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported])
 SV_MODULE_OPTIONAL([JACK],[jack >= 0.100],[jack/jack.h],[jack],[jack_client_open])
 SV_MODULE_OPTIONAL([libpulse],[libpulse >= 0.9],[pulse/pulseaudio.h],[pulse],[pa_stream_new])
 SV_MODULE_OPTIONAL([lrdf],[lrdf >= 0.2],[lrdf.h],[lrdf],[lrdf_init])
--- a/layer/Colour3DPlotLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/Colour3DPlotLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -25,6 +25,7 @@
 #include <QImage>
 #include <QRect>
 #include <QTextStream>
+#include <QSettings>
 
 #include <iostream>
 
@@ -34,6 +35,8 @@
 #include <alloca.h>
 #endif
 
+using std::vector;
+
 //#define DEBUG_COLOUR_3D_PLOT_LAYER_PAINT 1
 
 
@@ -41,7 +44,6 @@
     m_model(0),
     m_cache(0),
     m_peaksCache(0),
-    m_peakResolution(256),
     m_cacheValidStart(0),
     m_cacheValidEnd(0),
     m_colourScale(LinearScale),
@@ -51,13 +53,18 @@
     m_binScale(LinearBinScale),
     m_normalizeColumns(false),
     m_normalizeVisibleArea(false),
+    m_normalizeHybrid(false),
     m_invertVertical(false),
     m_opaque(false),
     m_smooth(false),
+    m_peakResolution(256),
     m_miny(0),
     m_maxy(0)
 {
-    
+    QSettings settings;
+    settings.beginGroup("Preferences");
+    setColourMap(settings.value("colour-3d-plot-colour", ColourMapper::Green).toInt());
+    settings.endGroup();
 }
 
 Colour3DPlotLayer::~Colour3DPlotLayer()
@@ -77,8 +84,8 @@
     connectSignals(m_model);
 
     connect(m_model, SIGNAL(modelChanged()), this, SLOT(modelChanged()));
-    connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
-	    this, SLOT(modelChanged(size_t, size_t)));
+    connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+	    this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
 
     m_peakResolution = 256;
     if (model->getResolution() > 512) {
@@ -106,13 +113,13 @@
 }
 
 void
-Colour3DPlotLayer::cacheInvalid(size_t startFrame, size_t endFrame)
+Colour3DPlotLayer::cacheInvalid(sv_frame_t startFrame, sv_frame_t endFrame)
 {
-    if (!m_cache) return;
+    if (!m_cache || !m_model) return;
 
-    size_t modelResolution = m_model->getResolution();
-    size_t start = startFrame / modelResolution;
-    size_t end = endFrame / modelResolution + 1;
+    int modelResolution = m_model->getResolution();
+    int start = int(startFrame / modelResolution);
+    int end = int(endFrame / modelResolution + 1);
     if (m_cacheValidStart < end) m_cacheValidStart = end;
     if (m_cacheValidEnd > start) m_cacheValidEnd = start;
     if (m_cacheValidStart > m_cacheValidEnd) m_cacheValidEnd = m_cacheValidStart;
@@ -134,7 +141,7 @@
 }
 
 void
-Colour3DPlotLayer::modelChanged(size_t startFrame, size_t endFrame)
+Colour3DPlotLayer::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
 {
     if (!m_colourScaleSet && m_colourScale == LinearScale) {
         if (m_model && m_model->getWidth() > 50) {
@@ -233,11 +240,11 @@
 	*min = -50;
 	*max = 50;
 
-        *deflt = lrintf(log10(1.f) * 20.0);;
+        *deflt = int(lrint(log10(1.0) * 20.0));
 	if (*deflt < *min) *deflt = *min;
 	if (*deflt > *max) *deflt = *max;
 
-	val = lrintf(log10(m_gain) * 20.0);
+	val = int(lrint(log10(m_gain) * 20.0));
 	if (val < *min) val = *min;
 	if (val > *max) val = *max;
 
@@ -335,7 +342,7 @@
 Colour3DPlotLayer::setProperty(const PropertyName &name, int value)
 {
     if (name == "Gain") {
-	setGain(pow(10, float(value)/20.0));
+	setGain(float(pow(10, value/20.0)));
     } else if (name == "Colour Scale") {
 	switch (value) {
 	default:
@@ -430,6 +437,21 @@
 }
 
 void
+Colour3DPlotLayer::setNormalizeHybrid(bool n)
+{
+    if (m_normalizeHybrid == n) return;
+    m_normalizeHybrid = n;
+    cacheInvalid();
+    emit layerParametersChanged();
+}
+
+bool
+Colour3DPlotLayer::getNormalizeHybrid() const
+{
+    return m_normalizeHybrid;
+}
+
+void
 Colour3DPlotLayer::setNormalizeVisibleArea(bool n)
 {
     if (m_normalizeVisibleArea == n) return;
@@ -488,12 +510,12 @@
 }
 
 void
-Colour3DPlotLayer::setLayerDormant(const View *v, bool dormant)
+Colour3DPlotLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     if (dormant) {
 
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-        SVDEBUG << "Colour3DPlotLayer::setLayerDormant(" << dormant << ")"
+        cerr << "Colour3DPlotLayer::setLayerDormant(" << dormant << ")"
                   << endl;
 #endif
 
@@ -512,21 +534,26 @@
 }
 
 bool
-Colour3DPlotLayer::isLayerScrollable(const View *v) const
+Colour3DPlotLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
-    if (m_normalizeVisibleArea) return false;
+    if (m_normalizeVisibleArea) {
+        return false;
+    }
+    if (shouldPaintDenseIn(v)) {
+        return true;
+    }
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
 }
 
 bool
-Colour3DPlotLayer::getValueExtents(float &min, float &max,
+Colour3DPlotLayer::getValueExtents(double &min, double &max,
                                    bool &logarithmic, QString &unit) const
 {
     if (!m_model) return false;
 
     min = 0;
-    max = m_model->getHeight();
+    max = double(m_model->getHeight());
 
     logarithmic = false;
     unit = "";
@@ -535,34 +562,43 @@
 }
 
 bool
-Colour3DPlotLayer::getDisplayExtents(float &min, float &max) const
+Colour3DPlotLayer::getDisplayExtents(double &min, double &max) const
 {
     if (!m_model) return false;
 
+    double hmax = double(m_model->getHeight());
+    
     min = m_miny;
     max = m_maxy;
     if (max <= min) {
         min = 0;
-        max = m_model->getHeight();
+        max = hmax;
     }
     if (min < 0) min = 0;
-    if (max > m_model->getHeight()) max = m_model->getHeight();
+    if (max > hmax) max = hmax;
 
     return true;
 }
 
 bool
-Colour3DPlotLayer::setDisplayExtents(float min, float max)
+Colour3DPlotLayer::setDisplayExtents(double min, double max)
 {
     if (!m_model) return false;
 
-    m_miny = lrintf(min);
-    m_maxy = lrintf(max);
+    m_miny = int(lrint(min));
+    m_maxy = int(lrint(max));
     
     emit layerParametersChanged();
     return true;
 }
 
+bool
+Colour3DPlotLayer::getYScaleValue(const LayerGeometryProvider *, int,
+                                  double &, QString &) const
+{
+    return false;//!!!
+}
+
 int
 Colour3DPlotLayer::getVerticalZoomSteps(int &defaultStep) const
 {
@@ -578,9 +614,9 @@
 {
     if (!m_model) return 0;
 
-    float min, max;
+    double min, max;
     getDisplayExtents(min, max);
-    return m_model->getHeight() - lrintf(max - min);
+    return m_model->getHeight() - int(lrint(max - min));
 }
 
 void
@@ -592,8 +628,8 @@
 
     int dist = m_model->getHeight() - step;
     if (dist < 1) dist = 1;
-    float centre = m_miny + (float(m_maxy) - float(m_miny)) / 2.f;
-    m_miny = lrintf(centre - float(dist)/2);
+    double centre = m_miny + (m_maxy - m_miny) / 2.0;
+    m_miny = int(lrint(centre - dist/2.0));
     if (m_miny < 0) m_miny = 0;
     m_maxy = m_miny + dist;
     if (m_maxy > m_model->getHeight()) m_maxy = m_model->getHeight();
@@ -612,59 +648,71 @@
                                  0, m_model->getHeight(), "");
 }
 
-float
-Colour3DPlotLayer::getYForBin(View *v, float bin) const
+double
+Colour3DPlotLayer::getYForBin(LayerGeometryProvider *v, double bin) const
 {
-    float y = bin;
+    double y = bin;
     if (!m_model) return y;
-    float mn = 0, mx = m_model->getHeight();
+    double mn = 0, mx = m_model->getHeight();
     getDisplayExtents(mn, mx);
-    float h = v->height();
+    double h = v->getPaintHeight();
     if (m_binScale == LinearBinScale) {
         y = h - (((bin - mn) * h) / (mx - mn));
     } else {
-        float logmin = mn + 1, logmax = mx + 1;
+        double logmin = mn + 1, logmax = mx + 1;
         LogRange::mapRange(logmin, logmax);
         y = h - (((LogRange::map(bin + 1) - logmin) * h) / (logmax - logmin));
     }
     return y;
 }
 
-float
-Colour3DPlotLayer::getBinForY(View *v, float y) const
+int
+Colour3DPlotLayer::getIYForBin(LayerGeometryProvider *v, int bin) const
 {
-    float bin = y;
+    return int(round(getYForBin(v, bin)));
+}
+
+double
+Colour3DPlotLayer::getBinForY(LayerGeometryProvider *v, double y) const
+{
+    double bin = y;
     if (!m_model) return bin;
-    float mn = 0, mx = m_model->getHeight();
+    double mn = 0, mx = m_model->getHeight();
     getDisplayExtents(mn, mx);
-    float h = v->height();
+    double h = v->getPaintHeight();
     if (m_binScale == LinearBinScale) {
         bin = mn + ((h - y) * (mx - mn)) / h;
     } else {
-        float logmin = mn + 1, logmax = mx + 1;
+        double logmin = mn + 1, logmax = mx + 1;
         LogRange::mapRange(logmin, logmax);
         bin = LogRange::unmap(logmin + ((h - y) * (logmax - logmin)) / h) - 1;
     }
     return bin;
 }
 
+int
+Colour3DPlotLayer::getIBinForY(LayerGeometryProvider *v, int y) const
+{
+    return int(floor(getBinForY(v, y)));
+}
+
 QString
-Colour3DPlotLayer::getFeatureDescription(View *v, QPoint &pos) const
+Colour3DPlotLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     if (!m_model) return "";
 
     int x = pos.x();
     int y = pos.y();
 
-    size_t modelStart = m_model->getStartFrame();
-    size_t modelResolution = m_model->getResolution();
+    sv_frame_t modelStart = m_model->getStartFrame();
+    int modelResolution = m_model->getResolution();
 
-    float srRatio =
-        float(v->getViewManager()->getMainModelSampleRate()) /
-        float(m_model->getSampleRate());
+    double srRatio =
+        v->getViewManager()->getMainModelSampleRate() /
+        m_model->getSampleRate();
 
-    int sx0 = int((v->getFrameForX(x) / srRatio - long(modelStart)) /
-                  long(modelResolution));
+    int sx0 = int((double(v->getFrameForX(x)) / srRatio - double(modelStart)) /
+                  modelResolution);
 
     int f0 = sx0 * modelResolution;
     int f1 =  f0 + modelResolution;
@@ -680,10 +728,14 @@
     if (symin < 0) symin = 0;
     if (symax > sh) symax = sh;
 
- //    float binHeight = float(v->height()) / (symax - symin);
-//    int sy = int((v->height() - y) / binHeight) + symin;
+ //    double binHeight = double(v->getPaintHeight()) / (symax - symin);
+//    int sy = int((v->getPaintHeight() - y) / binHeight) + symin;
 
-    int sy = getBinForY(v, y);
+    int sy = getIBinForY(v, y);
+
+    if (sy < 0 || sy >= m_model->getHeight()) {
+        return "";
+    }
 
     if (m_invertVertical) sy = m_model->getHeight() - sy - 1;
 
@@ -707,14 +759,15 @@
 }
 
 int
-Colour3DPlotLayer::getColourScaleWidth(QPainter &) const
+Colour3DPlotLayer::getColourScaleWidth(QPainter &p) const
 {
-    int cw = 20;
+    // Font is rotated
+    int cw = p.fontMetrics().height();
     return cw;
 }
 
 int
-Colour3DPlotLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const
+Colour3DPlotLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
     if (!m_model) return 0;
 
@@ -722,7 +775,7 @@
     int tw = paint.fontMetrics().width(sampleText);
     bool another = false;
 
-    for (size_t i = 0; i < m_model->getHeight(); ++i) {
+    for (int i = 0; i < m_model->getHeight(); ++i) {
 	if (m_model->getBinName(i).length() > sampleText.length()) {
 	    sampleText = m_model->getBinName(i);
             another = true;
@@ -736,7 +789,7 @@
 }
 
 void
-Colour3DPlotLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const
+Colour3DPlotLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
     if (!m_model) return;
 
@@ -747,11 +800,11 @@
     int ch = h - 20;
     if (ch > 20 && m_cache) {
 
-        float min = m_model->getMinimumLevel();
-        float max = m_model->getMaximumLevel();
+        double min = m_model->getMinimumLevel();
+        double max = m_model->getMaximumLevel();
 
-        float mmin = min;
-        float mmax = max;
+        double mmin = min;
+        double mmax = max;
 
         if (m_colourScale == LogScale) {
             LogRange::mapRange(mmin, mmax);
@@ -760,23 +813,23 @@
             mmax = 1.f;
         } else if (m_colourScale == AbsoluteScale) {
             if (mmin < 0) {
-                if (fabsf(mmin) > fabsf(mmax)) mmax = fabsf(mmin);
-                else mmax = fabsf(mmax);
+                if (fabs(mmin) > fabs(mmax)) mmax = fabs(mmin);
+                else mmax = fabs(mmax);
                 mmin = 0;
             } else {
-                mmin = fabsf(mmin);
-                mmax = fabsf(mmax);
+                mmin = fabs(mmin);
+                mmax = fabs(mmax);
             }
         }
     
-        if (max == min) max = min + 1.0;
-        if (mmax == mmin) mmax = mmin + 1.0;
+        if (max == min) max = min + 1.f;
+        if (mmax == mmin) mmax = mmin + 1.f;
     
         paint.setPen(v->getForeground());
         paint.drawRect(4, 10, cw - 8, ch+1);
 
         for (int y = 0; y < ch; ++y) {
-            float value = ((max - min) * (ch - y - 1)) / ch + min;
+            double value = ((max - min) * (double(ch-y) - 1.0)) / double(ch) + min;
             if (m_colourScale == LogScale) {
                 value = LogRange::map(value);
             }
@@ -796,7 +849,7 @@
         paint.save();
 
         QFont font = paint.font();
-        font.setPixelSize(10);
+        font.setPixelSize(int(font.pixelSize() * 0.65));
         paint.setFont(font);
 
         int msw = paint.fontMetrics().width(maxstr);
@@ -834,18 +887,20 @@
 
     int py = h;
 
-    for (size_t i = symin; i <= symax; ++i) {
+    int defaultFontHeight = paint.fontMetrics().height();
+    
+    for (int i = symin; i <= symax; ++i) {
 
         int y0;
 
-        y0 = lrintf(getYForBin(v, i));
+        y0 = getIYForBin(v, i);
         int h = py - y0;
 
         if (i > symin) {
             if (paint.fontMetrics().height() >= h) {
-                if (h >= 8) {
+                if (h >= defaultFontHeight * 0.8) {
                     QFont tf = paint.font();
-                    tf.setPixelSize(h-2);
+                    tf.setPixelSize(int(h * 0.8));
                     paint.setFont(tf);
                 } else {
                     continue;
@@ -861,7 +916,7 @@
 
         if (i > symin) {
 
-            size_t idx = i - 1;
+            int idx = i - 1;
             if (m_invertVertical) idx = m_model->getHeight() - idx - 1;
 
             QString text = m_model->getBinName(idx);
@@ -878,64 +933,85 @@
 }
 
 DenseThreeDimensionalModel::Column
-Colour3DPlotLayer::getColumn(size_t col) const
+Colour3DPlotLayer::getColumn(int col) const
 {
+    Profiler profiler("Colour3DPlotLayer::getColumn");
+
     DenseThreeDimensionalModel::Column values = m_model->getColumn(col);
-    while (values.size() < m_model->getHeight()) values.push_back(0.f);
-    if (!m_normalizeColumns) return values;
+    values.resize(m_model->getHeight(), 0.f);
+    if (!m_normalizeColumns && !m_normalizeHybrid) return values;
 
-    float colMax = 0.f, colMin = 0.f;
-    float min = 0.f, max = 0.f;
+    double colMax = 0.f, colMin = 0.f;
+    double min = 0.f, max = 0.f;
 
+    int nv = int(values.size());
+    
     min = m_model->getMinimumLevel();
     max = m_model->getMaximumLevel();
 
-    for (size_t y = 0; y < values.size(); ++y) {
+    for (int y = 0; y < nv; ++y) {
         if (y == 0 || values.at(y) > colMax) colMax = values.at(y);
         if (y == 0 || values.at(y) < colMin) colMin = values.at(y);
     }
     if (colMin == colMax) colMax = colMin + 1;
     
-    for (size_t y = 0; y < values.size(); ++y) {
+    for (int y = 0; y < nv; ++y) {
     
-        float value = values.at(y);
-        float norm = (value - colMin) / (colMax - colMin);
-        float newvalue = min + (max - min) * norm;
+        double value = values.at(y);
+        double norm = (value - colMin) / (colMax - colMin);
+        double newvalue = min + (max - min) * norm;
 
-        if (value != newvalue) values[y] = newvalue;
+        if (value != newvalue) values[y] = float(newvalue);
+    }
+
+    if (m_normalizeHybrid && (colMax > 0.0)) {
+        double logmax = log10(colMax);
+        for (int y = 0; y < nv; ++y) {
+            values[y] = float(values[y] * logmax);
+        }
     }
 
     return values;
 }
     
 void
-Colour3DPlotLayer::fillCache(size_t firstBin, size_t lastBin) const
+Colour3DPlotLayer::fillCache(int firstColumn, int lastColumn) const
 {
-    Profiler profiler("Colour3DPlotLayer::fillCache");
+    // This call requests a (perhaps partial) fill of the cache
+    // between model columns firstColumn and lastColumn inclusive.
+    // The cache itself always has size sufficient to contain the
+    // whole model, but its validity may be less, depending on which
+    // regions have been requested via calls to this function.  Note
+    // that firstColumn and lastColumn are *model* column numbers. If
+    // the model starts at a frame > 0, a firstColumn of zero still
+    // corresponds to the first column in the model, not the first
+    // column on the resulting rendered layer.
 
-    size_t modelStart = m_model->getStartFrame();
-    size_t modelEnd = m_model->getEndFrame();
-    size_t modelResolution = m_model->getResolution();
+    Profiler profiler("Colour3DPlotLayer::fillCache", true);
+
+    int cacheWidth = m_model->getWidth();
+    int cacheHeight = m_model->getHeight();
 
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-    SVDEBUG << "Colour3DPlotLayer::fillCache: " << firstBin << " -> " << lastBin << endl;
+    cerr << "Colour3DPlotLayer::fillCache: range " << firstColumn << " -> " << lastColumn << " (cache size will be " << cacheWidth << " x " << cacheHeight << ")" << endl;
 #endif
 
-    size_t modelStartBin = modelStart / modelResolution;
-    size_t modelEndBin = modelEnd / modelResolution;
-
-    size_t cacheWidth = modelEndBin - modelStartBin + 1;
-    if (lastBin > modelEndBin) cacheWidth = lastBin - modelStartBin + 1;
-    size_t cacheHeight = m_model->getHeight();
-
-    if (m_cache && (m_cache->height() != int(cacheHeight))) {
+    if (m_cache && m_cache->height() != cacheHeight) {
+        // height has changed: delete everything rather than resizing
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
+        cerr << "Colour3DPlotLayer::fillCache: Cache height has changed, recreating" << endl;
+#endif
         delete m_cache;
         delete m_peaksCache;
         m_cache = 0;
         m_peaksCache = 0;
     } 
 
-    if (m_cache && (m_cache->width() != int(cacheWidth))) {
+    if (m_cache && m_cache->width() != cacheWidth) {
+        // width has changed and we have an existing cache: resize it
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
+        cerr << "Colour3DPlotLayer::fillCache: Cache width has changed, resizing existing cache" << endl;
+#endif
         QImage *newCache =
             new QImage(m_cache->copy(0, 0, cacheWidth, cacheHeight));
         delete m_cache;
@@ -943,22 +1019,24 @@
         if (m_peaksCache) {
             QImage *newPeaksCache =
                 new QImage(m_peaksCache->copy
-                           (0, 0, cacheWidth / m_peakResolution, cacheHeight));
+                           (0, 0, cacheWidth / m_peakResolution + 1, cacheHeight));
             delete m_peaksCache;
             m_peaksCache = newPeaksCache;
         }
     }
 
     if (!m_cache) {
-        m_cache = new QImage
-            (cacheWidth, cacheHeight, QImage::Format_Indexed8);
-// No longer exists in Qt5:        m_cache->setNumColors(256);
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
+        cerr << "Colour3DPlotLayer::fillCache: Have no cache, making one" << endl;
+#endif
+        m_cache = new QImage(cacheWidth, cacheHeight, QImage::Format_Indexed8);
+        m_cache->setColorCount(256);
         m_cache->fill(0);
         if (!m_normalizeVisibleArea) {
             m_peaksCache = new QImage
                 (cacheWidth / m_peakResolution + 1, cacheHeight,
                  QImage::Format_Indexed8);
-// No longer exists in Qt5:            m_peaksCache->setNumColors(256);
+            m_peaksCache->setColorCount(256);
             m_peaksCache->fill(0);
         } else if (m_peaksCache) {
             delete m_peaksCache;
@@ -968,20 +1046,26 @@
         m_cacheValidEnd = 0;
     }
 
-    if (m_cacheValidStart <= firstBin && m_cacheValidEnd >= lastBin) {
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
+    cerr << "cache size = " << m_cache->width() << "x" << m_cache->height()
+         << " peaks cache size = " << m_peaksCache->width() << "x" << m_peaksCache->height() << endl;
+#endif
+
+    if (m_cacheValidStart <= firstColumn && m_cacheValidEnd >= lastColumn) {
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
         cerr << "Cache is valid in this region already" << endl;
 #endif
         return;
     }
     
-    size_t fillStart = firstBin;
-    size_t fillEnd = lastBin;
+    int fillStart = firstColumn;
+    int fillEnd = lastColumn;
 
-    if (fillStart < modelStartBin) fillStart = modelStartBin;
-    if (fillStart > modelEndBin) fillStart = modelEndBin;
-    if (fillEnd < modelStartBin) fillEnd = modelStartBin;
-    if (fillEnd > modelEndBin) fillEnd = modelEndBin;
+    if (fillStart >= cacheWidth) fillStart = cacheWidth-1;
+    if (fillStart < 0) fillStart = 0;
+    if (fillEnd >= cacheWidth) fillEnd = cacheWidth-1;
+    if (fillEnd < 0) fillEnd = 0;
+    if (fillEnd < fillStart) fillEnd = fillStart;
 
     bool normalizeVisible = (m_normalizeVisibleArea && !m_normalizeColumns);
 
@@ -999,20 +1083,21 @@
 
     } else {
 
-        // the only valid area, ever, is the currently visible one
+        // when normalising the visible area, the only valid area,
+        // ever, is the currently visible one
 
         m_cacheValidStart = fillStart;
         m_cacheValidEnd = fillEnd;
     }
 
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-    cerr << "Cache size " << cacheWidth << "x" << cacheHeight << " will be valid from " << m_cacheValidStart << " to " << m_cacheValidEnd << endl;
+    cerr << "Cache size " << cacheWidth << "x" << cacheHeight << " will be valid from " << m_cacheValidStart << " to " << m_cacheValidEnd << " (fillStart = " << fillStart << ", fillEnd = " << fillEnd << ")" << endl;
 #endif
 
     DenseThreeDimensionalModel::Column values;
 
-    float min = m_model->getMinimumLevel();
-    float max = m_model->getMaximumLevel();
+    double min = m_model->getMinimumLevel();
+    double max = m_model->getMaximumLevel();
 
     if (m_colourScale == LogScale) {
         LogRange::mapRange(min, max);
@@ -1021,16 +1106,16 @@
         max = 1.f;
     } else if (m_colourScale == AbsoluteScale) {
         if (min < 0) {
-            if (fabsf(min) > fabsf(max)) max = fabsf(min);
-            else max = fabsf(max);
+            if (fabs(min) > fabs(max)) max = fabs(min);
+            else max = fabs(max);
             min = 0;
         } else {
-            min = fabsf(min);
-            max = fabsf(max);
+            min = fabs(min);
+            max = fabs(max);
         }
     }
     
-    if (max == min) max = min + 1.0;
+    if (max == min) max = min + 1.f;
     
     ColourMapper mapper(m_colourMap, 0.f, 255.f);
     
@@ -1044,18 +1129,18 @@
         }
     }
     
-    float visibleMax = 0.f, visibleMin = 0.f;
+    double visibleMax = 0.f, visibleMin = 0.f;
 
     if (normalizeVisible) {
         
-        for (size_t c = fillStart; c <= fillEnd; ++c) {
+        for (int c = fillStart; c <= fillEnd; ++c) {
 	
             values = getColumn(c);
 
-            float colMax = 0.f, colMin = 0.f;
+            double colMax = 0.f, colMin = 0.f;
 
-            for (size_t y = 0; y < cacheHeight; ++y) {
-                if (y >= values.size()) break;
+            for (int y = 0; y < cacheHeight; ++y) {
+                if (!in_range_for(values, y)) break;
                 if (y == 0 || values[y] > colMax) colMax = values[y];
                 if (y == 0 || values[y] < colMin) colMin = values[y];
             }
@@ -1070,12 +1155,12 @@
             if (visibleMin > visibleMax) std::swap(visibleMin, visibleMax);
         } else if (m_colourScale == AbsoluteScale) {
             if (visibleMin < 0) {
-                if (fabsf(visibleMin) > fabsf(visibleMax)) visibleMax = fabsf(visibleMin);
-                else visibleMax = fabsf(visibleMax);
+                if (fabs(visibleMin) > fabs(visibleMax)) visibleMax = fabs(visibleMin);
+                else visibleMax = fabs(visibleMax);
                 visibleMin = 0;
             } else {
-                visibleMin = fabsf(visibleMin);
-                visibleMax = fabsf(visibleMax);
+                visibleMin = fabs(visibleMin);
+                visibleMax = fabs(visibleMax);
             }
         }
     }
@@ -1090,14 +1175,22 @@
         }
     }
 
-    for (size_t c = fillStart; c <= fillEnd; ++c) {
+    Profiler profiler2("Colour3DPlotLayer::fillCache: filling", true);
+
+    for (int c = fillStart; c <= fillEnd; ++c) {
 	
         values = getColumn(c);
 
-        for (size_t y = 0; y < cacheHeight; ++y) {
+        if (c >= m_cache->width()) {
+            cerr << "ERROR: column " << c << " >= cache width "
+                 << m_cache->width() << endl;
+            continue;
+        }
 
-            float value = min;
-            if (y < values.size()) {
+        for (int y = 0; y < cacheHeight; ++y) {
+
+            double value = min;
+            if (in_range_for(values, y)) {
                 value = values.at(y);
             }
 
@@ -1106,11 +1199,11 @@
             if (m_colourScale == LogScale) {
                 value = LogRange::map(value);
             } else if (m_colourScale == AbsoluteScale) {
-                value = fabsf(value);
+                value = fabs(value);
             }
             
             if (normalizeVisible) {
-                float norm = (value - visibleMin) / (visibleMax - visibleMin);
+                double norm = (value - visibleMin) / (visibleMax - visibleMin);
                 value = min + (max - min) * norm;
             }
 
@@ -1122,19 +1215,35 @@
             if (m_invertVertical) {
                 m_cache->setPixel(c, cacheHeight - y - 1, pixel);
             } else {
-                m_cache->setPixel(c, y, pixel);
+                if (y >= m_cache->height()) {
+                    cerr << "ERROR: row " << y << " >= cache height " << m_cache->height() << endl;
+                } else {
+                    m_cache->setPixel(c, y, pixel);
+                }
             }
         }
 
         if (peaks) {
-            size_t notch = (c % m_peakResolution);
+            int notch = (c % m_peakResolution);
             if (notch == m_peakResolution-1 || c == fillEnd) {
-                size_t pc = c / m_peakResolution;
-                for (size_t y = 0; y < cacheHeight; ++y) {
+                int pc = c / m_peakResolution;
+                if (pc >= m_peaksCache->width()) {
+                    cerr << "ERROR: peak column " << pc
+                         << " (from col " << c << ") >= peaks cache width "
+                         << m_peaksCache->width() << endl;
+                    continue;
+                }
+                for (int y = 0; y < cacheHeight; ++y) {
                     if (m_invertVertical) {
                         m_peaksCache->setPixel(pc, cacheHeight - y - 1, peaks[y]);
                     } else {
-                        m_peaksCache->setPixel(pc, y, peaks[y]);
+                        if (y >= m_peaksCache->height()) {
+                            cerr << "ERROR: row " << y
+                                 << " >= peaks cache height "
+                                 << m_peaksCache->height() << endl;
+                        } else {
+                            m_peaksCache->setPixel(pc, y, peaks[y]);
+                        }
                     }
                 }
                 for (int y = 0; y < cacheHeight; ++y) {
@@ -1147,8 +1256,25 @@
     delete[] peaks;
 }
 
+bool
+Colour3DPlotLayer::shouldPaintDenseIn(const LayerGeometryProvider *v) const
+{
+    if (!m_model || !v || !(v->getViewManager())) {
+        return false;
+    }
+    double srRatio =
+        v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate();
+    if (m_opaque || 
+        m_smooth ||
+        m_model->getHeight() >= v->getPaintHeight() ||
+        ((m_model->getResolution() * srRatio) / v->getZoomLevel()) < 2) {
+        return true;
+    }
+    return false;
+}
+
 void
-Colour3DPlotLayer::paint(View *v, QPainter &paint, QRect rect) const
+Colour3DPlotLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
 /*
     if (m_model) {
@@ -1157,23 +1283,23 @@
 */
     Profiler profiler("Colour3DPlotLayer::paint");
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-    SVDEBUG << "Colour3DPlotLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl;
+    cerr << "Colour3DPlotLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", rect is (" << rect.x() << "," << rect.y() << ") " << rect.width() << "x" << rect.height() << endl;
 #endif
 
     int completion = 0;
     if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) {
 	if (completion > 0) {
-	    paint.fillRect(0, 10, v->width() * completion / 100,
+	    paint.fillRect(0, 10, v->getPaintWidth() * completion / 100,
 			   10, QColor(120, 120, 120));
 	}
 	return;
     }
 
-    if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->rect();
+    if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->getPaintRect();
 
-    size_t modelStart = m_model->getStartFrame();
-    size_t modelEnd = m_model->getEndFrame();
-    size_t modelResolution = m_model->getResolution();
+    sv_frame_t modelStart = m_model->getStartFrame();
+    sv_frame_t modelEnd = m_model->getEndFrame();
+    int modelResolution = m_model->getResolution();
 
     // The cache is from the model's start frame to the model's end
     // frame at the model's window increment frames per pixel.  We
@@ -1187,16 +1313,16 @@
     int x0 = rect.left();
     int x1 = rect.right() + 1;
 
-    int h = v->height();
+    int h = v->getPaintHeight();
 
-    float srRatio =
-        float(v->getViewManager()->getMainModelSampleRate()) /
-        float(m_model->getSampleRate());
+    double srRatio =
+        v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate();
 
-    int sx0 = int((v->getFrameForX(x0) / srRatio - long(modelStart))
-                  / long(modelResolution));
-    int sx1 = int((v->getFrameForX(x1) / srRatio - long(modelStart))
-                  / long(modelResolution));
+    // the s-prefix values are source, i.e. model, column and bin numbers
+    int sx0 = int((double(v->getFrameForX(x0)) / srRatio - double(modelStart))
+                  / modelResolution);
+    int sx1 = int((double(v->getFrameForX(x1)) / srRatio - double(modelStart))
+                  / modelResolution);
     int sh = m_model->getHeight();
 
     int symin = m_miny;
@@ -1213,38 +1339,36 @@
               sx1 < 0 ? 0 : sx1);
 
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-    SVDEBUG << "Colour3DPlotLayer::paint: height = "<< m_model->getHeight() << ", modelStart = " << modelStart << ", resolution = " << modelResolution << ", model rate = " << m_model->getSampleRate() << " (zoom level = " << v->getZoomLevel() << ", srRatio = " << srRatio << ")" << endl;
+    cerr << "Colour3DPlotLayer::paint: height = "<< m_model->getHeight() << ", modelStart = " << modelStart << ", resolution = " << modelResolution << ", model rate = " << m_model->getSampleRate() << " (zoom level = " << v->getZoomLevel() << ", srRatio = " << srRatio << ")" << endl;
 #endif
 
-    if (m_opaque || 
-        m_smooth ||
-        int(m_model->getHeight()) >= v->height() ||
-        ((modelResolution * srRatio) / v->getZoomLevel()) < 2) {
+    if (shouldPaintDenseIn(v)) {
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-        SVDEBUG << "calling paintDense" << endl;
+        cerr << "calling paintDense" << endl;
 #endif
         paintDense(v, paint, rect);
         return;
     }
 
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-    SVDEBUG << "Colour3DPlotLayer::paint: w " << x1-x0 << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sx1-sx0 << ", sh " << sh << endl;
+    cerr << "Colour3DPlotLayer::paint: w " << x1-x0 << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sx1-sx0 << ", sh " << sh << endl;
     cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", resolution " << m_model->getResolution() << endl;
 #endif
 
     QPoint illuminatePos;
     bool illuminate = v->shouldIlluminateLocalFeatures(this, illuminatePos);
-    char labelbuf[10];
+    
+    const int buflen = 40;
+    char labelbuf[buflen];
 
     for (int sx = sx0; sx <= sx1; ++sx) {
 
-	int fx = sx * int(modelResolution);
+	sv_frame_t fx = sx * modelResolution + modelStart;
 
-	if (fx + int(modelResolution) <= int(modelStart) ||
-	    fx > int(modelEnd)) continue;
+	if (fx + modelResolution <= modelStart || fx > modelEnd) continue;
 
-        int rx0 = v->getXForFrame(int((fx + int(modelStart)) * srRatio));
-	int rx1 = v->getXForFrame(int((fx + int(modelStart) + int(modelResolution) + 1) * srRatio));
+        int rx0 = v->getXForFrame(int(double(fx) * srRatio));
+	int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * srRatio));
 
 	int rw = rx1 - rx0;
 	if (rw < 1) rw = 1;
@@ -1255,8 +1379,8 @@
         
 	for (int sy = symin; sy < symax; ++sy) {
 
-            int ry0 = getYForBin(v, sy);
-            int ry1 = getYForBin(v, sy + 1);
+            int ry0 = getIYForBin(v, sy);
+            int ry1 = getIYForBin(v, sy + 1);
             QRect r(rx0, ry1, rw, ry0 - ry1);
 
 	    QRgb pixel = qRgb(255, 255, 255);
@@ -1298,13 +1422,15 @@
 	    if (showLabel) {
 		if (sx >= 0 && sx < m_cache->width() &&
 		    sy >= 0 && sy < m_cache->height()) {
-		    float value = m_model->getValueAt(sx, sy);
-		    sprintf(labelbuf, "%06f", value);
+		    double value = m_model->getValueAt(sx, sy);
+		    snprintf(labelbuf, buflen, "%06f", value);
 		    QString text(labelbuf);
-		    paint.setPen(v->getBackground());
-		    paint.drawText(rx0 + 2,
-				   ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(),
-				   text);
+		    v->drawVisibleText
+                        (paint,
+                         rx0 + 2,
+                         ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(),
+                         text,
+                         View::OutlinedText);
 		}
 	    }
 	}
@@ -1312,23 +1438,23 @@
 }
 
 void
-Colour3DPlotLayer::paintDense(View *v, QPainter &paint, QRect rect) const
+Colour3DPlotLayer::paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    Profiler profiler("Colour3DPlotLayer::paintDense");
+    Profiler profiler("Colour3DPlotLayer::paintDense", true);
     if (!m_cache) return;
 
-    float modelStart = m_model->getStartFrame();
-    float modelResolution = m_model->getResolution();
+    double modelStart = double(m_model->getStartFrame());
+    double modelResolution = double(m_model->getResolution());
 
-    int mmsr = v->getViewManager()->getMainModelSampleRate();
-    int msr = m_model->getSampleRate();
-    float srRatio = float(mmsr) / float(msr);
+    sv_samplerate_t mmsr = v->getViewManager()->getMainModelSampleRate();
+    sv_samplerate_t msr = m_model->getSampleRate();
+    double srRatio = mmsr / msr;
 
     int x0 = rect.left();
     int x1 = rect.right() + 1;
 
     const int w = x1 - x0; // const so it can be used as array size below
-    int h = v->height(); // we always paint full height
+    int h = v->getPaintHeight(); // we always paint full height
     int sh = m_model->getHeight();
 
     int symin = m_miny;
@@ -1349,58 +1475,66 @@
     int zoomLevel = v->getZoomLevel();
     
     QImage *source = m_cache;
-    
-    SVDEBUG << "modelResolution " << modelResolution << ", srRatio "
-              << srRatio << ", m_peakResolution " << m_peakResolution
-              << ", zoomLevel " << zoomLevel << ", result "
-              << ((modelResolution * srRatio * m_peakResolution) / zoomLevel)
-              << endl;
+
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT    
+    cerr << "modelResolution " << modelResolution << ", srRatio "
+         << srRatio << ", m_peakResolution " << m_peakResolution
+         << ", zoomLevel " << zoomLevel << ", result "
+         << ((modelResolution * srRatio * m_peakResolution) / zoomLevel)
+         << endl;
+#endif
 
     if (m_peaksCache) {
         if (((modelResolution * srRatio * m_peakResolution) / zoomLevel) < 1) {
-            SVDEBUG << "using peaks cache" << endl;
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT    
+            cerr << "using peaks cache" << endl;
+#endif
             source = m_peaksCache;
             modelResolution *= m_peakResolution;
         } else {
-            SVDEBUG << "not using peaks cache" << endl;
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT    
+            cerr << "not using peaks cache" << endl;
+#endif
         }
     } else {
-        SVDEBUG << "have no peaks cache" << endl;
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT    
+        cerr << "have no peaks cache" << endl;
+#endif
     }
 
-    int psy1i = -1;
     int sw = source->width();
     
-    long xf = -1;
-    long nxf = v->getFrameForX(x0);
+    sv_frame_t xf = -1;
+    sv_frame_t nxf = v->getFrameForX(x0);
 
-    float epsilon = 0.000001;
+    double epsilon = 0.000001;
 
-#ifdef __GNUC__
-    float sxa[w * 2];
-#else
-    float *sxa = (float *)alloca(w * 2 * sizeof(float));
-#endif
+    vector<double> sxa(w*2);
+    
     for (int x = 0; x < w; ++x) {
 
         xf = nxf;
         nxf = xf + zoomLevel;
 
-        float sx0 = (float(xf) / srRatio - modelStart) / modelResolution;
-        float sx1 = (float(nxf) / srRatio - modelStart) / modelResolution;
+        double sx0 = (double(xf) / srRatio - modelStart) / modelResolution;
+        double sx1 = (double(nxf) / srRatio - modelStart) / modelResolution;
 
         sxa[x*2] = sx0;
         sxa[x*2 + 1] = sx1;
     }
 
-    float logmin = symin+1, logmax = symax+1;
+    double logmin = symin+1, logmax = symax+1;
     LogRange::mapRange(logmin, logmax);
 
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
+    cerr << "m_smooth = " << m_smooth << ", w = " << w << ", h = " << h << endl;
+#endif
+
     if (m_smooth) {
         
         for (int y = 0; y < h; ++y) {
 
-            float sy = getBinForY(v, y) - 0.5;
+            double sy = getBinForY(v, y) - 0.5;
             int syi = int(sy + epsilon);
             if (syi < 0 || syi >= source->height()) continue;
 
@@ -1417,50 +1551,52 @@
 
                 targetLine[x] = 0;
 
-                float sx0 = sxa[x*2];
+                double sx0 = sxa[x*2];
                 if (sx0 < 0) continue;
                 int sx0i = int(sx0 + epsilon);
                 if (sx0i >= sw) break;
 
-                float a, b, value;
+                double a = sourceLine[sx0i];
+                double b = a;
+                double value;
 
-                float sx1 = sxa[x*2+1];
+                double sx1 = sxa[x*2+1];
                 if (sx1 > sx0 + 1.f) {
                     int sx1i = int(sx1);
                     bool have = false;
                     for (int sx = sx0i; sx <= sx1i; ++sx) {
                         if (sx < 0 || sx >= sw) continue;
                         if (!have) {
-                            a = float(sourceLine[sx]);
-                            b = float(nextSource[sx]);
+                            a = sourceLine[sx];
+                            b = nextSource[sx];
                             have = true;
                         } else {
-                            a = std::max(a, float(sourceLine[sx]));
-                            b = std::max(b, float(nextSource[sx]));
+                            a = std::max(a, double(sourceLine[sx]));
+                            b = std::max(b, double(nextSource[sx]));
                         }
                     }
-                    float yprop = sy - syi;
+                    double yprop = sy - syi;
                     value = (a * (1.f - yprop) + b * yprop);
                 } else {
-                    a = float(sourceLine[sx0i]);
-                    b = float(nextSource[sx0i]);
-                    float yprop = sy - syi;
+                    a = sourceLine[sx0i];
+                    b = nextSource[sx0i];
+                    double yprop = sy - syi;
                     value = (a * (1.f - yprop) + b * yprop);
                     int oi = sx0i + 1;
-                    float xprop = sx0 - sx0i;
+                    double xprop = sx0 - sx0i;
                     xprop -= 0.5;
                     if (xprop < 0) {
                         oi = sx0i - 1;
                         xprop = -xprop;
                     }
                     if (oi < 0 || oi >= sw) oi = sx0i;
-                    a = float(sourceLine[oi]);
-                    b = float(nextSource[oi]);
+                    a = sourceLine[oi];
+                    b = nextSource[oi];
                     value = (value * (1.f - xprop) +
                              (a * (1.f - yprop) + b * yprop) * xprop);
                 }
                 
-                int vi = lrintf(value);
+                int vi = int(lrint(value));
                 if (vi > 255) vi = 255;
                 if (vi < 0) vi = 0;
                 targetLine[x] = uchar(vi);
@@ -1468,22 +1604,28 @@
         }
     } else {
 
+        double sy0 = getBinForY(v, 0);
+
+        int psy0i = -1, psy1i = -1;
+
         for (int y = 0; y < h; ++y) {
 
-            float sy0, sy1;
-
-            sy0 = getBinForY(v, y + 1);
-            sy1 = getBinForY(v, y);
+            double sy1 = sy0;
+            sy0 = getBinForY(v, double(y + 1));
 
             int sy0i = int(sy0 + epsilon);
             int sy1i = int(sy1);
 
             uchar *targetLine = img.scanLine(y);
 
-            if (sy0i == sy1i && sy0i == psy1i) { // same source scan line as just computed
+            if (sy0i == psy0i && sy1i == psy1i) {
+                // same source scan line as just computed
                 goto copy;
             }
 
+            psy0i = sy0i;
+            psy1i = sy1i;
+
             for (int x = 0; x < w; ++x) {
                 peaks[x] = 0;
             }
@@ -1496,11 +1638,11 @@
             
                 for (int x = 0; x < w; ++x) {
 
-                    float sx1 = sxa[x*2 + 1];
+                    double sx1 = sxa[x*2 + 1];
                     if (sx1 < 0) continue;
                     int sx1i = int(sx1);
 
-                    float sx0 = sxa[x*2];
+                    double sx0 = sxa[x*2];
                     if (sx0 < 0) continue;
                     int sx0i = int(sx0 + epsilon);
                     if (sx0i >= sw) break;
@@ -1527,8 +1669,8 @@
 }
 
 bool
-Colour3DPlotLayer::snapToFeatureFrame(View *v, int &frame,
-				      size_t &resolution,
+Colour3DPlotLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				      int &resolution,
 				      SnapType snap) const
 {
     if (!m_model) {
@@ -1536,8 +1678,8 @@
     }
 
     resolution = m_model->getResolution();
-    int left = (frame / resolution) * resolution;
-    int right = left + resolution;
+    sv_frame_t left = (frame / resolution) * resolution;
+    sv_frame_t right = left + resolution;
 
     switch (snap) {
     case SnapLeft:  frame = left;  break;
--- a/layer/Colour3DPlotLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/Colour3DPlotLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -49,20 +49,20 @@
         return m_model ? m_model->getZoomConstraint() : 0;
     }
     virtual const Model *getModel() const { return m_model; }
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame, 
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, 
+				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourHasMeaningfulValue;
@@ -70,7 +70,7 @@
 
     void setModel(const DenseThreeDimensionalModel *model);
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual PropertyList getProperties() const;
     virtual PropertyType getPropertyType(const PropertyName &) const;
@@ -116,12 +116,26 @@
     void setBinScale(BinScale);
     BinScale getBinScale() const;
 
+    /**
+     * Normalize each column to its maximum value, independent of its
+     * neighbours.
+     */
     void setNormalizeColumns(bool n);
     bool getNormalizeColumns() const;
 
+    /**
+     * Normalize each value against the maximum in the visible region.
+     */
     void setNormalizeVisibleArea(bool n);
     bool getNormalizeVisibleArea() const;
 
+    /**
+     * Normalize each column to its maximum value, and then scale by
+     * the log of the (absolute) maximum value.
+     */
+    void setNormalizeHybrid(bool n);
+    bool getNormalizeHybrid() const;
+
     void setInvertVertical(bool i);
     bool getInvertVertical() const;
 
@@ -131,11 +145,14 @@
     void setSmooth(bool i);
     bool getSmooth() const;
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
-    virtual bool getDisplayExtents(float &min, float &max) const;
-    virtual bool setDisplayExtents(float min, float max);
+    virtual bool getDisplayExtents(double &min, double &max) const;
+    virtual bool setDisplayExtents(double min, double max);
+
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int /* y */,
+                                double &/* value */, QString &/* unit */) const;
 
     virtual int getVerticalZoomSteps(int &defaultStep) const;
     virtual int getCurrentVerticalZoomStep() const;
@@ -149,17 +166,17 @@
 
 protected slots:
     void cacheInvalid();
-    void cacheInvalid(size_t startFrame, size_t endFrame);
+    void cacheInvalid(sv_frame_t startFrame, sv_frame_t endFrame);
     void modelChanged();
-    void modelChanged(size_t, size_t);
+    void modelChangedWithin(sv_frame_t, sv_frame_t);
 
 protected:
     const DenseThreeDimensionalModel *m_model; // I do not own this
     
     mutable QImage *m_cache;
     mutable QImage *m_peaksCache;
-    mutable size_t m_cacheValidStart;
-    mutable size_t m_cacheValidEnd;
+    mutable int m_cacheValidStart;
+    mutable int m_cacheValidEnd;
 
     ColourScale m_colourScale;
     bool        m_colourScaleSet;
@@ -168,22 +185,55 @@
     BinScale    m_binScale;
     bool        m_normalizeColumns;
     bool        m_normalizeVisibleArea;
+    bool        m_normalizeHybrid;
     bool        m_invertVertical;
     bool        m_opaque;
     bool        m_smooth;
-    size_t      m_peakResolution;
+    int         m_peakResolution;
 
+    // Minimum and maximum bin numbers visible within the view. We
+    // always snap to whole bins at view edges.
     int         m_miny;
     int         m_maxy;
+
+    /**
+     * Return the y coordinate at which the given bin "starts"
+     * (i.e. at the bottom of the bin, if the given bin is an integer
+     * and the vertical scale is the usual way up). Bin number may be
+     * fractional, to obtain a position part-way through a bin.
+     */
+    double getYForBin(LayerGeometryProvider *, double bin) const;
+
+    /**
+     * As getYForBin, but rounding to integer values.
+     */
+    int getIYForBin(LayerGeometryProvider *, int bin) const;
     
-    DenseThreeDimensionalModel::Column getColumn(size_t col) const;
+    /**
+     * Return the bin number, possibly fractional, at the given y
+     * coordinate. Note that the whole numbers occur at the positions
+     * at which the bins "start" (i.e. the bottom of the visible bin,
+     * if the vertical scale is the usual way up).
+     */
+    double getBinForY(LayerGeometryProvider *, double y) const;
+
+    /**
+     * As getBinForY, but rounding to integer values.
+     */
+    int getIBinForY(LayerGeometryProvider *, int y) const;
+    
+    DenseThreeDimensionalModel::Column getColumn(int col) const;
+
+    /**
+     * True if we have the opaque or smooth flag set, or if the cells
+     * are so small you can't see their borders. False for big,
+     * translucent cells.
+     */
+    bool shouldPaintDenseIn(const LayerGeometryProvider *) const; 
 
     int getColourScaleWidth(QPainter &) const;
-    void fillCache(size_t firstBin, size_t lastBin) const;
-    void paintDense(View *v, QPainter &paint, QRect rect) const;
-
-    float getYForBin(View *, float bin) const;
-    float getBinForY(View *, float y) const;
+    void fillCache(int firstBin, int lastBin) const;
+    void paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 };
 
 #endif
--- a/layer/ColourDatabase.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/ColourDatabase.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -34,29 +34,28 @@
 int
 ColourDatabase::getColourCount() const
 {
-    return m_colours.size();
+    return int(m_colours.size());
 }
 
 QString
 ColourDatabase::getColourName(int c) const
 {
-    if (c < 0 || size_t(c) >= m_colours.size()) return "";
+    if (!in_range_for(m_colours, c)) return "";
     return m_colours[c].name;
 }
 
 QColor
 ColourDatabase::getColour(int c) const
 {
-    if (c < 0 || size_t(c) >= m_colours.size()) return Qt::black;
+    if (!in_range_for(m_colours, c)) return Qt::black;
     return m_colours[c].colour;
 }
 
 QColor
 ColourDatabase::getColour(QString name) const
 {
-    for (ColourList::const_iterator i = m_colours.begin();
-         i != m_colours.end(); ++i) {
-        if (i->name == name) return i->colour;
+    for (auto &c: m_colours) {
+        if (c.name == name) return c.colour;
     }
 
     return Qt::black;
@@ -66,9 +65,8 @@
 ColourDatabase::getColourIndex(QString name) const
 {
     int index = 0;
-    for (ColourList::const_iterator i = m_colours.begin();
-         i != m_colours.end(); ++i) {
-        if (i->name == name) return index;
+    for (auto &c: m_colours) {
+        if (c.name == name) return index;
         ++index;
     }
 
@@ -76,12 +74,11 @@
 }
 
 int
-ColourDatabase::getColourIndex(QColor c) const
+ColourDatabase::getColourIndex(QColor col) const
 {
     int index = 0;
-    for (ColourList::const_iterator i = m_colours.begin();
-         i != m_colours.end(); ++i) {
-        if (i->colour == c) return index;
+    for (auto &c: m_colours) {
+        if (c.colour == col) return index;
         ++index;
     }
 
@@ -91,14 +88,14 @@
 bool
 ColourDatabase::useDarkBackground(int c) const
 {
-    if (c < 0 || size_t(c) >= m_colours.size()) return false;
+    if (!in_range_for(m_colours, c)) return false;
     return m_colours[c].darkbg;
 }
 
 void
 ColourDatabase::setUseDarkBackground(int c, bool dark)
 {
-    if (c < 0 || size_t(c) >= m_colours.size()) return;
+    if (!in_range_for(m_colours, c)) return;
     if (m_colours[c].darkbg != dark) {
         m_colours[c].darkbg = dark;
         emit colourDatabaseChanged();
@@ -109,6 +106,7 @@
 ColourDatabase::addColour(QColor c, QString name)
 {
     int index = 0;
+
     for (ColourList::iterator i = m_colours.begin();
          i != m_colours.end(); ++i) {
         if (i->name == name) {
@@ -147,7 +145,7 @@
 {
     colourName = "";
     colourSpec = "";
-    if (index < 0 || size_t(index) >= m_colours.size()) return;
+    if (!in_range_for(m_colours, index)) return;
 
     colourName = getColourName(index);
     QColor c = getColour(index);
--- a/layer/ColourMapper.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/ColourMapper.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,7 +21,45 @@
 
 #include "base/Debug.h"
 
-ColourMapper::ColourMapper(int map, float min, float max) :
+#include <vector>
+
+using namespace std;
+
+static vector<QColor> convertStrings(const vector<QString> &strs)
+{
+    vector<QColor> converted;
+    for (const auto &s: strs) converted.push_back(QColor(s));
+    reverse(converted.begin(), converted.end());
+    return converted;
+}
+
+static vector<QColor> ice = convertStrings({
+        // Based on ColorBrewer ylGnBu
+        "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5",
+        "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040"
+        });
+
+static vector<QColor> cherry = convertStrings({
+        "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497",
+        "#ae017e", "#7a0177", "#49006a"
+        });
+    
+static void
+mapDiscrete(double norm, vector<QColor> &colours, double &r, double &g, double &b)
+{
+    int n = int(colours.size());
+    double m = norm * (n-1);
+    if (m >= n-1) { colours[n-1].getRgbF(&r, &g, &b, 0); return; }
+    if (m <= 0) { colours[0].getRgbF(&r, &g, &b, 0); return; }
+    int base(int(floor(m)));
+    double prop0 = (base + 1.0) - m, prop1 = m - base;
+    QColor c0(colours[base]), c1(colours[base+1]);
+    r = c0.redF() * prop0 + c1.redF() * prop1;
+    g = c0.greenF() * prop0 + c1.greenF() * prop1;
+    b = c0.blueF() * prop0 + c1.blueF() * prop1;
+}
+
+ColourMapper::ColourMapper(int map, double min, double max) :
     QObject(),
     m_map(map),
     m_min(min),
@@ -51,12 +89,12 @@
     StandardMap map = (StandardMap)n;
 
     switch (map) {
-    case DefaultColours:   return tr("Default");
+    case Green:            return tr("Green");
     case WhiteOnBlack:     return tr("White on Black");
     case BlackOnWhite:     return tr("Black on White");
-    case RedOnBlue:        return tr("Red on Blue");
-    case YellowOnBlack:    return tr("Yellow on Black");
-    case BlueOnBlack:      return tr("Blue on Black");
+    case Cherry:           return tr("Cherry");
+    case Wasp:             return tr("Wasp");
+    case Ice:              return tr("Ice");
     case Sunset:           return tr("Sunset");
     case FruitSalad:       return tr("Fruit Salad");
     case Banded:           return tr("Banded");
@@ -69,26 +107,25 @@
 }
 
 QColor
-ColourMapper::map(float value) const
+ColourMapper::map(double value) const
 {
-    float norm = (value - m_min) / (m_max - m_min);
-    if (norm < 0.f) norm = 0.f;
-    if (norm > 1.f) norm = 1.f;
+    double norm = (value - m_min) / (m_max - m_min);
+    if (norm < 0.0) norm = 0.0;
+    if (norm > 1.0) norm = 1.0;
     
-    float h = 0.f, s = 0.f, v = 0.f, r = 0.f, g = 0.f, b = 0.f;
+    double h = 0.0, s = 0.0, v = 0.0, r = 0.0, g = 0.0, b = 0.0;
     bool hsv = true;
 
-//    float red = 0.f, green = 0.3333f;
-    float blue = 0.6666f, pieslice = 0.3333f;
+    double blue = 0.6666, pieslice = 0.3333;
 
     if (m_map >= getColourMapCount()) return Qt::black;
     StandardMap map = (StandardMap)m_map;
 
     switch (map) {
 
-    case DefaultColours:
-        h = blue - norm * 2.f * pieslice;
-        s = 0.5f + norm/2.f;
+    case Green:
+        h = blue - norm * 2.0 * pieslice;
+        s = 0.5f + norm/2.0;
         v = norm;
         break;
 
@@ -98,53 +135,40 @@
         break;
 
     case BlackOnWhite:
-        r = g = b = 1.f - norm;
+        r = g = b = 1.0 - norm;
         hsv = false;
         break;
 
-    case RedOnBlue:
-        h = blue - pieslice/4.f + norm * (pieslice + pieslice/4.f);
-        s = 1.f;
+    case Cherry:
+        hsv = false;
+        mapDiscrete(norm, cherry, r, g, b);
+        break;
+
+    case Wasp:
+        h = 0.15;
+        s = 1.0;
         v = norm;
         break;
 
-    case YellowOnBlack:
-        h = 0.15f;
-        s = 1.f;
-        v = norm;
-        break;
-
-    case BlueOnBlack:
-        h = blue;
-        s = 1.f;
-        v = norm * 2.f;
-        if (v > 1.f) {
-            v = 1.f;
-            s = 1.f - (sqrtf(norm) - 0.707f) * 3.413f;
-            if (s < 0.f) s = 0.f;
-            if (s > 1.f) s = 1.f;
-        }
-        break;
-
     case Sunset:
-        r = (norm - 0.24f) * 2.38f;
-        if (r > 1.f) r = 1.f;
-        if (r < 0.f) r = 0.f;
-        g = (norm - 0.64f) * 2.777f;
-        if (g > 1.f) g = 1.f;
-        if (g < 0.f) g = 0.f;
+        r = (norm - 0.24) * 2.38;
+        if (r > 1.0) r = 1.0;
+        if (r < 0.0) r = 0.0;
+        g = (norm - 0.64) * 2.777;
+        if (g > 1.0) g = 1.0;
+        if (g < 0.0) g = 0.0;
         b = (3.6f * norm);
-        if (norm > 0.277f) b = 2.f - b;
-        if (b > 1.f) b = 1.f;
-        if (b < 0.f) b = 0.f;
+        if (norm > 0.277) b = 2.0 - b;
+        if (b > 1.0) b = 1.0;
+        if (b < 0.0) b = 0.0;
         hsv = false;
         break;
 
     case FruitSalad:
-        h = blue + (pieslice/6.f) - norm;
-        if (h < 0.f) h += 1.f;
-        s = 1.f;
-        v = 1.f;
+        h = blue + (pieslice/6.0) - norm;
+        if (h < 0.0) h += 1.0;
+        s = 1.0;
+        v = 1.0;
         break;
 
     case Banded:
@@ -164,50 +188,54 @@
 
     case Printer:
         if (norm > 0.8) {
-            r = 1.f;
+            r = 1.0;
         } else if (norm > 0.7) {
-            r = 0.9f;
+            r = 0.9;
         } else if (norm > 0.6) {
-            r = 0.8f;
+            r = 0.8;
         } else if (norm > 0.5) {
-            r = 0.7f;
+            r = 0.7;
         } else if (norm > 0.4) {
-            r = 0.6f;
+            r = 0.6;
         } else if (norm > 0.3) {
-            r = 0.5f;
+            r = 0.5;
         } else if (norm > 0.2) {
-            r = 0.4f;
+            r = 0.4;
         } else {
-            r = 0.f;
+            r = 0.0;
         }
-        r = g = b = 1.f - r;
+        r = g = b = 1.0 - r;
         hsv = false;
         break;
 
     case HighGain:
-        if (norm <= 1.f / 256.f) {
-            norm = 0.f;
+        if (norm <= 1.0 / 256.0) {
+            norm = 0.0;
         } else {
-            norm = 0.1f + (powf(((norm - 0.5f) * 2.f), 3.f) + 1.f) / 2.081f;
+            norm = 0.1f + (pow(((norm - 0.5) * 2.0), 3.0) + 1.0) / 2.081;
         }
         // now as for Sunset
-        r = (norm - 0.24f) * 2.38f;
-        if (r > 1.f) r = 1.f;
-        if (r < 0.f) r = 0.f;
-        g = (norm - 0.64f) * 2.777f;
-        if (g > 1.f) g = 1.f;
-        if (g < 0.f) g = 0.f;
+        r = (norm - 0.24) * 2.38;
+        if (r > 1.0) r = 1.0;
+        if (r < 0.0) r = 0.0;
+        g = (norm - 0.64) * 2.777;
+        if (g > 1.0) g = 1.0;
+        if (g < 0.0) g = 0.0;
         b = (3.6f * norm);
-        if (norm > 0.277f) b = 2.f - b;
-        if (b > 1.f) b = 1.f;
-        if (b < 0.f) b = 0.f;
+        if (norm > 0.277) b = 2.0 - b;
+        if (b > 1.0) b = 1.0;
+        if (b < 0.0) b = 0.0;
         hsv = false;
 /*
-        if (r > 1.f) r = 1.f;
-        r = g = b = 1.f - r;
+        if (r > 1.0) r = 1.0;
+        r = g = b = 1.0 - r;
         hsv = false;
 */
         break;
+
+    case Ice:
+        hsv = false;
+        mapDiscrete(norm, ice, r, g, b);
     }
 
     if (hsv) {
@@ -225,7 +253,7 @@
 
     switch (map) {
 
-    case DefaultColours:
+    case Green:
         return QColor(255, 150, 50);
 
     case WhiteOnBlack:
@@ -234,13 +262,13 @@
     case BlackOnWhite:
         return Qt::darkGreen;
 
-    case RedOnBlue:
+    case Cherry:
         return Qt::green;
 
-    case YellowOnBlack:
+    case Wasp:
         return QColor::fromHsv(240, 255, 255);
 
-    case BlueOnBlack:
+    case Ice:
         return Qt::red;
 
     case Sunset:
@@ -278,6 +306,16 @@
     case HighGain:
         return true;
 
+    case Green:
+    case Sunset:
+    case WhiteOnBlack:
+    case Cherry:
+    case Wasp:
+    case Ice:
+    case FruitSalad:
+    case Banded:
+    case Highlight:
+        
     default:
         return false;
     }
--- a/layer/ColourMapper.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/ColourMapper.h	Wed Apr 20 12:06:28 2016 +0100
@@ -29,17 +29,17 @@
     Q_OBJECT
 
 public:
-    ColourMapper(int map, float minValue, float maxValue);
+    ColourMapper(int map, double minValue, double maxValue);
     virtual ~ColourMapper();
 
     enum StandardMap {
-        DefaultColours,
+        Green,
         Sunset,
         WhiteOnBlack,
         BlackOnWhite,
-        RedOnBlue,
-        YellowOnBlack,
-        BlueOnBlack,
+        Cherry,
+        Wasp,
+        Ice,
         FruitSalad,
         Banded,
         Highlight,
@@ -48,21 +48,21 @@
     };
 
     int getMap() const { return m_map; }
-    float getMinValue() const { return m_min; }
-    float getMaxValue() const { return m_max; }
+    double getMinValue() const { return m_min; }
+    double getMaxValue() const { return m_max; }
 
     static int getColourMapCount();
     static QString getColourMapName(int n);
 
-    QColor map(float value) const;
+    QColor map(double value) const;
 
     QColor getContrastingColour() const; // for cursors etc
     bool hasLightBackground() const;
 
 protected:
     int m_map;
-    float m_min;
-    float m_max;
+    double m_min;
+    double m_max;
 };
 
 #endif
--- a/layer/ColourScaleLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/ColourScaleLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,11 +19,13 @@
 #include <QString>
 #include <QColor>
 
+class LayerGeometryProvider;
+
 class ColourScaleLayer
 {
 public:
     virtual QString getScaleUnits() const = 0;
-    virtual QColor getColourForValue(View *v, float value) const = 0;
+    virtual QColor getColourForValue(LayerGeometryProvider *v, double value) const = 0;
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/FlexiNoteLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,1919 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "FlexiNoteLayer.h"
+
+#include "data/model/Model.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "base/RealTime.h"
+#include "base/Profiler.h"
+#include "base/Pitch.h"
+#include "base/LogRange.h"
+#include "base/RangeMapper.h"
+#include "ColourDatabase.h"
+#include "view/View.h"
+
+#include "PianoScale.h"
+#include "LinearNumericalScale.h"
+#include "LogNumericalScale.h"
+
+#include "data/model/FlexiNoteModel.h"
+
+#include "widgets/ItemEditDialog.h"
+#include "widgets/TextAbbrev.h"
+
+#include <QPainter>
+#include <QPainterPath>
+#include <QMouseEvent>
+#include <QTextStream>
+#include <QMessageBox>
+
+#include <iostream>
+#include <cmath>
+#include <utility>
+#include <limits> // GF: included to compile std::numerical_limits on linux
+#include <vector>
+
+
+FlexiNoteLayer::FlexiNoteLayer() :
+    SingleColourLayer(),
+
+    // m_model(0),
+    // m_editing(false),
+    // m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    // m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    // m_editingCommand(0),
+    // m_verticalScale(AutoAlignScale),
+    // m_scaleMinimum(0),
+    // m_scaleMaximum(0)
+
+    m_model(0),
+    m_editing(false),
+    m_intelligentActions(true),
+    m_dragPointX(0),
+    m_dragPointY(0),
+    m_dragStartX(0),
+    m_dragStartY(0),
+    m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    m_greatestLeftNeighbourFrame(0),
+    m_smallestRightNeighbourFrame(0),
+    m_editingCommand(0),
+    m_verticalScale(AutoAlignScale),
+    m_editMode(DragNote),
+    m_scaleMinimum(34), 
+    m_scaleMaximum(77)
+{
+}
+
+void
+FlexiNoteLayer::setModel(FlexiNoteModel *model) 
+{
+    if (m_model == model) return;
+    m_model = model;
+
+    connectSignals(m_model);
+
+    // m_scaleMinimum = 0;
+    // m_scaleMaximum = 0;
+
+    emit modelReplaced();
+}
+
+Layer::PropertyList
+FlexiNoteLayer::getProperties() const
+{
+    PropertyList list = SingleColourLayer::getProperties();
+    list.push_back("Vertical Scale");
+    list.push_back("Scale Units");
+    return list;
+}
+
+QString
+FlexiNoteLayer::getPropertyLabel(const PropertyName &name) const
+{
+    if (name == "Vertical Scale") return tr("Vertical Scale");
+    if (name == "Scale Units") return tr("Scale Units");
+    return SingleColourLayer::getPropertyLabel(name);
+}
+
+Layer::PropertyType
+FlexiNoteLayer::getPropertyType(const PropertyName &name) const
+{
+    if (name == "Scale Units") return UnitsProperty;
+    if (name == "Vertical Scale") return ValueProperty;
+    return SingleColourLayer::getPropertyType(name);
+}
+
+QString
+FlexiNoteLayer::getPropertyGroupName(const PropertyName &name) const
+{
+    if (name == "Vertical Scale" || name == "Scale Units") {
+        return tr("Scale");
+    }
+    return SingleColourLayer::getPropertyGroupName(name);
+}
+
+QString
+FlexiNoteLayer::getScaleUnits() const
+{
+    if (m_model) return m_model->getScaleUnits();
+    else return "";
+}
+
+int
+FlexiNoteLayer::getPropertyRangeAndValue(const PropertyName &name,
+                                         int *min, int *max, int *deflt) const
+{
+    int val = 0;
+
+    if (name == "Vertical Scale") {
+    
+        if (min) *min = 0;
+        if (max) *max = 3;
+        if (deflt) *deflt = int(AutoAlignScale);
+    
+        val = int(m_verticalScale);
+
+    } else if (name == "Scale Units") {
+
+        if (deflt) *deflt = 0;
+        if (m_model) {
+            val = UnitDatabase::getInstance()->getUnitId
+                (getScaleUnits());
+        }
+
+    } else {
+
+        val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
+    }
+
+    return val;
+}
+
+QString
+FlexiNoteLayer::getPropertyValueLabel(const PropertyName &name,
+                                      int value) const
+{
+    if (name == "Vertical Scale") {
+        switch (value) {
+        default:
+        case 0: return tr("Auto-Align");
+        case 1: return tr("Linear");
+        case 2: return tr("Log");
+        case 3: return tr("MIDI Notes");
+        }
+    }
+    return SingleColourLayer::getPropertyValueLabel(name, value);
+}
+
+void
+FlexiNoteLayer::setProperty(const PropertyName &name, int value)
+{
+    if (name == "Vertical Scale") {
+        setVerticalScale(VerticalScale(value));
+    } else if (name == "Scale Units") {
+        if (m_model) {
+            m_model->setScaleUnits
+                (UnitDatabase::getInstance()->getUnitById(value));
+            emit modelChanged();
+        }
+    } else {
+        return SingleColourLayer::setProperty(name, value);
+    }
+}
+
+void
+FlexiNoteLayer::setVerticalScale(VerticalScale scale)
+{
+    if (m_verticalScale == scale) return;
+    m_verticalScale = scale;
+    emit layerParametersChanged();
+}
+
+bool
+FlexiNoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const
+{
+    QPoint discard;
+    return !v->shouldIlluminateLocalFeatures(this, discard);
+}
+
+bool
+FlexiNoteLayer::shouldConvertMIDIToHz() const
+{
+    QString unit = getScaleUnits();
+    return (unit != "Hz");
+//    if (unit == "" ||
+//        unit.startsWith("MIDI") ||
+//        unit.startsWith("midi")) return true;
+//    return false;
+}
+
+bool
+FlexiNoteLayer::getValueExtents(double &min, double &max,
+                                bool &logarithmic, QString &unit) const
+{
+    if (!m_model) return false;
+    min = m_model->getValueMinimum();
+    max = m_model->getValueMaximum();
+
+    if (shouldConvertMIDIToHz()) {
+        unit = "Hz";
+        min = Pitch::getFrequencyForPitch(int(lrint(min)));
+        max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
+    } else unit = getScaleUnits();
+
+    if (m_verticalScale == MIDIRangeScale ||
+        m_verticalScale == LogScale) logarithmic = true;
+
+    return true;
+}
+
+bool
+FlexiNoteLayer::getDisplayExtents(double &min, double &max) const
+{
+    if (!m_model || shouldAutoAlign()) {
+//        std::cerr << "No model or shouldAutoAlign()" << std::endl;
+        return false;
+    }
+
+    if (m_verticalScale == MIDIRangeScale) {
+        min = Pitch::getFrequencyForPitch(0);
+        max = Pitch::getFrequencyForPitch(127);
+        return true;
+    }
+
+    if (m_scaleMinimum == m_scaleMaximum) {
+        min = m_model->getValueMinimum();
+        max = m_model->getValueMaximum();
+    } else {
+        min = m_scaleMinimum;
+        max = m_scaleMaximum;
+    }
+
+    if (shouldConvertMIDIToHz()) {
+        min = Pitch::getFrequencyForPitch(int(lrint(min)));
+        max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
+    }
+
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl;
+#endif
+
+    return true;
+}
+
+bool
+FlexiNoteLayer::setDisplayExtents(double min, double max)
+{
+    if (!m_model) return false;
+
+    if (min == max) {
+        if (min == 0.f) {
+            max = 1.f;
+        } else {
+            max = min * 1.0001f;
+        }
+    }
+
+    m_scaleMinimum = min;
+    m_scaleMaximum = max;
+
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "FlexiNoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl;
+#endif
+    
+    emit layerParametersChanged();
+    return true;
+}
+
+int
+FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const
+{
+    if (shouldAutoAlign()) return 0;
+    if (!m_model) return 0;
+
+    defaultStep = 0;
+    return 100;
+}
+
+int
+FlexiNoteLayer::getCurrentVerticalZoomStep() const
+{
+    if (shouldAutoAlign()) return 0;
+    if (!m_model) return 0;
+
+    RangeMapper *mapper = getNewVerticalZoomRangeMapper();
+    if (!mapper) return 0;
+
+    double dmin, dmax;
+    getDisplayExtents(dmin, dmax);
+
+    int nr = mapper->getPositionForValue(dmax - dmin);
+
+    delete mapper;
+
+    return 100 - nr;
+}
+
+//!!! lots of duplication with TimeValueLayer
+
+void
+FlexiNoteLayer::setVerticalZoomStep(int step)
+{
+    if (shouldAutoAlign()) return;
+    if (!m_model) return;
+
+    RangeMapper *mapper = getNewVerticalZoomRangeMapper();
+    if (!mapper) return;
+    
+    double min, max;
+    bool logarithmic;
+    QString unit;
+    getValueExtents(min, max, logarithmic, unit);
+    
+    double dmin, dmax;
+    getDisplayExtents(dmin, dmax);
+
+    double newdist = mapper->getValueForPosition(100 - step);
+
+    double newmin, newmax;
+
+    if (logarithmic) {
+
+        // see SpectrogramLayer::setVerticalZoomStep
+
+        newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
+        newmin = newmax - newdist;
+
+//        cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
+
+    } else {
+        double dmid = (dmax + dmin) / 2;
+        newmin = dmid - newdist / 2;
+        newmax = dmid + newdist / 2;
+    }
+
+    if (newmin < min) {
+        newmax += (min - newmin);
+        newmin = min;
+    }
+    if (newmax > max) {
+        newmax = max;
+    }
+    
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "FlexiNoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
+#endif
+
+    setDisplayExtents(newmin, newmax);
+}
+
+RangeMapper *
+FlexiNoteLayer::getNewVerticalZoomRangeMapper() const
+{
+    if (!m_model) return 0;
+    
+    RangeMapper *mapper;
+
+    double min, max;
+    bool logarithmic;
+    QString unit;
+    getValueExtents(min, max, logarithmic, unit);
+
+    if (min == max) return 0;
+    
+    if (logarithmic) {
+        mapper = new LogRangeMapper(0, 100, min, max, unit);
+    } else {
+        mapper = new LinearRangeMapper(0, 100, min, max, unit);
+    }
+
+    return mapper;
+}
+
+FlexiNoteModel::PointList
+FlexiNoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
+{
+    if (!m_model) return FlexiNoteModel::PointList();
+
+    sv_frame_t frame = v->getFrameForX(x);
+
+    FlexiNoteModel::PointList onPoints =
+        m_model->getPoints(frame);
+
+    if (!onPoints.empty()) {
+        return onPoints;
+    }
+
+    FlexiNoteModel::PointList prevPoints =
+        m_model->getPreviousPoints(frame);
+    FlexiNoteModel::PointList nextPoints =
+        m_model->getNextPoints(frame);
+
+    FlexiNoteModel::PointList usePoints = prevPoints;
+
+    if (prevPoints.empty()) {
+        usePoints = nextPoints;
+    } else if (prevPoints.begin()->frame < v->getStartFrame() &&
+               !(nextPoints.begin()->frame > v->getEndFrame())) {
+        usePoints = nextPoints;
+    } else if (nextPoints.begin()->frame - frame <
+               frame - prevPoints.begin()->frame) {
+        usePoints = nextPoints;
+    }
+
+    if (!usePoints.empty()) {
+        int fuzz = 2;
+        int px = v->getXForFrame(usePoints.begin()->frame);
+        if ((px > x && px - x > fuzz) ||
+            (px < x && x - px > fuzz + 1)) {
+            usePoints.clear();
+        }
+    }
+
+    return usePoints;
+}
+
+bool
+FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &p) const
+{
+    if (!m_model) return false;
+
+    sv_frame_t frame = v->getFrameForX(x);
+
+    FlexiNoteModel::PointList onPoints = m_model->getPoints(frame);
+    if (onPoints.empty()) return false;
+
+//    cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << endl;
+
+    int nearestDistance = -1;
+
+    for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin();
+         i != onPoints.end(); ++i) {
+        
+        int distance = getYForValue(v, (*i).value) - y;
+        if (distance < 0) distance = -distance;
+        if (nearestDistance == -1 || distance < nearestDistance) {
+            nearestDistance = distance;
+            p = *i;
+        }
+    }
+
+    return true;
+}
+
+bool
+FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &p) const
+{
+    // GF: find the note that is closest to the cursor
+    if (!m_model) return false;
+
+    sv_frame_t frame = v->getFrameForX(x);
+
+    FlexiNoteModel::PointList onPoints = m_model->getPoints(frame);
+    if (onPoints.empty()) return false;
+
+//    std::cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << std::endl;
+
+    int nearestDistance = -1;
+
+    for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin();
+         i != onPoints.end(); ++i) {
+        
+        int distance = getYForValue(v, (*i).value) - y;
+        if (distance < 0) distance = -distance;
+        if (nearestDistance == -1 || distance < nearestDistance) {
+            nearestDistance = distance;
+            p = *i;
+        }
+    }
+
+    return true;
+}
+
+QString
+FlexiNoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
+{
+    int x = pos.x();
+
+    if (!m_model || !m_model->getSampleRate()) return "";
+
+    FlexiNoteModel::PointList points = getLocalPoints(v, x);
+
+    if (points.empty()) {
+        if (!m_model->isReady()) {
+            return tr("In progress");
+        } else {
+            return tr("No local points");
+        }
+    }
+
+    FlexiNote note(0);
+    FlexiNoteModel::PointList::iterator i;
+
+    for (i = points.begin(); i != points.end(); ++i) {
+
+        int y = getYForValue(v, i->value);
+        int h = NOTE_HEIGHT; // GF: larger notes
+
+        if (m_model->getValueQuantization() != 0.0) {
+            h = y - getYForValue(v, i->value + m_model->getValueQuantization());
+            if (h < NOTE_HEIGHT) h = NOTE_HEIGHT;
+        }
+
+        // GF: this is not quite correct
+        if (pos.y() >= y - 4 && pos.y() <= y + h) {
+            note = *i;
+            break;
+        }
+    }
+
+    if (i == points.end()) return tr("No local points");
+
+    RealTime rt = RealTime::frame2RealTime(note.frame,
+                                           m_model->getSampleRate());
+    RealTime rd = RealTime::frame2RealTime(note.duration,
+                                           m_model->getSampleRate());
+    
+    QString pitchText;
+
+    if (shouldConvertMIDIToHz()) {
+
+        int mnote = int(lrint(note.value));
+        int cents = int(lrint((note.value - double(mnote)) * 100));
+        double freq = Pitch::getFrequencyForPitch(mnote, cents);
+        pitchText = tr("%1 (%2, %3 Hz)")
+            .arg(Pitch::getPitchLabel(mnote, cents))
+            .arg(mnote)
+            .arg(freq);
+
+    } else if (getScaleUnits() == "Hz") {
+
+        pitchText = tr("%1 Hz (%2, %3)")
+            .arg(note.value)
+            .arg(Pitch::getPitchLabelForFrequency(note.value))
+            .arg(Pitch::getPitchForFrequency(note.value));
+
+    } else {
+        pitchText = tr("%1 %2")
+            .arg(note.value).arg(getScaleUnits());
+    }
+
+    QString text;
+
+    if (note.label == "") {
+        text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
+            .arg(rt.toText(true).c_str())
+            .arg(pitchText)
+            .arg(rd.toText(true).c_str());
+    } else {
+        text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
+            .arg(rt.toText(true).c_str())
+            .arg(pitchText)
+            .arg(rd.toText(true).c_str())
+            .arg(note.label);
+    }
+
+    pos = QPoint(v->getXForFrame(note.frame),
+                 getYForValue(v, note.value));
+    return text;
+}
+
+bool
+FlexiNoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+                                   int &resolution,
+                                   SnapType snap) const
+{
+    if (!m_model) {
+        return Layer::snapToFeatureFrame(v, frame, resolution, snap);
+    }
+
+    resolution = m_model->getResolution();
+    FlexiNoteModel::PointList points;
+
+    if (snap == SnapNeighbouring) {
+    
+        points = getLocalPoints(v, v->getXForFrame(frame));
+        if (points.empty()) return false;
+        frame = points.begin()->frame;
+        return true;
+    }    
+
+    points = m_model->getPoints(frame, frame);
+    sv_frame_t snapped = frame;
+    bool found = false;
+
+    for (FlexiNoteModel::PointList::const_iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        cerr << "FlexiNoteModel: point at " << i->frame << endl;
+
+        if (snap == SnapRight) {
+
+            if (i->frame > frame) {
+                snapped = i->frame;
+                found = true;
+                break;
+            } else if (i->frame + i->duration >= frame) {
+                snapped = i->frame + i->duration;
+                found = true;
+                break;
+            }
+
+        } else if (snap == SnapLeft) {
+
+            if (i->frame <= frame) {
+                snapped = i->frame;
+                found = true; // don't break, as the next may be better
+            } else {
+                break;
+            }
+
+        } else { // nearest
+
+            FlexiNoteModel::PointList::const_iterator j = i;
+            ++j;
+
+            if (j == points.end()) {
+
+                snapped = i->frame;
+                found = true;
+                break;
+
+            } else if (j->frame >= frame) {
+
+                if (j->frame - frame < frame - i->frame) {
+                    snapped = j->frame;
+                } else {
+                    snapped = i->frame;
+                }
+                found = true;
+                break;
+            }
+        }
+    }
+
+    cerr << "snapToFeatureFrame: frame " << frame << " -> snapped " << snapped << ", found = " << found << endl;
+
+    frame = snapped;
+    return found;
+}
+
+void
+FlexiNoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
+{
+    min = 0.0;
+    max = 0.0;
+    log = false;
+
+    QString queryUnits;
+    if (shouldConvertMIDIToHz()) queryUnits = "Hz";
+    else queryUnits = getScaleUnits();
+
+    if (shouldAutoAlign()) {
+
+        if (!v->getValueExtents(queryUnits, min, max, log)) {
+
+            min = m_model->getValueMinimum();
+            max = m_model->getValueMaximum();
+
+            if (shouldConvertMIDIToHz()) {
+                min = Pitch::getFrequencyForPitch(int(lrint(min)));
+                max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
+            }
+
+#ifdef DEBUG_NOTE_LAYER
+            cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
+#endif
+
+        } else if (log) {
+
+            LogRange::mapRange(min, max);
+
+#ifdef DEBUG_NOTE_LAYER
+            cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
+#endif
+        }
+
+    } else {
+
+        getDisplayExtents(min, max);
+
+        if (m_verticalScale == MIDIRangeScale) {
+            min = Pitch::getFrequencyForPitch(0);
+            max = Pitch::getFrequencyForPitch(70);
+        } else if (shouldConvertMIDIToHz()) {
+            min = Pitch::getFrequencyForPitch(int(lrint(min)));
+            max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
+        }
+
+        if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
+            LogRange::mapRange(min, max);
+            log = true;
+        }
+    }
+
+    if (max == min) max = min + 1.0;
+}
+
+int
+FlexiNoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
+{
+    double min = 0.0, max = 0.0;
+    bool logarithmic = false;
+    int h = v->getPaintHeight();
+
+    getScaleExtents(v, min, max, logarithmic);
+
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "FlexiNoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
+#endif
+
+    if (shouldConvertMIDIToHz()) {
+        val = Pitch::getFrequencyForPitch(int(lrint(val)),
+                                          int(lrint((val - floor(val)) * 100.0)));
+#ifdef DEBUG_NOTE_LAYER
+        cerr << "shouldConvertMIDIToHz true, val now = " << val << endl;
+#endif
+    }
+
+    if (logarithmic) {
+        val = LogRange::map(val);
+#ifdef DEBUG_NOTE_LAYER
+        cerr << "logarithmic true, val now = " << val << endl;
+#endif
+    }
+
+    int y = int(h - ((val - min) * h) / (max - min)) - 1;
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "y = " << y << endl;
+#endif
+    return y;
+}
+
+double
+FlexiNoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
+{
+    double min = 0.0, max = 0.0;
+    bool logarithmic = false;
+    int h = v->getPaintHeight();
+
+    getScaleExtents(v, min, max, logarithmic);
+
+    double val = min + (double(h - y) * double(max - min)) / h;
+
+    if (logarithmic) {
+        val = pow(10.f, val);
+    }
+
+    if (shouldConvertMIDIToHz()) {
+        val = Pitch::getPitchForFrequency(val);
+    }
+
+    return val;
+}
+
+bool
+FlexiNoteLayer::shouldAutoAlign() const
+{
+    if (!m_model) return false;
+    return (m_verticalScale == AutoAlignScale);
+}
+
+void
+FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
+{
+    if (!m_model || !m_model->isOK()) return;
+
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    if (!sampleRate) return;
+
+//    Profiler profiler("FlexiNoteLayer::paint", true);
+
+    int x1 = rect.right();
+    sv_frame_t frame1 = v->getFrameForX(x1);
+
+    FlexiNoteModel::PointList points(m_model->getPoints(0, frame1));
+    if (points.empty()) return;
+
+    paint.setPen(getBaseQColor());
+
+    QColor brushColour(getBaseQColor());
+    brushColour.setAlpha(80);
+
+//    SVDEBUG << "FlexiNoteLayer::paint: resolution is "
+//        << m_model->getResolution() << " frames" << endl;
+
+    double min = m_model->getValueMinimum();
+    double max = m_model->getValueMaximum();
+    if (max == min) max = min + 1.0;
+
+    QPoint localPos;
+    FlexiNoteModel::Point illuminatePoint(0);
+    bool shouldIlluminate = false;
+
+    if (v->shouldIlluminateLocalFeatures(this, localPos)) {
+        shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
+                                          illuminatePoint);
+    }
+
+    paint.save();
+    paint.setRenderHint(QPainter::Antialiasing, false);
+    
+    int noteNumber = 0;
+
+    for (FlexiNoteModel::PointList::const_iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        ++noteNumber;
+        const FlexiNoteModel::Point &p(*i);
+
+        int x = v->getXForFrame(p.frame);
+        int y = getYForValue(v, p.value);
+        int w = v->getXForFrame(p.frame + p.duration) - x;
+        int h = NOTE_HEIGHT; //GF: larger notes
+    
+        if (m_model->getValueQuantization() != 0.0) {
+            h = y - getYForValue(v, p.value + m_model->getValueQuantization());
+            if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes
+        }
+
+        if (w < 1) w = 1;
+        paint.setPen(getBaseQColor());
+        paint.setBrush(brushColour);
+
+        if (shouldIlluminate &&
+                // "illuminatePoint == p"
+                !FlexiNoteModel::Point::Comparator()(illuminatePoint, p) &&
+                !FlexiNoteModel::Point::Comparator()(p, illuminatePoint)) {
+
+                paint.drawLine(x, -1, x, v->getPaintHeight() + 1);
+                paint.drawLine(x+w, -1, x+w, v->getPaintHeight() + 1);
+        
+                paint.setPen(v->getForeground());
+                // paint.setBrush(v->getForeground());
+        
+                QString vlabel = QString("freq: %1%2").arg(p.value).arg(m_model->getScaleUnits());
+                // v->drawVisibleText(paint, 
+                //                    x - paint.fontMetrics().width(vlabel) - 2,
+                //                    y + paint.fontMetrics().height()/2
+                //                      - paint.fontMetrics().descent(), 
+                //                    vlabel, View::OutlinedText);
+                v->drawVisibleText(paint, 
+                                   x,
+                                   y - h/2 - 2 - paint.fontMetrics().height()
+                                     - paint.fontMetrics().descent(), 
+                                   vlabel, View::OutlinedText);
+
+                QString hlabel = "dur: " + QString(RealTime::frame2RealTime
+                    (p.duration, m_model->getSampleRate()).toText(true).c_str());
+                v->drawVisibleText(paint, 
+                                   x,
+                                   y - h/2 - paint.fontMetrics().descent() - 2,
+                                   hlabel, View::OutlinedText);
+
+                QString llabel = QString("%1").arg(p.label);
+                v->drawVisibleText(paint, 
+                                   x,
+                                   y + h + 2 + paint.fontMetrics().descent(),
+                                   llabel, View::OutlinedText);
+                QString nlabel = QString("%1").arg(noteNumber);
+                v->drawVisibleText(paint, 
+                                   x + paint.fontMetrics().averageCharWidth() / 2,
+                                   y + h/2 - paint.fontMetrics().descent(),
+                                   nlabel, View::OutlinedText);
+        }
+    
+        paint.drawRect(x, y - h/2, w, h);
+    }
+
+    paint.restore();
+}
+
+int
+FlexiNoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
+{
+    if (!m_model || shouldAutoAlign()) {
+        return 0;
+    } else  {
+        if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
+            return LogNumericalScale().getWidth(v, paint) + 10; // for piano
+        } else {
+            return LinearNumericalScale().getWidth(v, paint);
+        }
+    }
+}
+
+void
+FlexiNoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
+{
+    if (!m_model || m_model->getPoints().empty()) return;
+
+    QString unit;
+    double min, max;
+    bool logarithmic;
+
+    int w = getVerticalScaleWidth(v, false, paint);
+    int h = v->getPaintHeight();
+
+    getScaleExtents(v, min, max, logarithmic);
+
+    if (logarithmic) {
+        LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
+    } else {
+        LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
+    }
+    
+    if (logarithmic && (getScaleUnits() == "Hz")) {
+        PianoScale().paintPianoVertical
+            (v, paint, QRect(w - 10, 0, 10, h), 
+             LogRange::unmap(min), 
+             LogRange::unmap(max));
+        paint.drawLine(w, 0, w, h);
+    }
+        
+    if (getScaleUnits() != "") {
+        int mw = w - 5;
+        paint.drawText(5,
+                       5 + paint.fontMetrics().ascent(),
+                       TextAbbrev::abbreviate(getScaleUnits(),
+                                              paint.fontMetrics(),
+                                              mw));
+    }
+}
+
+void
+FlexiNoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
+
+    if (!m_model) return;
+
+    sv_frame_t frame = v->getFrameForX(e->x());
+    if (frame < 0) frame = 0;
+    frame = frame / m_model->getResolution() * m_model->getResolution();
+
+    double value = getValueForY(v, e->y());
+
+    m_editingPoint = FlexiNoteModel::Point(frame, float(value), 0, 0.8f, tr("New Point"));
+    m_originalPoint = m_editingPoint;
+
+    if (m_editingCommand) finish(m_editingCommand);
+    m_editingCommand = new FlexiNoteModel::EditCommand(m_model,
+                                                       tr("Draw Point"));
+    m_editingCommand->addPoint(m_editingPoint);
+
+    m_editing = true;
+}
+
+void
+FlexiNoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
+
+    if (!m_model || !m_editing) return;
+
+    sv_frame_t frame = v->getFrameForX(e->x());
+    if (frame < 0) frame = 0;
+    frame = frame / m_model->getResolution() * m_model->getResolution();
+
+    double newValue = getValueForY(v, e->y());
+
+    sv_frame_t newFrame = m_editingPoint.frame;
+    sv_frame_t newDuration = frame - newFrame;
+    if (newDuration < 0) {
+        newFrame = frame;
+        newDuration = -newDuration;
+    } else if (newDuration == 0) {
+        newDuration = 1;
+    }
+
+    m_editingCommand->deletePoint(m_editingPoint);
+    m_editingPoint.frame = newFrame;
+    m_editingPoint.value = float(newValue);
+    m_editingPoint.duration = newDuration;
+    m_editingCommand->addPoint(m_editingPoint);
+}
+
+void
+FlexiNoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
+{
+//    SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
+    if (!m_model || !m_editing) return;
+    finish(m_editingCommand);
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+FlexiNoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
+{
+    if (!m_model) return;
+
+    if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
+
+    if (m_editingCommand) {
+        finish(m_editingCommand);
+        m_editingCommand = 0;
+    }
+
+    m_editing = true;
+}
+
+void
+FlexiNoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
+{
+}
+
+void
+FlexiNoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
+{
+    if (!m_model || !m_editing) return;
+
+    m_editing = false;
+
+    FlexiNoteModel::Point p(0);
+    if (!getPointToDrag(v, e->x(), e->y(), p)) return;
+    if (p.frame != m_editingPoint.frame || p.value != m_editingPoint.value) return;
+
+    m_editingCommand = new FlexiNoteModel::EditCommand(m_model, tr("Erase Point"));
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    finish(m_editingCommand);
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+FlexiNoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
+    std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
+
+    if (!m_model) return;
+
+    if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
+    m_originalPoint = FlexiNote(m_editingPoint);
+    
+    if (m_editMode == RightBoundary) {
+        m_dragPointX = v->getXForFrame(m_editingPoint.frame + m_editingPoint.duration);
+    } else {
+        m_dragPointX = v->getXForFrame(m_editingPoint.frame);
+    }
+    m_dragPointY = getYForValue(v, m_editingPoint.value);
+
+    if (m_editingCommand) {
+        finish(m_editingCommand);
+        m_editingCommand = 0;
+    }
+
+    m_editing = true;
+    m_dragStartX = e->x();
+    m_dragStartY = e->y();
+    
+    sv_frame_t onset = m_originalPoint.frame;
+    sv_frame_t offset = m_originalPoint.frame + m_originalPoint.duration - 1;
+    
+    m_greatestLeftNeighbourFrame = -1;
+    m_smallestRightNeighbourFrame = std::numeric_limits<int>::max();
+    
+    for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin();
+         i != m_model->getPoints().end(); ++i) {
+        FlexiNote currentNote = *i;
+        
+        // left boundary
+        if (currentNote.frame + currentNote.duration - 1 < onset) {
+            m_greatestLeftNeighbourFrame = currentNote.frame + currentNote.duration - 1;
+        }
+        
+        // right boundary
+        if (currentNote.frame > offset) {
+            m_smallestRightNeighbourFrame = currentNote.frame;
+            break;
+        }
+    }
+    std::cerr << "editStart: mode is " << m_editMode << ", note frame: " << onset << ", left boundary: " << m_greatestLeftNeighbourFrame << ", right boundary: " << m_smallestRightNeighbourFrame << std::endl;
+}
+
+void
+FlexiNoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
+    std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
+
+    if (!m_model || !m_editing) return;
+
+    int xdist = e->x() - m_dragStartX;
+    int ydist = e->y() - m_dragStartY;
+    int newx = m_dragPointX + xdist;
+    int newy = m_dragPointY + ydist;
+
+    sv_frame_t dragFrame = v->getFrameForX(newx);
+    if (dragFrame < 0) dragFrame = 0;
+    dragFrame = dragFrame / m_model->getResolution() * m_model->getResolution();
+    
+    double value = getValueForY(v, newy);
+
+    if (!m_editingCommand) {
+        m_editingCommand = new FlexiNoteModel::EditCommand(m_model,
+                                                           tr("Drag Point"));
+    }
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    std::cerr << "edit mode: " << m_editMode << " intelligent actions = "
+              << m_intelligentActions << std::endl;
+    
+    switch (m_editMode) {
+    case LeftBoundary : {
+        // left 
+        if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1;
+        // right
+        if (m_intelligentActions && dragFrame >= m_originalPoint.frame + m_originalPoint.duration) {
+            dragFrame = m_originalPoint.frame + m_originalPoint.duration - 1;
+        }
+        m_editingPoint.frame = dragFrame;
+        m_editingPoint.duration = m_originalPoint.frame - dragFrame + m_originalPoint.duration;
+        break;
+    }
+    case RightBoundary : {
+        // left
+        if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1;
+        if (m_intelligentActions && dragFrame >= m_smallestRightNeighbourFrame) dragFrame = m_smallestRightNeighbourFrame - 1;
+        m_editingPoint.duration = dragFrame - m_originalPoint.frame + 1;
+        break;
+    }
+    case DragNote : {
+        // left
+        if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1;
+        // right
+        if (m_intelligentActions && dragFrame + m_originalPoint.duration >= m_smallestRightNeighbourFrame) {
+            dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.duration;
+        }
+        m_editingPoint.frame = dragFrame;
+
+        m_editingPoint.value = float(value);
+
+        // Re-analyse region within +/- 1 semitone of the dragged value
+        float cents = 0;
+        int midiPitch = Pitch::getPitchForFrequency(m_editingPoint.value, &cents);
+        double lower = Pitch::getFrequencyForPitch(midiPitch - 1, cents);
+        double higher = Pitch::getFrequencyForPitch(midiPitch + 1, cents);
+        
+        emit reAnalyseRegion(m_editingPoint.frame,
+                             m_editingPoint.frame + m_editingPoint.duration,
+                             float(lower), float(higher));
+        break;
+    }
+    case SplitNote: // nothing
+        break;
+    }
+
+//    updateNoteValueFromPitchCurve(v, m_editingPoint);
+    m_editingCommand->addPoint(m_editingPoint);
+
+    std::cerr << "added new point(" << m_editingPoint.frame << "," << m_editingPoint.duration << ")" << std::endl;
+}
+
+void
+FlexiNoteLayer::editEnd(LayerGeometryProvider *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
+    std::cerr << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
+    
+    if (!m_model || !m_editing) return;
+
+    if (m_editingCommand) {
+
+        QString newName = m_editingCommand->getName();
+
+        if (m_editMode == DragNote) {
+            //!!! command nesting is wrong?
+            emit materialiseReAnalysis();
+        }
+
+        m_editingCommand->deletePoint(m_editingPoint);
+        updateNoteValueFromPitchCurve(v, m_editingPoint);
+        m_editingCommand->addPoint(m_editingPoint);
+
+        if (m_editingPoint.frame != m_originalPoint.frame) {
+            if (m_editingPoint.value != m_originalPoint.value) {
+                newName = tr("Edit Point");
+            } else {
+                newName = tr("Relocate Point");
+            }
+        } else {
+            newName = tr("Change Point Value");
+        }
+
+        m_editingCommand->setName(newName);
+        finish(m_editingCommand);
+    }
+
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e)
+{
+    // GF: note splitting starts (!! remove printing soon)
+    std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl;
+    if (!m_model) return;
+
+    if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
+    // m_originalPoint = m_editingPoint;
+    // 
+    // m_dragPointX = v->getXForFrame(m_editingPoint.frame);
+    // m_dragPointY = getYForValue(v, m_editingPoint.value);
+
+    if (m_editingCommand) {
+        finish(m_editingCommand);
+        m_editingCommand = 0;
+    }
+
+    m_editing = true;
+    m_dragStartX = e->x();
+    m_dragStartY = e->y();
+}
+
+void
+FlexiNoteLayer::splitEnd(LayerGeometryProvider *v, QMouseEvent *e)
+{
+    // GF: note splitting ends. (!! remove printing soon)
+    std::cerr << "splitEnd" << std::endl;
+    if (!m_model || !m_editing || m_editMode != SplitNote) return;
+
+    int xdist = e->x() - m_dragStartX;
+    int ydist = e->y() - m_dragStartY;
+    if (xdist != 0 || ydist != 0) { 
+        std::cerr << "mouse moved" << std::endl;    
+        return; 
+    }
+
+    sv_frame_t frame = v->getFrameForX(e->x());
+
+    splitNotesAt(v, frame, e);
+}
+
+void
+FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame)
+{
+    splitNotesAt(v, frame, 0);
+}
+
+void
+FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e)
+{
+    FlexiNoteModel::PointList onPoints = m_model->getPoints(frame);
+    if (onPoints.empty()) return;
+    
+    FlexiNote note(*onPoints.begin());
+
+    FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+        (m_model, tr("Edit Point"));
+    command->deletePoint(note);
+
+    if (!e || !(e->modifiers() & Qt::ShiftModifier)) {
+
+        int gap = 0; // MM: I prefer a gap of 0, but we can decide later
+    
+        FlexiNote newNote1(note.frame, note.value, 
+                           frame - note.frame - gap, 
+                           note.level, note.label);
+    
+        FlexiNote newNote2(frame, note.value, 
+                           note.duration - newNote1.duration, 
+                           note.level, note.label);
+                       
+        if (m_intelligentActions) {
+            if (updateNoteValueFromPitchCurve(v, newNote1)) {
+                command->addPoint(newNote1);
+            }
+            if (updateNoteValueFromPitchCurve(v, newNote2)) {
+                command->addPoint(newNote2);
+            }
+        } else {
+            command->addPoint(newNote1);
+            command->addPoint(newNote2);
+        }
+    }
+
+    finish(command);
+}
+
+void
+FlexiNoteLayer::addNote(LayerGeometryProvider *v, QMouseEvent *e)
+{
+    std::cerr << "addNote" << std::endl;
+    if (!m_model) return;
+
+    sv_frame_t duration = 10000;
+    
+    sv_frame_t frame = v->getFrameForX(e->x());
+    double value = getValueForY(v, e->y());
+    
+    FlexiNoteModel::PointList noteList = m_model->getPoints();
+
+    if (m_intelligentActions) {
+        sv_frame_t smallestRightNeighbourFrame = 0;
+        for (FlexiNoteModel::PointList::const_iterator i = noteList.begin();
+             i != noteList.end(); ++i) {
+            FlexiNote currentNote = *i;
+            if (currentNote.frame > frame) {
+                smallestRightNeighbourFrame = currentNote.frame;
+                break;
+            }
+        }
+        if (smallestRightNeighbourFrame > 0) {
+            duration = std::min(smallestRightNeighbourFrame - frame + 1, duration);
+            duration = (duration > 0) ? duration : 0;
+        }
+    }
+
+    if (!m_intelligentActions || 
+        (m_model->getPoints(frame).empty() && duration > 0)) {
+        FlexiNote newNote(frame, float(value), duration, 100.f, "new note");
+        FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+            (m_model, tr("Add Point"));
+        command->addPoint(newNote);
+        finish(command);
+    }
+}
+
+SparseTimeValueModel *
+FlexiNoteLayer::getAssociatedPitchModel(LayerGeometryProvider *v) const
+{
+    // Better than we used to do, but still not very satisfactory
+
+//    cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
+
+    for (int i = 0; i < v->getView()->getLayerCount(); ++i) {
+        Layer *layer = v->getView()->getLayer(i);
+        if (layer &&
+            layer->getLayerPresentationName() != "candidate") {
+//            cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
+            SparseTimeValueModel *model = qobject_cast<SparseTimeValueModel *>
+                (layer->getModel());
+//            cerr << "FlexiNoteLayer::getAssociatedPitchModel: and its model is " << model << endl;
+            if (model && model->getScaleUnits() == "Hz") {
+                cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
+                return model;
+            }
+        }
+    }
+    cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
+    return 0;
+}
+
+void
+FlexiNoteLayer::snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+        (m_model, tr("Snap Notes"));
+
+    cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl;
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        FlexiNote note(*i);
+
+        cerr << "snapSelectedNotesToPitchTrack: looking at note from " << note.frame << " to " << note.frame + note.duration << endl;
+
+        if (!s.contains(note.frame) &&
+            !s.contains(note.frame + note.duration - 1)) {
+            continue;
+        }
+
+        cerr << "snapSelectedNotesToPitchTrack: making new note" << endl;
+        FlexiNote newNote(note);
+
+        command->deletePoint(note);
+
+        if (updateNoteValueFromPitchCurve(v, newNote)) {
+            command->addPoint(newNote);
+        }
+    }
+    
+    finish(command);
+}
+
+void
+FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
+{
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    FlexiNoteModel::PointList::iterator i = points.begin();
+    if (inclusive) {
+        while (i != points.end() && i->frame + i->duration < s.getStartFrame()) {
+            ++i;
+        }
+    } else {
+        while (i != points.end() && i->frame < s.getStartFrame()) {
+            ++i;
+        }
+    }
+        
+    if (i == points.end()) return;
+
+    FlexiNoteModel::EditCommand *command = 
+        new FlexiNoteModel::EditCommand(m_model, tr("Merge Notes"));
+
+    FlexiNote newNote(*i);
+
+    while (i != points.end()) {
+
+        if (inclusive) {
+            if (i->frame >= s.getEndFrame()) break;
+        } else {
+            if (i->frame + i->duration > s.getEndFrame()) break;
+        }
+
+        newNote.duration = i->frame + i->duration - newNote.frame;
+        command->deletePoint(*i);
+
+        ++i;
+    }
+
+    updateNoteValueFromPitchCurve(v, newNote);
+    command->addPoint(newNote);
+    finish(command);
+}
+
+bool
+FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const
+{
+    SparseTimeValueModel *model = getAssociatedPitchModel(v);
+    if (!model) return false;
+        
+    std::cerr << model->getTypeName() << std::endl;
+
+    SparseModel<TimeValuePoint>::PointList dataPoints =
+        model->getPoints(note.frame, note.frame + note.duration);
+   
+    std::cerr << "frame " << note.frame << ": " << dataPoints.size() << " candidate points" << std::endl;
+   
+    if (dataPoints.empty()) return false;
+
+    std::vector<double> pitchValues;
+   
+    for (SparseModel<TimeValuePoint>::PointList::const_iterator i =
+             dataPoints.begin(); i != dataPoints.end(); ++i) {
+        if (i->frame >= note.frame &&
+            i->frame < note.frame + note.duration) {
+            pitchValues.push_back(i->value);
+        }
+    }
+        
+    if (pitchValues.empty()) return false;
+
+    sort(pitchValues.begin(), pitchValues.end());
+    int size = int(pitchValues.size());
+    double median;
+
+    if (size % 2 == 0) {
+        median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2;
+    } else {
+        median = pitchValues[size/2];
+    }
+
+    std::cerr << "updateNoteValueFromPitchCurve: corrected from " << note.value << " to median " << median << std::endl;
+    
+    note.value = float(median);
+
+    return true;
+}
+
+void 
+FlexiNoteLayer::mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *e)
+{
+    // GF: context sensitive cursors
+    // v->getView()->setCursor(Qt::ArrowCursor);
+    FlexiNoteModel::Point note(0);
+    if (!getNoteToEdit(v, e->x(), e->y(), note)) { 
+        // v->getView()->setCursor(Qt::UpArrowCursor);
+        return; 
+    }
+
+    bool closeToLeft = false, closeToRight = false,
+        closeToTop = false, closeToBottom = false;
+    getRelativeMousePosition(v, note, e->x(), e->y(),
+                             closeToLeft, closeToRight,
+                             closeToTop, closeToBottom);
+    
+    if (closeToLeft) {
+        v->getView()->setCursor(Qt::SizeHorCursor);
+        m_editMode = LeftBoundary;
+        cerr << "edit mode -> LeftBoundary" << endl;
+    } else if (closeToRight) {
+        v->getView()->setCursor(Qt::SizeHorCursor);
+        m_editMode = RightBoundary;
+        cerr << "edit mode -> RightBoundary" << endl;
+    } else if (closeToTop) {
+        v->getView()->setCursor(Qt::CrossCursor);
+        m_editMode = DragNote;
+        cerr << "edit mode -> DragNote" << endl;
+    } else if (closeToBottom) {
+        v->getView()->setCursor(Qt::UpArrowCursor);
+        m_editMode = SplitNote;
+        cerr << "edit mode -> SplitNote" << endl;
+    } else {
+        v->getView()->setCursor(Qt::ArrowCursor);
+    }
+}
+
+void
+FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
+{
+    // GF: TODO: consoloidate the tolerance values
+    if (!m_model) return;
+
+    int ctol = 0;
+    int noteStartX = v->getXForFrame(note.frame);
+    int noteEndX = v->getXForFrame(note.frame + note.duration);
+    int noteValueY = getYForValue(v,note.value);
+    int noteStartY = noteValueY - (NOTE_HEIGHT / 2);
+    int noteEndY = noteValueY + (NOTE_HEIGHT / 2);
+    
+    bool closeToNote = false;
+    
+    if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true;
+    if (!closeToNote) return;
+    
+    int tol = NOTE_HEIGHT / 2;
+    
+    if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true;
+    if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true;
+    if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true;
+    if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true;
+
+//    cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl;
+}
+
+
+bool
+FlexiNoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
+{
+    std::cerr << "Opening note editor dialog" << std::endl;
+    if (!m_model) return false;
+
+    FlexiNoteModel::Point note(0);
+    if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
+
+//    FlexiNoteModel::Point note = *points.begin();
+
+    ItemEditDialog *dialog = new ItemEditDialog
+        (m_model->getSampleRate(),
+         ItemEditDialog::ShowTime |
+         ItemEditDialog::ShowDuration |
+         ItemEditDialog::ShowValue |
+         ItemEditDialog::ShowText,
+         getScaleUnits());
+
+    dialog->setFrameTime(note.frame);
+    dialog->setValue(note.value);
+    dialog->setFrameDuration(note.duration);
+    dialog->setText(note.label);
+
+    if (dialog->exec() == QDialog::Accepted) {
+
+        FlexiNoteModel::Point newNote = note;
+        newNote.frame = dialog->getFrameTime();
+        newNote.value = dialog->getValue();
+        newNote.duration = dialog->getFrameDuration();
+        newNote.label = dialog->getText();
+        
+        FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+            (m_model, tr("Edit Point"));
+        command->deletePoint(note);
+        command->addPoint(newNote);
+        finish(command);
+    }
+
+    delete dialog;
+    return true;
+}
+
+void
+FlexiNoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Drag Selection"));
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        if (s.contains(i->frame)) {
+            FlexiNoteModel::Point newPoint(*i);
+            newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
+            command->deletePoint(*i);
+            command->addPoint(newPoint);
+        }
+    }
+
+    finish(command);
+}
+
+void
+FlexiNoteLayer::resizeSelection(Selection s, Selection newSize)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Resize Selection"));
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    double ratio =
+        double(newSize.getEndFrame() - newSize.getStartFrame()) /
+        double(s.getEndFrame() - s.getStartFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        if (s.contains(i->frame)) {
+
+            double targetStart = double(i->frame);
+            targetStart = double(newSize.getStartFrame()) +
+                targetStart - double(s.getStartFrame()) * ratio;
+
+            double targetEnd = double(i->frame + i->duration);
+            targetEnd = double(newSize.getStartFrame()) +
+                targetEnd - double(s.getStartFrame()) * ratio;
+
+            FlexiNoteModel::Point newPoint(*i);
+            newPoint.frame = lrint(targetStart);
+            newPoint.duration = lrint(targetEnd - targetStart);
+            command->deletePoint(*i);
+            command->addPoint(newPoint);
+        }
+    }
+
+    finish(command);
+}
+
+void
+FlexiNoteLayer::deleteSelection(Selection s)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Delete Selected Points"));
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        if (s.contains(i->frame)) {
+            command->deletePoint(*i);
+        }
+    }
+
+    finish(command);
+}    
+
+void
+FlexiNoteLayer::deleteSelectionInclusive(Selection s)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Delete Selected Points"));
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+        bool overlap = !(
+            ((s.getStartFrame() <= i->frame) && (s.getEndFrame() <= i->frame)) || // selection is left of note
+            ((s.getStartFrame() >= (i->frame+i->duration)) && (s.getEndFrame() >= (i->frame+i->duration))) // selection is right of note
+            );
+        if (overlap) {
+            command->deletePoint(*i);
+        }
+    }
+
+    finish(command);
+}
+
+void
+FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+        if (s.contains(i->frame)) {
+            Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label);
+            point.setReferenceFrame(alignToReference(v, i->frame));
+            to.addPoint(point);
+        }
+    }
+}
+
+bool
+FlexiNoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /*frameOffset */, bool /* interactive */)
+{
+    if (!m_model) return false;
+
+    const Clipboard::PointList &points = from.getPoints();
+
+    bool realign = false;
+
+    if (clipboardHasDifferentAlignment(v, from)) {
+
+        QMessageBox::StandardButton button =
+            QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
+                                  tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
+                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+                                  QMessageBox::Yes);
+
+        if (button == QMessageBox::Cancel) {
+            return false;
+        }
+
+        if (button == QMessageBox::Yes) {
+            realign = true;
+        }
+    }
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Paste"));
+
+    for (Clipboard::PointList::const_iterator i = points.begin();
+         i != points.end(); ++i) {
+        
+        if (!i->haveFrame()) continue;
+        sv_frame_t frame = 0;
+
+        if (!realign) {
+            
+            frame = i->getFrame();
+
+        } else {
+
+            if (i->haveReferenceFrame()) {
+                frame = i->getReferenceFrame();
+                frame = alignFromReference(v, frame);
+            } else {
+                frame = i->getFrame();
+            }
+        }
+
+        FlexiNoteModel::Point newPoint(frame);
+  
+        if (i->haveLabel()) newPoint.label = i->getLabel();
+        if (i->haveValue()) newPoint.value = i->getValue();
+        else newPoint.value = (m_model->getValueMinimum() +
+                               m_model->getValueMaximum()) / 2;
+        if (i->haveLevel()) newPoint.level = i->getLevel();
+        if (i->haveDuration()) newPoint.duration = i->getDuration();
+        else {
+            sv_frame_t nextFrame = frame;
+            Clipboard::PointList::const_iterator j = i;
+            for (; j != points.end(); ++j) {
+                if (!j->haveFrame()) continue;
+                if (j != i) break;
+            }
+            if (j != points.end()) {
+                nextFrame = j->getFrame();
+            }
+            if (nextFrame == frame) {
+                newPoint.duration = m_model->getResolution();
+            } else {
+                newPoint.duration = nextFrame - frame;
+            }
+        }
+        
+        command->addPoint(newPoint);
+    }
+
+    finish(command);
+    return true;
+}
+
+void
+FlexiNoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
+{
+    m_pendingNoteOns.insert(FlexiNote(frame, float(pitch), 0, float(velocity / 127.0), ""));
+}
+
+void
+FlexiNoteLayer::addNoteOff(sv_frame_t frame, int pitch)
+{
+    for (FlexiNoteSet::iterator i = m_pendingNoteOns.begin();
+         i != m_pendingNoteOns.end(); ++i) {
+        if (lrint((*i).value) == pitch) {
+            FlexiNote note(*i);
+            m_pendingNoteOns.erase(i);
+            note.duration = frame - note.frame;
+            if (m_model) {
+                FlexiNoteModel::AddPointCommand *c = new FlexiNoteModel::AddPointCommand
+                    (m_model, note, tr("Record FlexiNote"));
+                // execute and bundle:
+                CommandHistory::getInstance()->addCommand(c, true, true);
+            }
+            break;
+        }
+    }
+}
+
+void
+FlexiNoteLayer::abandonNoteOns()
+{
+    m_pendingNoteOns.clear();
+}
+
+int
+FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
+{
+    impose = false;
+    return ColourDatabase::getInstance()->getColourIndex
+        (QString(darkbg ? "White" : "Black"));
+}
+
+void
+FlexiNoteLayer::toXml(QTextStream &stream,
+                      QString indent, QString extraAttributes) const
+{
+    SingleColourLayer::toXml(stream, indent, extraAttributes +
+                             QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
+                             .arg(m_verticalScale)
+                             .arg(m_scaleMinimum)
+                             .arg(m_scaleMaximum));
+}
+
+void
+FlexiNoteLayer::setProperties(const QXmlAttributes &attributes)
+{
+    SingleColourLayer::setProperties(attributes);
+
+    bool ok;
+    VerticalScale scale = (VerticalScale)
+        attributes.value("verticalScale").toInt(&ok);
+    if (ok) setVerticalScale(scale);
+
+//    bool alsoOk;
+//    double min = attributes.value("scaleMinimum").toDouble(&ok);
+//    double max = attributes.value("scaleMaximum").toDouble(&alsoOk);
+//    if (ok && alsoOk && min != max) setDisplayExtents(min, max);
+}
+
+void
+FlexiNoteLayer::setVerticalRangeToNoteRange(LayerGeometryProvider *v)
+{
+    double minf = std::numeric_limits<double>::max();
+    double maxf = 0;
+    bool hasNotes = 0;
+    for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin();
+         i != m_model->getPoints().end(); ++i) {
+        hasNotes = 1;
+        FlexiNote note = *i;
+        if (note.value < minf) minf = note.value;
+        if (note.value > maxf) maxf = note.value;
+    }
+    
+    std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl;
+    
+    if (hasNotes) {
+        v->getView()->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); 
+        // MM: this is a hack because we rely on 
+        // * this layer being automatically aligned to layer 1
+        // * layer one is a log frequency layer.
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/FlexiNoteLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,215 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _FLEXINOTE_LAYER_H_
+#define _FLEXINOTE_LAYER_H_
+
+#define NOTE_HEIGHT 16
+
+#include "SingleColourLayer.h"
+#include "VerticalScaleLayer.h"
+
+#include "data/model/FlexiNoteModel.h"
+
+#include <QObject>
+#include <QColor>
+
+class View;
+class QPainter;
+class SparseTimeValueModel;
+
+class FlexiNoteLayer : public SingleColourLayer,
+                       public VerticalScaleLayer
+{
+    Q_OBJECT
+
+public:
+    FlexiNoteLayer();
+
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
+
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
+
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
+
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+                    int &resolution,
+                    SnapType snap) const;
+
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
+
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
+
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
+
+    virtual void splitStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void splitEnd(LayerGeometryProvider *v, QMouseEvent *);
+    
+    virtual void addNote(LayerGeometryProvider *v, QMouseEvent *e);
+
+    virtual void mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *);
+
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
+
+    virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
+    virtual void resizeSelection(Selection s, Selection newSize);
+    virtual void deleteSelection(Selection s);
+    virtual void deleteSelectionInclusive(Selection s);
+
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
+                       bool interactive);
+
+    void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame);
+    void snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s);
+    void mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive);
+
+    virtual const Model *getModel() const { return m_model; }
+    void setModel(FlexiNoteModel *model);
+
+    virtual PropertyList getProperties() const;
+    virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual PropertyType getPropertyType(const PropertyName &) const;
+    virtual QString getPropertyGroupName(const PropertyName &) const;
+    virtual int getPropertyRangeAndValue(const PropertyName &,
+                                         int *min, int *max, int *deflt) const;
+    virtual QString getPropertyValueLabel(const PropertyName &,
+                      int value) const;
+    virtual void setProperty(const PropertyName &, int value);
+
+    enum VerticalScale {
+        AutoAlignScale,
+        LinearScale,
+        LogScale,
+        MIDIRangeScale
+    };
+    
+    //GF: Tonioni: context sensitive note edit actions (denoted clockwise from top).
+    enum EditMode {
+        DragNote,
+        RightBoundary,
+        SplitNote,
+        LeftBoundary
+    };
+    
+    void setIntelligentActions(bool on) { m_intelligentActions=on; }
+
+    void setVerticalScale(VerticalScale scale);
+    VerticalScale getVerticalScale() const { return m_verticalScale; }
+
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
+
+    virtual bool isLayerEditable() const { return true; }
+
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
+
+    virtual bool getValueExtents(double &min, double &max,
+                                 bool &log, QString &unit) const;
+
+    virtual bool getDisplayExtents(double &min, double &max) const;
+    virtual bool setDisplayExtents(double min, double max);
+
+    virtual int getVerticalZoomSteps(int &defaultStep) const;
+    virtual int getCurrentVerticalZoomStep() const;
+    virtual void setVerticalZoomStep(int);
+    virtual RangeMapper *getNewVerticalZoomRangeMapper() const;
+
+    /**
+     * Add a note-on.  Used when recording MIDI "live".  The note will
+     * not be finally added to the layer until the corresponding
+     * note-off.
+     */
+    void addNoteOn(sv_frame_t frame, int pitch, int velocity);
+    
+    /**
+     * Add a note-off.  This will cause a note to appear, if and only
+     * if there is a matching pending note-on.
+     */
+    void addNoteOff(sv_frame_t frame, int pitch);
+
+    /**
+     * Abandon all pending note-on events.
+     */
+    void abandonNoteOns();
+
+    virtual void toXml(QTextStream &stream, QString indent = "",
+                       QString extraAttributes = "") const;
+
+    void setProperties(const QXmlAttributes &attributes);
+    
+    void setVerticalRangeToNoteRange(LayerGeometryProvider *v);
+
+    /// VerticalScaleLayer methods
+    virtual int getYForValue(LayerGeometryProvider *v, double value) const;
+    virtual double getValueForY(LayerGeometryProvider *v, int y) const;
+    virtual QString getScaleUnits() const;
+
+signals:
+    void reAnalyseRegion(sv_frame_t, sv_frame_t, float, float);
+    void materialiseReAnalysis();
+    
+protected:
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
+    bool shouldConvertMIDIToHz() const;
+
+    virtual int getDefaultColourHint(bool dark, bool &impose);
+
+    FlexiNoteModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
+
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const;
+    bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const;
+    void getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const;
+    SparseTimeValueModel *getAssociatedPitchModel(LayerGeometryProvider *v) const;
+    bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const;
+    void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e);
+
+    FlexiNoteModel *m_model;
+    bool m_editing;
+    bool m_intelligentActions;
+    int m_dragPointX;
+    int m_dragPointY;
+    int m_dragStartX;
+    int m_dragStartY;
+    FlexiNoteModel::Point m_originalPoint;
+    FlexiNoteModel::Point m_editingPoint;
+    sv_frame_t m_greatestLeftNeighbourFrame;
+    sv_frame_t m_smallestRightNeighbourFrame;
+    FlexiNoteModel::EditCommand *m_editingCommand;
+    VerticalScale m_verticalScale;
+    EditMode m_editMode;
+
+    typedef std::set<FlexiNoteModel::Point, FlexiNoteModel::Point::Comparator> FlexiNoteSet;
+    FlexiNoteSet m_pendingNoteOns;
+
+    mutable double m_scaleMinimum;
+    mutable double m_scaleMaximum;
+
+    bool shouldAutoAlign() const;
+
+    void finish(FlexiNoteModel::EditCommand *command) {
+        Command *c = command->finish();
+        if (c) CommandHistory::getInstance()->addCommand(c, false);
+    }
+};
+
+#endif
+
--- a/layer/ImageLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/ImageLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -78,7 +78,7 @@
 }
 
 QString
-ImageLayer::getPropertyLabel(const PropertyName &name) const
+ImageLayer::getPropertyLabel(const PropertyName &) const
 {
     return "";
 }
@@ -110,20 +110,20 @@
 }
 
 bool
-ImageLayer::getValueExtents(float &, float &, bool &, QString &) const
+ImageLayer::getValueExtents(double &, double &, bool &, QString &) const
 {
     return false;
 }
 
 bool
-ImageLayer::isLayerScrollable(const View *v) const
+ImageLayer::isLayerScrollable(const LayerGeometryProvider *) const
 {
     return true;
 }
 
 
 ImageModel::PointList
-ImageLayer::getLocalPoints(View *v, int x, int y) const
+ImageLayer::getLocalPoints(LayerGeometryProvider *v, int x, int ) const
 {
     if (!m_model) return ImageModel::PointList();
 
@@ -169,7 +169,7 @@
 }
 
 QString
-ImageLayer::getFeatureDescription(View *v, QPoint &pos) const
+ImageLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -185,9 +185,9 @@
 	}
     }
 
-    long useFrame = points.begin()->frame;
+//    int useFrame = points.begin()->frame;
 
-    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
+//    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
 
     QString text;
 /*    
@@ -208,8 +208,8 @@
 //!!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer
 
 bool
-ImageLayer::snapToFeatureFrame(View *v, int &frame,
-			      size_t &resolution,
+ImageLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+			      int &resolution,
 			      SnapType snap) const
 {
     if (!m_model) {
@@ -228,7 +228,7 @@
     }    
 
     points = m_model->getPoints(frame, frame);
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
 
     for (ImageModel::PointList::const_iterator i = points.begin();
@@ -280,26 +280,26 @@
 }
 
 void
-ImageLayer::paint(View *v, QPainter &paint, QRect rect) const
+ImageLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
-    int sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("ImageLayer::paint", true);
 
 //    int x0 = rect.left(), x1 = rect.right();
-    int x0 = 0, x1 = v->width();
+    int x0 = 0, x1 = v->getPaintWidth();
 
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1);
+    sv_frame_t frame0 = v->getFrameForX(x0);
+    sv_frame_t frame1 = v->getFrameForX(x1);
 
     ImageModel::PointList points(m_model->getPoints(frame0, frame1));
     if (points.empty()) return;
 
     paint.save();
-    paint.setClipRect(rect.x(), 0, rect.width(), v->height());
+    paint.setClipRect(rect.x(), 0, rect.width(), v->getPaintHeight());
 
     QColor penColour;
     penColour = v->getForeground();
@@ -338,7 +338,7 @@
 }
 
 void
-ImageLayer::drawImage(View *v, QPainter &paint, const ImageModel::Point &p,
+ImageLayer::drawImage(LayerGeometryProvider *v, QPainter &paint, const ImageModel::Point &p,
                       int x, int nx) const
 {
     QString label = p.label;
@@ -358,12 +358,12 @@
     int bottomMargin = 10;
     int spacing = 5;
 
-    if (v->height() < 100) {
+    if (v->getPaintHeight() < 100) {
         topMargin = 5;
         bottomMargin = 5;
     }
 
-    int maxBoxHeight = v->height() - topMargin - bottomMargin;
+    int maxBoxHeight = v->getPaintHeight() - topMargin - bottomMargin;
 
     int availableWidth = nx - x - 3;
     if (availableWidth < 20) availableWidth = 20;
@@ -372,7 +372,7 @@
 
     if (label != "") {
 
-        int likelyHeight = v->height() / 4;
+        int likelyHeight = v->getPaintHeight() / 4;
 
         int likelyWidth = // available height times image aspect
             ((maxBoxHeight - likelyHeight) * imageSize.width())
@@ -435,10 +435,10 @@
         division += paint.fontMetrics().height();
     }                
 
-    bottomMargin = v->height() - topMargin - boxHeight;
-    if (bottomMargin > topMargin + v->height()/7) {
-        topMargin += v->height()/8;
-        bottomMargin -= v->height()/8;
+    bottomMargin = v->getPaintHeight() - topMargin - boxHeight;
+    if (bottomMargin > topMargin + v->getPaintHeight()/7) {
+        topMargin += v->getPaintHeight()/8;
+        bottomMargin -= v->getPaintHeight()/8;
     }
 
     paint.drawRect(x - 1,
@@ -480,7 +480,7 @@
 }
 
 void
-ImageLayer::setLayerDormant(const View *v, bool dormant)
+ImageLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     if (dormant) {
         // Delete the images named in the view's scaled map from the
@@ -517,10 +517,8 @@
 }
 
 QImage 
-ImageLayer::getImage(View *v, QString name, QSize maxSize) const
+ImageLayer::getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const
 {
-    bool need = false;
-
 //    SVDEBUG << "ImageLayer::getImage(" << v << ", " << name << ", ("
 //              << maxSize.width() << "x" << maxSize.height() << "))" << endl;
 
@@ -556,7 +554,7 @@
 }
 
 void
-ImageLayer::drawStart(View *v, QMouseEvent *e)
+ImageLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -565,7 +563,7 @@
 	return;
     }
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -580,13 +578,13 @@
 }
 
 void
-ImageLayer::drawDrag(View *v, QMouseEvent *e)
+ImageLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -596,13 +594,11 @@
 }
 
 void
-ImageLayer::drawEnd(View *v, QMouseEvent *)
+ImageLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
 
-    bool ok = false;
-
     ImageDialog dialog(tr("Select image"), "", "");
 
     if (dialog.exec() == QDialog::Accepted) {
@@ -623,7 +619,7 @@
 }
 
 bool
-ImageLayer::addImage(long frame, QString url)
+ImageLayer::addImage(sv_frame_t frame, QString url)
 {
     QImage image(getLocalFilename(url));
     if (image.isNull()) {
@@ -642,7 +638,7 @@
 }
 
 void
-ImageLayer::editStart(View *v, QMouseEvent *e)
+ImageLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -664,12 +660,12 @@
 }
 
 void
-ImageLayer::editDrag(View *v, QMouseEvent *e)
+ImageLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
-    long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
-    long frame = m_originalPoint.frame + frameDiff;
+    sv_frame_t frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
+    sv_frame_t frame = m_originalPoint.frame + frameDiff;
 
     if (frame < 0) frame = 0;
     frame = (frame / m_model->getResolution()) * m_model->getResolution();
@@ -684,7 +680,7 @@
 }
 
 void
-ImageLayer::editEnd(View *, QMouseEvent *)
+ImageLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -698,7 +694,7 @@
 }
 
 bool
-ImageLayer::editOpen(View *v, QMouseEvent *e)
+ImageLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -727,7 +723,7 @@
 }    
 
 void
-ImageLayer::moveSelection(Selection s, size_t newStartFrame)
+ImageLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
     if (!m_model) return;
 
@@ -771,9 +767,9 @@
 
 	if (s.contains(i->frame)) {
 
-	    double target = i->frame;
-	    target = newSize.getStartFrame() + 
-		double(target - s.getStartFrame()) * ratio;
+	    double target = double(i->frame);
+	    target = double(newSize.getStartFrame()) +
+		target - double(s.getStartFrame()) * ratio;
 
 	    ImageModel::Point newPoint(*i);
 	    newPoint.frame = lrint(target);
@@ -805,7 +801,7 @@
 }
 
 void
-ImageLayer::copy(View *v, Selection s, Clipboard &to)
+ImageLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -823,7 +819,7 @@
 }
 
 bool
-ImageLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
+ImageLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -834,7 +830,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
                                   tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
                                   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
                                   QMessageBox::Yes);
@@ -856,7 +852,7 @@
         
         if (!i->haveFrame()) continue;
 
-        size_t frame = 0;
+        sv_frame_t frame = 0;
 
         if (!realign) {
             
@@ -970,7 +966,7 @@
 }
 
 void
-ImageLayer::setProperties(const QXmlAttributes &attributes)
+ImageLayer::setProperties(const QXmlAttributes &)
 {
 }
 
--- a/layer/ImageLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/ImageLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -38,31 +38,31 @@
     ImageLayer();
     virtual ~ImageLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void moveSelection(Selection s, size_t newStartFrame);
+    virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
-    virtual bool editOpen(View *, QMouseEvent *); // on double-click
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *); // on double-click
 
     virtual const Model *getModel() const { return m_model; }
     void setModel(ImageModel *model);
@@ -80,43 +80,43 @@
         return ColourAbsent;
     }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
     void setProperties(const QXmlAttributes &attributes);
 
-    virtual bool addImage(long frame, QString url); // using a command
+    virtual bool addImage(sv_frame_t frame, QString url); // using a command
 
 protected slots:
     void checkAddSources();
     void fileSourceReady();
 
 protected:
-    ImageModel::PointList getLocalPoints(View *v, int x, int y) const;
+    ImageModel::PointList getLocalPoints(LayerGeometryProvider *v, int x, int y) const;
 
     bool getImageOriginalSize(QString name, QSize &size) const;
-    QImage getImage(View *v, QString name, QSize maxSize) const;
+    QImage getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const;
 
-    void drawImage(View *v, QPainter &paint, const ImageModel::Point &p,
+    void drawImage(LayerGeometryProvider *v, QPainter &paint, const ImageModel::Point &p,
                    int x, int nx) const;
 
     //!!! how to reap no-longer-used images?
 
     typedef std::map<QString, QImage> ImageMap;
-    typedef std::map<const View *, ImageMap> ViewImageMap;
+    typedef std::map<const LayerGeometryProvider *, ImageMap> ViewImageMap;
     typedef std::map<QString, FileSource *> FileSourceMap;
 
     static ImageMap m_images;
--- a/layer/ImageRegionFinder.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/ImageRegionFinder.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -107,9 +107,9 @@
         return false;
     }
 
-    float ar = float(qRed(a) / 255.f);
-    float ag = float(qGreen(a) / 255.f);
-    float ab = float(qBlue(a) / 255.f);
+    float ar = float(qRed(a)) / 255.f;
+    float ag = float(qGreen(a)) / 255.f;
+    float ab = float(qBlue(a)) / 255.f;
     float amag = sqrtf(ar * ar + ag * ag + ab * ab);
     float thresh = amag / 2;
 
--- a/layer/Layer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/Layer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -51,8 +51,8 @@
     connect(model, SIGNAL(modelChanged()),
             this, SIGNAL(modelChanged()));
 
-    connect(model, SIGNAL(modelChanged(size_t, size_t)),
-	    this, SIGNAL(modelChanged(size_t, size_t)));
+    connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+	    this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
 
     connect(model, SIGNAL(completionChanged()),
 	    this, SIGNAL(modelCompletionChanged()));
@@ -115,7 +115,7 @@
 }
 
 void
-Layer::setLayerDormant(const View *v, bool dormant)
+Layer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     const void *vv = (const void *)v;
     QMutexLocker locker(&m_dormancyMutex);
@@ -123,7 +123,7 @@
 }
 
 bool
-Layer::isLayerDormant(const View *v) const
+Layer::isLayerDormant(const LayerGeometryProvider *v) const
 {
     const void *vv = (const void *)v;
     QMutexLocker locker(&m_dormancyMutex);
@@ -132,65 +132,65 @@
 }
 
 void
-Layer::showLayer(View *view, bool show)
+Layer::showLayer(LayerGeometryProvider *view, bool show)
 {
     setLayerDormant(view, !show);
     emit layerParametersChanged();
 }
 
 bool
-Layer::getXScaleValue(const View *v, int x, float &value, QString &unit) const
+Layer::getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const
 {
     if (!hasTimeXAxis()) return false;
 
     const Model *m = getModel();
     if (!m) return false;
 
-    value = float(v->getFrameForX(x)) / m->getSampleRate();
+    value = double(v->getFrameForX(x)) / m->getSampleRate();
     unit = "s";
     return true;
 }
 
 bool
-Layer::getYScaleDifference(const View *v, int y0, int y1,
-                           float &diff, QString &unit) const
+Layer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
+                           double &diff, QString &unit) const
 {
-    float v0, v1;
+    double v0, v1;
     if (!getYScaleValue(v, y0, v0, unit) ||
         !getYScaleValue(v, y1, v1, unit)) {
         diff = 0.f;
         return false;
     }
-    diff = fabsf(v1 - v0);
+    diff = fabs(v1 - v0);
     return true;
 }
 
-size_t
-Layer::alignToReference(View *v, size_t frame) const
+sv_frame_t
+Layer::alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
     const Model *m = getModel();
     SVDEBUG << "Layer::alignToReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << endl;
     if (m && m->getAlignmentReference()) {
         return m->alignToReference(frame);
     } else {
-        return v->alignToReference(frame);
+        return v->getView()->alignToReference(frame);
     }
 }
 
-size_t
-Layer::alignFromReference(View *v, size_t frame) const
+sv_frame_t
+Layer::alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
     const Model *m = getModel();
     SVDEBUG << "Layer::alignFromReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << endl;
     if (m && m->getAlignmentReference()) {
         return m->alignFromReference(frame);
     } else {
-        return v->alignFromReference(frame);
+        return v->getView()->alignFromReference(frame);
     }
 }
 
 bool
-Layer::clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const
+Layer::clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const
 {
     // Notes on pasting to an aligned layer:
     // 
@@ -250,12 +250,12 @@
         // reference (i.e. having been copied from the reference
         // model).
         
-        long sourceFrame = i->getFrame();
-        long referenceFrame = sourceFrame;
+        sv_frame_t sourceFrame = i->getFrame();
+        sv_frame_t referenceFrame = sourceFrame;
         if (i->haveReferenceFrame()) {
             referenceFrame = i->getReferenceFrame();
         }
-        long myMappedFrame = alignToReference(v, sourceFrame);
+        sv_frame_t myMappedFrame = alignToReference(v, sourceFrame);
 
 //        cerr << "sourceFrame = " << sourceFrame << ", referenceFrame = " << referenceFrame << " (have = " << i->haveReferenceFrame() << "), myMappedFrame = " << myMappedFrame << endl;
 
@@ -320,8 +320,8 @@
     QString fs = attributes.value("startFrame");
     int x0 = 0, x1 = 0;
     if (fs != "") {
-        rect.startFrame = fs.toLong();
-        rect.endFrame = attributes.value("endFrame").toLong();
+        rect.startFrame = fs.toInt();
+        rect.endFrame = attributes.value("endFrame").toInt();
         rect.haveFrames = true;
     } else {
         x0 = attributes.value("startX").toInt();
@@ -371,7 +371,7 @@
 }
 
 void
-Layer::measureStart(View *v, QMouseEvent *e)
+Layer::measureStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     setMeasureRectFromPixrect(v, m_draggingRect,
                               QRect(e->x(), e->y(), 0, 0));
@@ -379,7 +379,7 @@
 }
 
 void
-Layer::measureDrag(View *v, QMouseEvent *e)
+Layer::measureDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_haveDraggingRect) return;
 
@@ -391,7 +391,7 @@
 }
 
 void
-Layer::measureEnd(View *v, QMouseEvent *e)
+Layer::measureEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_haveDraggingRect) return;
     measureDrag(v, e);
@@ -405,7 +405,7 @@
 }
 
 void
-Layer::measureDoubleClick(View *v, QMouseEvent *e)
+Layer::measureDoubleClick(LayerGeometryProvider *, QMouseEvent *)
 {
     // nothing, in the base class
 }
@@ -425,7 +425,7 @@
 }
 
 void
-Layer::paintMeasurementRects(View *v, QPainter &paint,
+Layer::paintMeasurementRects(LayerGeometryProvider *v, QPainter &paint,
                              bool showFocus, QPoint focusPoint) const
 {
     updateMeasurePixrects(v);
@@ -457,7 +457,7 @@
 }
 
 bool
-Layer::nearestMeasurementRectChanged(View *v, QPoint prev, QPoint now) const
+Layer::nearestMeasurementRectChanged(LayerGeometryProvider *v, QPoint prev, QPoint now) const
 {
     updateMeasurePixrects(v);
     
@@ -468,10 +468,10 @@
 }
 
 void
-Layer::updateMeasurePixrects(View *v) const
+Layer::updateMeasurePixrects(LayerGeometryProvider *v) const
 {
-    long sf = v->getStartFrame();
-    long ef = v->getEndFrame();
+    sv_frame_t sf = v->getStartFrame();
+    sv_frame_t ef = v->getEndFrame();
 
     for (MeasureRectSet::const_iterator i = m_measureRects.begin(); 
          i != m_measureRects.end(); ++i) {
@@ -495,7 +495,7 @@
             if (i->startFrame >= v->getStartFrame()) {
                 x0 = v->getXForFrame(i->startFrame);
             }
-            if (i->endFrame <= long(v->getEndFrame())) {
+            if (i->endFrame <= int(v->getEndFrame())) {
                 x1 = v->getXForFrame(i->endFrame);
             }
         }
@@ -507,26 +507,26 @@
 }
 
 void
-Layer::updateMeasureRectYCoords(View *v, const MeasureRect &r) const
+Layer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const
 {
-    int y0 = lrint(r.startY * v->height());
-    int y1 = lrint(r.endY * v->height());
+    int y0 = int(lrint(r.startY * v->getPaintHeight()));
+    int y1 = int(lrint(r.endY * v->getPaintHeight()));
     r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0);
 }
 
 void
-Layer::setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const
+Layer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const
 {
     if (start) {
-        r.startY = double(y) / double(v->height());
+        r.startY = double(y) / double(v->getPaintHeight());
         r.endY = r.startY;
     } else {
-        r.endY = double(y) / double(v->height());
+        r.endY = double(y) / double(v->getPaintHeight());
     }
 }
 
 void
-Layer::setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const
+Layer::setMeasureRectFromPixrect(LayerGeometryProvider *v, MeasureRect &r, QRect pixrect) const
 {
     r.pixrect = pixrect;
     r.haveFrames = hasTimeXAxis();
@@ -541,7 +541,7 @@
 Layer::MeasureRectSet::const_iterator
 Layer::findFocusedMeasureRect(QPoint focusPoint) const
 {
-    float frDist = 0;
+    double frDist = 0;
     MeasureRectSet::const_iterator focusRectItr = m_measureRects.end();
 
     for (MeasureRectSet::const_iterator i = m_measureRects.begin(); 
@@ -554,7 +554,7 @@
         int xd = focusPoint.x() - cx;
         int yd = focusPoint.y() - cy;
         
-        float d = sqrt(float(xd * xd + yd * yd));
+        double d = sqrt(double(xd * xd + yd * yd));
         
         if (focusRectItr == m_measureRects.end() || d < frDist) {
             focusRectItr = i;
@@ -566,18 +566,18 @@
 }
 
 void
-Layer::paintMeasurementRect(View *v, QPainter &paint,
+Layer::paintMeasurementRect(LayerGeometryProvider *v, QPainter &paint,
                             const MeasureRect &r, bool focus) const
 {
     if (r.haveFrames) {
         
         int x0 = -1;
-        int x1 = v->width() + 1;
+        int x1 = v->getPaintWidth() + 1;
         
         if (r.startFrame >= v->getStartFrame()) {
             x0 = v->getXForFrame(r.startFrame);
         }
-        if (r.endFrame <= long(v->getEndFrame())) {
+        if (r.endFrame <= v->getEndFrame()) {
             x1 = v->getXForFrame(r.endFrame);
         }
         
--- a/layer/Layer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/Layer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -39,6 +39,7 @@
 class Model;
 class QPainter;
 class View;
+class LayerGeometryProvider;
 class QMouseEvent;
 class Clipboard;
 class RangeMapper;
@@ -62,7 +63,7 @@
     Model *getModel() {
 	return const_cast<Model *>(const_cast<const Layer *>(this)->getModel());
     }
-
+    
     /**
      * Return a zoom constraint object defining the supported zoom
      * levels for this layer.  If this returns zero, the layer will
@@ -83,12 +84,13 @@
     /**
      * Paint the given rectangle of this layer onto the given view
      * using the given painter, superimposing it on top of any
-     * existing material in that view.  The view is provided here
-     * because it is possible for one layer to exist in more than one
-     * view, so the dimensions of the view may vary from one paint
-     * call to another (without any view having been resized).
+     * existing material in that view.  The LayerGeometryProvider (an
+     * interface implemented by View) is provided here because it is
+     * possible for one layer to exist in more than one view, so the
+     * dimensions of the view may vary from one paint call to another
+     * (without any view having been resized).
      */
-    virtual void paint(View *, QPainter &, QRect) const = 0;   
+    virtual void paint(LayerGeometryProvider *, QPainter &, QRect) const = 0;   
 
     /**
      * Enable or disable synchronous painting.  If synchronous
@@ -128,29 +130,29 @@
     virtual QString getLayerPresentationName() const;
     virtual QPixmap getLayerPresentationPixmap(QSize) const { return QPixmap(); }
 
-    virtual int getVerticalScaleWidth(View *, bool detailed,
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool detailed,
                                       QPainter &) const = 0;
 
-    virtual void paintVerticalScale(View *, bool detailed,
+    virtual void paintVerticalScale(LayerGeometryProvider *, bool /* detailed */,
                                     QPainter &, QRect) const { }
 
-    virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */,
+    virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint /* cursorPos */,
                                      std::vector<QRect> &) const {
         return false;
     }
-    virtual void paintCrosshairs(View *, QPainter &, QPoint) const { }
+    virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const { }
 
-    virtual void paintMeasurementRects(View *, QPainter &,
+    virtual void paintMeasurementRects(LayerGeometryProvider *, QPainter &,
                                        bool showFocus, QPoint focusPoint) const;
 
-    virtual bool nearestMeasurementRectChanged(View *, QPoint prev,
+    virtual bool nearestMeasurementRectChanged(LayerGeometryProvider *, QPoint prev,
                                                QPoint now) const;
 
-    virtual QString getFeatureDescription(View *, QPoint &) const {
+    virtual QString getFeatureDescription(LayerGeometryProvider *, QPoint &) const {
 	return "";
     }
 
-    virtual QString getLabelPreceding(size_t frame) const {
+    virtual QString getLabelPreceding(sv_frame_t /* frame */) const {
         return "";
     }
 
@@ -180,9 +182,9 @@
      * (and leave frame unmodified).  If returning true, also return
      * the resolution of the model in this layer in sample frames.
      */
-    virtual bool snapToFeatureFrame(View *   /* v */,
-				    int &    /* frame */,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider * /* v */,
+				    sv_frame_t & /* frame */,
+				    int &resolution,
 				    SnapType /* snap */) const {
 	resolution = 1;
 	return false;
@@ -204,9 +206,9 @@
      * (and leave frame unmodified).  If returning true, also return
      * the resolution of the model in this layer in sample frames.
      */
-    virtual bool snapToSimilarFeature(View *   /* v */,
-                                      int &    /* source frame */,
-                                      size_t &resolution,
+    virtual bool snapToSimilarFeature(LayerGeometryProvider * /* v */,
+                                      sv_frame_t & /* source frame */,
+                                      int &resolution,
                                       SnapType /* snap */) const {
 	resolution = 1;
 	return false;
@@ -217,25 +219,30 @@
     // Layer needs to get actual mouse events, I guess.  Draw mode is
     // probably the easier.
 
-    virtual void drawStart(View *, QMouseEvent *) { }
-    virtual void drawDrag(View *, QMouseEvent *) { }
-    virtual void drawEnd(View *, QMouseEvent *) { }
-    virtual void eraseStart(View *, QMouseEvent *) { }
-    virtual void eraseDrag(View *, QMouseEvent *) { }
-    virtual void eraseEnd(View *, QMouseEvent *) { }
+    virtual void drawStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void drawDrag(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void drawEnd(LayerGeometryProvider *, QMouseEvent *) { }
 
-    virtual void editStart(View *, QMouseEvent *) { }
-    virtual void editDrag(View *, QMouseEvent *) { }
-    virtual void editEnd(View *, QMouseEvent *) { }
+    virtual void eraseStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void eraseDrag(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void eraseEnd(LayerGeometryProvider *, QMouseEvent *) { }
+
+    virtual void editStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void editDrag(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void editEnd(LayerGeometryProvider *, QMouseEvent *) { }
+
+    virtual void splitStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void splitEnd(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void addNote(LayerGeometryProvider *, QMouseEvent *) { };
 
     // Measurement rectangle (or equivalent).  Unlike draw and edit,
     // the base Layer class can provide working implementations of
     // these for most situations.
     //
-    virtual void measureStart(View *, QMouseEvent *);
-    virtual void measureDrag(View *, QMouseEvent *);
-    virtual void measureEnd(View *, QMouseEvent *);
-    virtual void measureDoubleClick(View *, QMouseEvent *);
+    virtual void measureStart(LayerGeometryProvider *, QMouseEvent *);
+    virtual void measureDrag(LayerGeometryProvider *, QMouseEvent *);
+    virtual void measureEnd(LayerGeometryProvider *, QMouseEvent *);
+    virtual void measureDoubleClick(LayerGeometryProvider *, QMouseEvent *);
 
     virtual bool haveCurrentMeasureRect() const {
         return m_haveCurrentMeasureRect;
@@ -247,13 +254,13 @@
      * double-click).  If there is no item or editing is not
      * supported, return false.
      */
-    virtual bool editOpen(View *, QMouseEvent *) { return false; }
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *) { return false; }
 
-    virtual void moveSelection(Selection, size_t /* newStartFrame */) { }
+    virtual void moveSelection(Selection, sv_frame_t /* newStartFrame */) { }
     virtual void resizeSelection(Selection, Selection /* newSize */) { }
     virtual void deleteSelection(Selection) { }
 
-    virtual void copy(View *, Selection, Clipboard & /* to */) { }
+    virtual void copy(LayerGeometryProvider *, Selection, Clipboard & /* to */) { }
 
     /**
      * Paste from the given clipboard onto the layer at the given
@@ -262,9 +269,9 @@
      * return false if the user cancelled the paste operation.  This
      * function should return true if a paste actually occurred.
      */
-    virtual bool paste(View *,
+    virtual bool paste(LayerGeometryProvider *,
                        const Clipboard & /* from */,
-                       int /* frameOffset */,
+                       sv_frame_t /* frameOffset */,
                        bool /* interactive */) { return false; }
 
     // Text mode:
@@ -284,7 +291,7 @@
      * scrolling better if it is known that individual views can be
      * scrolled safely in this way.
      */
-    virtual bool isLayerScrollable(const View *) const { return true; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return true; }
 
     /**
      * This should return true if the layer completely obscures any
@@ -339,14 +346,14 @@
      * isReady(int *) call.  The view may choose to show a progress
      * meter if it finds that this returns < 100 at any given moment.
      */
-    virtual int getCompletion(View *) const { return 100; }
+    virtual int getCompletion(LayerGeometryProvider *) const { return 100; }
 
     /**
      * Return an error string if any errors have occurred while
      * loading or processing data for the given view.  Return the
      * empty string if no error has occurred.
      */
-    virtual QString getError(View *) const { return ""; }
+    virtual QString getError(LayerGeometryProvider *) const { return ""; }
 
     virtual void setObjectName(const QString &name);
 
@@ -395,13 +402,13 @@
      * A layer class that overrides this function must also call this
      * class's implementation.
      */
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
     /**
      * Return whether the layer is dormant (i.e. hidden) in the given
      * view.
      */
-    virtual bool isLayerDormant(const View *v) const;
+    virtual bool isLayerDormant(const LayerGeometryProvider *v) const;
 
     virtual PlayParameters *getPlayParameters();
 
@@ -418,7 +425,7 @@
      * This function returns the "normal" extents for the layer, not
      * necessarily the extents actually in use in the display.
      */
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const = 0;
 
     /**
@@ -429,8 +436,8 @@
      * extent (using the normal layer extents or deferring to whatever
      * is in use for the same units elsewhere in the view).
      */
-    virtual bool getDisplayExtents(float & /* min */,
-                                   float & /* max */) const {
+    virtual bool getDisplayExtents(double & /* min */,
+                                   double & /* max */) const {
         return false;
     }
 
@@ -441,8 +448,8 @@
      * return false for getDisplayExtents should also return false for
      * this function.
      */
-    virtual bool setDisplayExtents(float /* min */,
-                                   float /* max */) {
+    virtual bool setDisplayExtents(double /* min */,
+                                   double /* max */) {
         return false;
     }
 
@@ -452,15 +459,15 @@
      * measurement tool.  The default implementation works correctly
      * if the layer hasTimeXAxis().
      */
-    virtual bool getXScaleValue(const View *v, int x,
-                                float &value, QString &unit) const;
+    virtual bool getXScaleValue(const LayerGeometryProvider *v, int x,
+                                double &value, QString &unit) const;
 
     /** 
      * Return the value and unit at the given y coordinate in the
      * given view.
      */
-    virtual bool getYScaleValue(const View *, int /* y */,
-                                float &/* value */, QString &/* unit */) const {
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int /* y */,
+                                double &/* value */, QString &/* unit */) const {
         return false;
     }
 
@@ -470,8 +477,8 @@
      * The default implementation just calls getYScaleValue twice and
      * returns the difference, with the same unit.
      */
-    virtual bool getYScaleDifference(const View *v, int y0, int y1,
-                                     float &diff, QString &unit) const;
+    virtual bool getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
+                                     double &diff, QString &unit) const;
         
     /**
      * Get the number of vertical zoom steps available for this layer.
@@ -513,14 +520,21 @@
      */
     virtual RangeMapper *getNewVerticalZoomRangeMapper() const { return 0; }
 
+    /**
+     * Return true if this layer type can function without a model
+     * being set. If false (the default), the layer will not be loaded
+     * from a session if its model cannot be found.
+     */
+    virtual bool canExistWithoutModel() const { return false; }
+
 public slots:
-    void showLayer(View *, bool show);
+    void showLayer(LayerGeometryProvider *, bool show);
 
 signals:
     void modelChanged();
     void modelCompletionChanged();
     void modelAlignmentCompletionChanged();
-    void modelChanged(size_t startFrame, size_t endFrame);
+    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     void modelReplaced();
 
     void layerParametersChanged();
@@ -533,16 +547,16 @@
 protected:
     void connectSignals(const Model *);
 
-    virtual size_t alignToReference(View *v, size_t frame) const;
-    virtual size_t alignFromReference(View *v, size_t frame) const;
-    bool clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const;
+    virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const;
+    virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const;
+    bool clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const;
 
     struct MeasureRect {
 
         mutable QRect pixrect;
         bool haveFrames;
-        long startFrame; // only valid if haveFrames
-        long endFrame;   // ditto
+        sv_frame_t startFrame; // only valid if haveFrames
+        sv_frame_t endFrame;   // ditto
         double startY;
         double endY;
 
@@ -600,16 +614,16 @@
     // Note that pixrects are only correct for a single view.
     // So we should update them at the start of the paint procedure
     // (painting is single threaded) and only use them after that.
-    void updateMeasurePixrects(View *v) const;
+    void updateMeasurePixrects(LayerGeometryProvider *v) const;
 
-    virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const;
-    virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const;
-    virtual void setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const;
+    virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const;
+    virtual void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const;
+    virtual void setMeasureRectFromPixrect(LayerGeometryProvider *v, MeasureRect &r, QRect pixrect) const;
 
     // This assumes updateMeasurementPixrects has been called
     MeasureRectSet::const_iterator findFocusedMeasureRect(QPoint) const;
 
-    void paintMeasurementRect(View *v, QPainter &paint,
+    void paintMeasurementRect(LayerGeometryProvider *v, QPainter &paint,
                               const MeasureRect &r, bool focus) const;
 
     QString m_presentationName;
--- a/layer/LayerFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LayerFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,6 +21,7 @@
 #include "TimeInstantLayer.h"
 #include "TimeValueLayer.h"
 #include "NoteLayer.h"
+#include "FlexiNoteLayer.h"
 #include "RegionLayer.h"
 #include "TextLayer.h"
 #include "ImageLayer.h"
@@ -36,6 +37,7 @@
 #include "data/model/SparseOneDimensionalModel.h"
 #include "data/model/SparseTimeValueModel.h"
 #include "data/model/NoteModel.h"
+#include "data/model/FlexiNoteModel.h"
 #include "data/model/RegionModel.h"
 #include "data/model/TextModel.h"
 #include "data/model/ImageModel.h"
@@ -73,6 +75,7 @@
     case TimeInstants: return Layer::tr("Time Instants");
     case TimeValues:   return Layer::tr("Time Values");
     case Notes:        return Layer::tr("Notes");
+    case FlexiNotes:   return Layer::tr("Flexible Notes");
     case Regions:      return Layer::tr("Regions");
     case Text:         return Layer::tr("Text");
     case Image:        return Layer::tr("Images");
@@ -90,10 +93,11 @@
 	// likewise
 	return Layer::tr("Spectrogram");
 
-    default: break;
+    case UnknownLayer:
+    default:
+        cerr << "WARNING: LayerFactory::getLayerPresentationName passed unknown layer" << endl;
+        return Layer::tr("Unknown Layer");
     }
-
-    return Layer::tr("Layer");
 }
 
 bool
@@ -161,6 +165,11 @@
 	types.insert(Notes);
     }
 
+    // NOTE: GF: types is a set, so order of insertion does not matter
+    if (dynamic_cast<FlexiNoteModel *>(model)) {
+	types.insert(FlexiNotes);
+    }
+
     if (dynamic_cast<RegionModel *>(model)) {
 	types.insert(Regions);
     }
@@ -189,6 +198,10 @@
     LayerTypeSet types;
     types.insert(TimeInstants);
     types.insert(TimeValues);
+    // Because this is strictly a UI function -- list the layer types
+    // to show in a menu -- it should not contain FlexiNotes; the
+    // layer isn't meaningfully editable in SV
+//    types.insert(FlexiNotes);
     types.insert(Notes);
     types.insert(Regions);
     types.insert(Text);
@@ -205,6 +218,7 @@
     if (dynamic_cast<const TimeRulerLayer *>(layer)) return TimeRuler;
     if (dynamic_cast<const TimeInstantLayer *>(layer)) return TimeInstants;
     if (dynamic_cast<const TimeValueLayer *>(layer)) return TimeValues;
+    if (dynamic_cast<const FlexiNoteLayer *>(layer)) return FlexiNotes;
     if (dynamic_cast<const NoteLayer *>(layer)) return Notes;
     if (dynamic_cast<const RegionLayer *>(layer)) return Regions;
     if (dynamic_cast<const TextLayer *>(layer)) return Text;
@@ -225,6 +239,7 @@
     case TimeInstants: return "instants";
     case TimeValues: return "values";
     case Notes: return "notes";
+    case FlexiNotes: return "flexinotes";
     case Regions: return "regions";
     case Text: return "text";
     case Image: return "image";
@@ -233,7 +248,10 @@
     case Slice: return "spectrum";
     case MelodicRangeSpectrogram: return "spectrogram";
     case PeakFrequencySpectrogram: return "spectrogram";
-    default: return "unknown";
+    case UnknownLayer:
+    default:
+        cerr << "WARNING: LayerFactory::getLayerIconName passed unknown layer" << endl;
+        return "unknown";
     }
 }
 
@@ -247,6 +265,7 @@
     case TimeInstants: return "timeinstants";
     case TimeValues: return "timevalues";
     case Notes: return "notes";
+    case FlexiNotes: return "flexinotes";
     case Regions: return "regions";
     case Text: return "text";
     case Image: return "image";
@@ -255,7 +274,10 @@
     case Slice: return "slice";
     case MelodicRangeSpectrogram: return "melodicrange";
     case PeakFrequencySpectrogram: return "peakfrequency";
-    default: return "unknown";
+    case UnknownLayer:
+    default:
+        cerr << "WARNING: LayerFactory::getLayerTypeName passed unknown layer" << endl;
+        return "unknown";
     }
 }
 
@@ -268,6 +290,7 @@
     if (name == "timeinstants") return TimeInstants;
     if (name == "timevalues") return TimeValues;
     if (name == "notes") return Notes;
+    if (name == "flexinotes") return FlexiNotes;
     if (name == "regions") return Regions;
     if (name == "text") return Text;
     if (name == "image") return Image;
@@ -282,7 +305,7 @@
 {
 //    if (trySetModel<WaveformLayer, RangeSummarisableTimeValueModel>(layer, model))
 //	return;
-
+	
     if (trySetModel<WaveformLayer, WaveFileModel>(layer, model))
 	return;
 
@@ -301,9 +324,13 @@
     if (trySetModel<TimeValueLayer, SparseTimeValueModel>(layer, model))
 	return;
 
-    if (trySetModel<NoteLayer, NoteModel>(layer, model))
-	return;
+    if (trySetModel<NoteLayer, NoteModel>(layer, model)) 
+	return; 
 
+    // GF: added FlexiNoteLayer
+    if (trySetModel<FlexiNoteLayer, FlexiNoteModel>(layer, model)) 
+	return; 
+	
     if (trySetModel<RegionLayer, RegionModel>(layer, model))
 	return;
 
@@ -333,6 +360,8 @@
 	return new SparseOneDimensionalModel(baseModel->getSampleRate(), 1);
     } else if (layerType == TimeValues) {
 	return new SparseTimeValueModel(baseModel->getSampleRate(), 1, true);
+    } else if (layerType == FlexiNotes) {
+	return new FlexiNoteModel(baseModel->getSampleRate(), 1, true);
     } else if (layerType == Notes) {
 	return new NoteModel(baseModel->getSampleRate(), 1, true);
     } else if (layerType == Regions) {
@@ -402,6 +431,10 @@
 	layer = new TimeValueLayer;
 	break;
 
+    case FlexiNotes:
+	layer = new FlexiNoteLayer;
+	break;
+
     case Notes:
 	layer = new NoteLayer;
 	break;
@@ -438,11 +471,14 @@
 	layer = new SpectrogramLayer(SpectrogramLayer::MelodicPeaks);
 	break;
 
-    default: break;
+    case UnknownLayer:
+    default:
+        cerr << "WARNING: LayerFactory::createLayer passed unknown layer" << endl;
+        break;
     }
 
     if (!layer) {
-	SVDEBUG << "LayerFactory::createLayer: Unknown layer type " 
+	cerr << "LayerFactory::createLayer: Unknown layer type " 
 		  << type << endl;
     } else {
 //	SVDEBUG << "LayerFactory::createLayer: Setting object name "
@@ -477,7 +513,7 @@
         QDomElement layerElt = docNew.firstChildElement("layer");
         QDomNamedNodeMap attrNodes = layerElt.attributes();
         
-        for (unsigned int i = 0; i < attrNodes.length(); ++i) {
+        for (int i = 0; i < attrNodes.length(); ++i) {
             QDomAttr attr = attrNodes.item(i).toAttr();
             if (attr.isNull()) continue;
 //            cerr << "append \"" << attr.name()
@@ -488,7 +524,7 @@
         
         layerElt = docOld.firstChildElement("layer");
         attrNodes = layerElt.attributes();
-        for (unsigned int i = 0; i < attrNodes.length(); ++i) {
+        for (int i = 0; i < attrNodes.length(); ++i) {
             QDomAttr attr = attrNodes.item(i).toAttr();
             if (attr.isNull()) continue;
             if (attrs.value(attr.name()) == "") {
--- a/layer/LayerFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LayerFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -35,6 +35,7 @@
 	TimeInstants,
 	TimeValues,
 	Notes,
+	FlexiNotes,
 	Regions,
 	Text,
         Image,
@@ -56,6 +57,11 @@
 
     typedef std::set<LayerType> LayerTypeSet;
     LayerTypeSet getValidLayerTypes(Model *model);
+
+    /**
+     * Return the set of layer types that an end user should be
+     * allowed to create, empty, for subsequent editing.
+     */
     LayerTypeSet getValidEmptyLayerTypes();
 
     LayerType getLayerType(const Layer *);
--- a/layer/LinearColourScale.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LinearColourScale.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -20,33 +20,32 @@
 
 #include <cmath>
 
-#include "view/View.h"
+#include "view/LayerGeometryProvider.h"
 
 int
-LinearColourScale::getWidth(View *v,
+LinearColourScale::getWidth(LayerGeometryProvider *,
 			    QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 15;
 }
 
 void
-LinearColourScale::paintVertical(View *v,
+LinearColourScale::paintVertical(LayerGeometryProvider *v,
 				 const ColourScaleLayer *layer,
 				 QPainter &paint,
-				 int x0,
-				 float min,
-				 float max)
+				 int /* x0 */,
+				 double min,
+				 double max)
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     int n = 10;
 
-    float val = min;
-    float inc = (max - val) / n;
-
-    char buffer[40];
-
-    int w = getWidth(v, paint) + x0;
+    double val = min;
+    double inc = (max - val) / n;
+    
+    const int buflen = 40;
+    char buffer[buflen];
 
     int boxx = 5, boxy = 5;
     if (layer->getScaleUnits() != "") {
@@ -59,22 +58,22 @@
 
     paint.save();
     for (int y = 0; y < boxh; ++y) {
-	float val = ((boxh - y) * (max - min)) / boxh + min;
+	double val = ((boxh - y) * (max - min)) / boxh + min;
 	paint.setPen(layer->getColourForValue(v, val));
 	paint.drawLine(boxx + 1, y + boxy + 1, boxx + boxw, y + boxy + 1);
     }
     paint.restore();
 
-    float round = 1.f;
+//    double round = 1.f;
     int dp = 0;
     if (inc > 0) {
-        int prec = trunc(log10f(inc));
+        int prec = int(trunc(log10(inc)));
         prec -= 1;
         if (prec < 0) dp = -prec;
-        round = powf(10.f, prec);
-#ifdef DEBUG_TIME_VALUE_LAYER
-        cerr << "inc = " << inc << ", round = " << round << ", dp = " << dp << endl;
-#endif
+//        round = powf(10.f, prec);
+//#ifdef DEBUG_TIME_VALUE_LAYER
+//        cerr << "inc = " << inc << ", round = " << round << ", dp = " << dp << endl;
+//#endif
     }
 
     for (int i = 0; i < n; ++i) {
@@ -86,7 +85,7 @@
 	ty = y - paint.fontMetrics().height() +
 	    paint.fontMetrics().ascent() + 2;
 
-	sprintf(buffer, "%.*f", dp, val);
+	snprintf(buffer, buflen, "%.*f", dp, val);
 	QString label = QString(buffer);
 
 	paint.drawLine(boxx + boxw - boxw/3, y, boxx + boxw, y);
--- a/layer/LinearColourScale.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LinearColourScale.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,17 +19,17 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class ColourScaleLayer;
 
 class LinearColourScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
-     float minf, float maxf);
+    (LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
+     double minf, double maxf);
 };
 
 #endif
--- a/layer/LinearNumericalScale.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LinearNumericalScale.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -23,38 +23,37 @@
 #include "view/View.h"
 
 int
-LinearNumericalScale::getWidth(View *v,
+LinearNumericalScale::getWidth(LayerGeometryProvider *,
 			       QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 10;
 }
 
 void
-LinearNumericalScale::paintVertical(View *v,
+LinearNumericalScale::paintVertical(LayerGeometryProvider *v,
 				    const VerticalScaleLayer *layer,
 				    QPainter &paint,
 				    int x0,
-				    float minf,
-				    float maxf)
+				    double minf,
+				    double maxf)
 {
-    int h = v->height();
-
     int n = 10;
 
-    float val = minf;
-    float inc = (maxf - val) / n;
+    double val = minf;
+    double inc = (maxf - val) / n;
 
-    char buffer[40];
+    const int buflen = 40;
+    char buffer[buflen];
 
     int w = getWidth(v, paint) + x0;
 
-    float round = 1.f;
+    double round = 1.0;
     int dp = 0;
     if (inc > 0) {
-        int prec = trunc(log10f(inc));
+        int prec = int(trunc(log10(inc)));
         prec -= 1;
         if (prec < 0) dp = -prec;
-        round = powf(10.f, prec);
+        round = pow(10.0, prec);
 #ifdef DEBUG_TIME_VALUE_LAYER
         cerr << "inc = " << inc << ", round = " << round << ", dp = " << dp << endl;
 #endif
@@ -67,13 +66,13 @@
 	int y, ty;
         bool drawText = true;
 
-        float dispval = val;
+        double dispval = val;
 
 	if (i == n-1 &&
-	    v->height() < paint.fontMetrics().height() * (n*2)) {
+	    v->getPaintHeight() < paint.fontMetrics().height() * (n*2)) {
 	    if (layer->getScaleUnits() != "") drawText = false;
 	}
-	dispval = lrintf(val / round) * round;
+	dispval = int(rint(val / round) * round);
 
 #ifdef DEBUG_TIME_VALUE_LAYER
 	cerr << "val = " << val << ", dispval = " << dispval << endl;
@@ -88,7 +87,7 @@
 	    continue;
         }
 
-	sprintf(buffer, "%.*f", dp, dispval);
+	snprintf(buffer, buflen, "%.*f", dp, dispval);
 
 	QString label = QString(buffer);
 
--- a/layer/LinearNumericalScale.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LinearNumericalScale.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,17 +19,17 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class VerticalScaleLayer;
 
 class LinearNumericalScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const VerticalScaleLayer *layer, QPainter &paint, int x0,
-     float minf, float maxf);
+    (LayerGeometryProvider *v, const VerticalScaleLayer *layer,
+     QPainter &paint, int x0, double minf, double maxf);
 };
 
 #endif
--- a/layer/LogColourScale.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LogColourScale.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -25,30 +25,29 @@
 #include "view/View.h"
 
 int
-LogColourScale::getWidth(View *v,
+LogColourScale::getWidth(LayerGeometryProvider *,
 			    QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 15;
 }
 
 void
-LogColourScale::paintVertical(View *v,
+LogColourScale::paintVertical(LayerGeometryProvider *v,
 			      const ColourScaleLayer *layer,
 			      QPainter &paint,
-			      int x0,
-			      float minlog,
-			      float maxlog)
+			      int /* x0 */,
+			      double minlog,
+			      double maxlog)
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     int n = 10;
 
-    float val = minlog;
-    float inc = (maxlog - val) / n;
+    double val = minlog;
+    double inc = (maxlog - val) / n;
 
-    char buffer[40];
-
-    int w = getWidth(v, paint) + x0;
+    const int buflen = 40;
+    char buffer[buflen];
 
     int boxx = 5, boxy = 5;
     if (layer->getScaleUnits() != "") {
@@ -61,7 +60,7 @@
 
     paint.save();
     for (int y = 0; y < boxh; ++y) {
-	float val = ((boxh - y) * (maxlog - minlog)) / boxh + minlog;
+	double val = ((boxh - y) * (maxlog - minlog)) / boxh + minlog;
 	paint.setPen(layer->getColourForValue(v, LogRange::unmap(val)));
 	paint.drawLine(boxx + 1, y + boxy + 1, boxx + boxw, y + boxy + 1);
     }
@@ -69,7 +68,7 @@
 
     int dp = 0;
     if (inc > 0) {
-        int prec = trunc(log10f(inc));
+        int prec = int(trunc(log10(inc)));
         prec -= 1;
         if (prec < 0) dp = -prec;
     }
@@ -84,10 +83,10 @@
 	    paint.fontMetrics().ascent() + 2;
 
 	double dv = LogRange::unmap(val);
-	int digits = trunc(log10f(dv));
+	int digits = int(trunc(log10(dv)));
 	int sf = dp + (digits > 0 ? digits : 0);
 	if (sf < 2) sf = 2;
-	sprintf(buffer, "%.*g", sf, dv);
+	snprintf(buffer, buflen, "%.*g", sf, dv);
 
 	QString label = QString(buffer);
 
--- a/layer/LogColourScale.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LogColourScale.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,17 +19,17 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class ColourScaleLayer;
 
 class LogColourScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
-     float minf, float maxf);
+    (LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
+     double minf, double maxf);
 };
 
 #endif
--- a/layer/LogNumericalScale.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LogNumericalScale.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -27,43 +27,44 @@
 //#define DEBUG_TIME_VALUE_LAYER 1
 
 int
-LogNumericalScale::getWidth(View *,
+LogNumericalScale::getWidth(LayerGeometryProvider *,
 			    QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 10;
 }
 
 void
-LogNumericalScale::paintVertical(View *v,
+LogNumericalScale::paintVertical(LayerGeometryProvider *v,
 				 const VerticalScaleLayer *layer,
 				 QPainter &paint,
 				 int x0,
-				 float minlog,
-				 float maxlog)
+				 double minlog,
+				 double maxlog)
 {
     int w = getWidth(v, paint) + x0;
 
     int n = 10;
 
-    float val = minlog;
-    float inc = (maxlog - val) / n; // even increments of log scale
+    double val = minlog;
+    double inc = (maxlog - val) / n; // even increments of log scale
 
     // smallest increment as displayed
-    float minDispInc = LogRange::unmap(minlog + inc) - LogRange::unmap(minlog);
+    double minDispInc = LogRange::unmap(minlog + inc) - LogRange::unmap(minlog);
 
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "min = " << minlog << ", max = " << maxlog << ", inc = " << inc << ", minDispInc = " << minDispInc << endl;
 #endif
 
-    char buffer[40];
+    const int buflen = 40;
+    char buffer[buflen];
 
-    float round = 1.f;
+    double round = 1.f;
     int dp = 0;
 
     if (minDispInc > 0) {
-        int prec = trunc(log10f(minDispInc));
+        int prec = int(trunc(log10(minDispInc)));
         if (prec < 0) dp = -prec;
-        round = powf(10.f, prec);
+        round = pow(10.0, prec);
         if (dp > 4) dp = 4;
 #ifdef DEBUG_TIME_VALUE_LAYER
         cerr << "round = " << round << ", prec = " << prec << ", dp = " << dp << endl;
@@ -78,11 +79,11 @@
         bool drawText = true;
 
 	if (i == n-1 &&
-	    v->height() < paint.fontMetrics().height() * (n*2)) {
+	    v->getPaintHeight() < paint.fontMetrics().height() * (n*2)) {
 	    if (layer->getScaleUnits() != "") drawText = false;
 	}
 
-        float dispval = LogRange::unmap(val);
+        double dispval = LogRange::unmap(val);
 	dispval = floor(dispval / round) * round;
 
 #ifdef DEBUG_TIME_VALUE_LAYER
@@ -98,13 +99,13 @@
 	    continue;
         }
 
-	int digits = trunc(log10f(dispval));
+	int digits = int(trunc(log10(dispval)));
 	int sf = dp + (digits > 0 ? digits : 0);
 	if (sf < 4) sf = 4;
 #ifdef DEBUG_TIME_VALUE_LAYER
         cerr << "sf = " << sf << endl;
 #endif
-	sprintf(buffer, "%.*g", sf, dispval);
+	snprintf(buffer, buflen, "%.*g", sf, dispval);
 
 	QString label = QString(buffer);
 
--- a/layer/LogNumericalScale.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/LogNumericalScale.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,17 +19,17 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class VerticalScaleLayer;
 
 class LogNumericalScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const VerticalScaleLayer *layer, QPainter &paint, int x0,
-     float minlog, float maxlog);
+    (LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0,
+     double minlog, double maxlog);
 };
 
 #endif
--- a/layer/NoteLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/NoteLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -49,6 +49,10 @@
     SingleColourLayer(),
     m_model(0),
     m_editing(false),
+    m_dragPointX(0),
+    m_dragPointY(0),
+    m_dragStartX(0),
+    m_dragStartY(0),
     m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
     m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
     m_editingCommand(0),
@@ -56,12 +60,12 @@
     m_scaleMinimum(0),
     m_scaleMaximum(0)
 {
-    
+  	SVDEBUG << "constructed NoteLayer" << endl;
 }
 
 void
 NoteLayer::setModel(NoteModel *model)
-{
+{	
     if (m_model == model) return;
     m_model = model;
 
@@ -187,7 +191,7 @@
 }
 
 bool
-NoteLayer::isLayerScrollable(const View *v) const
+NoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
@@ -205,7 +209,7 @@
 }
 
 bool
-NoteLayer::getValueExtents(float &min, float &max,
+NoteLayer::getValueExtents(double &min, double &max,
                            bool &logarithmic, QString &unit) const
 {
     if (!m_model) return false;
@@ -214,8 +218,8 @@
 
     if (shouldConvertMIDIToHz()) {
         unit = "Hz";
-        min = Pitch::getFrequencyForPitch(lrintf(min));
-        max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+        min = Pitch::getFrequencyForPitch(int(lrint(min)));
+        max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
     } else unit = getScaleUnits();
 
     if (m_verticalScale == MIDIRangeScale ||
@@ -225,7 +229,7 @@
 }
 
 bool
-NoteLayer::getDisplayExtents(float &min, float &max) const
+NoteLayer::getDisplayExtents(double &min, double &max) const
 {
     if (!m_model || shouldAutoAlign()) return false;
 
@@ -244,8 +248,8 @@
     }
 
     if (shouldConvertMIDIToHz()) {
-        min = Pitch::getFrequencyForPitch(lrintf(min));
-        max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+        min = Pitch::getFrequencyForPitch(int(lrint(min)));
+        max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
     }
 
 #ifdef DEBUG_NOTE_LAYER
@@ -256,7 +260,7 @@
 }
 
 bool
-NoteLayer::setDisplayExtents(float min, float max)
+NoteLayer::setDisplayExtents(double min, double max)
 {
     if (!m_model) return false;
 
@@ -298,7 +302,7 @@
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return 0;
 
-    float dmin, dmax;
+    double dmin, dmax;
     getDisplayExtents(dmin, dmax);
 
     int nr = mapper->getPositionForValue(dmax - dmin);
@@ -319,29 +323,29 @@
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return;
     
-    float min, max;
+    double min, max;
     bool logarithmic;
     QString unit;
     getValueExtents(min, max, logarithmic, unit);
     
-    float dmin, dmax;
+    double dmin, dmax;
     getDisplayExtents(dmin, dmax);
 
-    float newdist = mapper->getValueForPosition(100 - step);
+    double newdist = mapper->getValueForPosition(100 - step);
 
-    float newmin, newmax;
+    double newmin, newmax;
 
     if (logarithmic) {
 
         // see SpectrogramLayer::setVerticalZoomStep
 
-        newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2;
+        newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
         newmin = newmax - newdist;
 
 //        cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
 
     } else {
-        float dmid = (dmax + dmin) / 2;
+        double dmid = (dmax + dmin) / 2;
         newmin = dmid - newdist / 2;
         newmax = dmid + newdist / 2;
     }
@@ -368,7 +372,7 @@
     
     RangeMapper *mapper;
 
-    float min, max;
+    double min, max;
     bool logarithmic;
     QString unit;
     getValueExtents(min, max, logarithmic, unit);
@@ -385,11 +389,11 @@
 }
 
 NoteModel::PointList
-NoteLayer::getLocalPoints(View *v, int x) const
+NoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     if (!m_model) return NoteModel::PointList();
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     NoteModel::PointList onPoints =
 	m_model->getPoints(frame);
@@ -407,11 +411,11 @@
 
     if (prevPoints.empty()) {
 	usePoints = nextPoints;
-    } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
+    } else if (int(prevPoints.begin()->frame) < v->getStartFrame() &&
 	       !(nextPoints.begin()->frame > v->getEndFrame())) {
 	usePoints = nextPoints;
-    } else if (long(nextPoints.begin()->frame) - frame <
-	       frame - long(prevPoints.begin()->frame)) {
+    } else if (int(nextPoints.begin()->frame) - frame <
+	       frame - int(prevPoints.begin()->frame)) {
 	usePoints = nextPoints;
     }
 
@@ -428,11 +432,11 @@
 }
 
 bool
-NoteLayer::getPointToDrag(View *v, int x, int y, NoteModel::Point &p) const
+NoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, NoteModel::Point &p) const
 {
     if (!m_model) return false;
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     NoteModel::PointList onPoints = m_model->getPoints(frame);
     if (onPoints.empty()) return false;
@@ -456,7 +460,7 @@
 }
 
 QString
-NoteLayer::getFeatureDescription(View *v, QPoint &pos) const
+NoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -502,9 +506,9 @@
 
     if (shouldConvertMIDIToHz()) {
 
-        int mnote = lrintf(note.value);
-        int cents = lrintf((note.value - mnote) * 100);
-        float freq = Pitch::getFrequencyForPitch(mnote, cents);
+        int mnote = int(lrint(note.value));
+        int cents = int(lrint((note.value - float(mnote)) * 100));
+        double freq = Pitch::getFrequencyForPitch(mnote, cents);
         pitchText = tr("%1 (%2, %3 Hz)")
             .arg(Pitch::getPitchLabel(mnote, cents))
             .arg(mnote)
@@ -543,8 +547,8 @@
 }
 
 bool
-NoteLayer::snapToFeatureFrame(View *v, int &frame,
-			      size_t &resolution,
+NoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+			      int &resolution,
 			      SnapType snap) const
 {
     if (!m_model) {
@@ -563,7 +567,7 @@
     }    
 
     points = m_model->getPoints(frame, frame);
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
 
     for (NoteModel::PointList::const_iterator i = points.begin();
@@ -615,7 +619,7 @@
 }
 
 void
-NoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
+NoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
 {
     min = 0.0;
     max = 0.0;
@@ -633,8 +637,8 @@
             max = m_model->getValueMaximum();
 
             if (shouldConvertMIDIToHz()) {
-                min = Pitch::getFrequencyForPitch(lrintf(min));
-                max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+                min = Pitch::getFrequencyForPitch(int(lrint(min)));
+                max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
             }
 
 #ifdef DEBUG_NOTE_LAYER
@@ -659,8 +663,8 @@
             min = Pitch::getFrequencyForPitch(0);
             max = Pitch::getFrequencyForPitch(127);
         } else if (shouldConvertMIDIToHz()) {
-            min = Pitch::getFrequencyForPitch(lrintf(min));
-            max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+            min = Pitch::getFrequencyForPitch(int(lrint(min)));
+            max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
         }
 
         if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
@@ -673,11 +677,11 @@
 }
 
 int
-NoteLayer::getYForValue(View *v, float val) const
+NoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
 {
-    float min = 0.0, max = 0.0;
+    double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -686,8 +690,8 @@
 #endif
 
     if (shouldConvertMIDIToHz()) {
-        val = Pitch::getFrequencyForPitch(lrintf(val),
-                                          lrintf((val - lrintf(val)) * 100));
+        val = Pitch::getFrequencyForPitch(int(lrint(val)),
+                                          int(lrint((val - rint(val)) * 100)));
 #ifdef DEBUG_NOTE_LAYER
         cerr << "shouldConvertMIDIToHz true, val now = " << val << endl;
 #endif
@@ -707,19 +711,19 @@
     return y;
 }
 
-float
-NoteLayer::getValueForY(View *v, int y) const
+double
+NoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
 {
-    float min = 0.0, max = 0.0;
+    double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
-    float val = min + (float(h - y) * float(max - min)) / h;
+    double val = min + (double(h - y) * double(max - min)) / h;
 
     if (logarithmic) {
-        val = powf(10.f, val);
+        val = pow(10.0, val);
     }
 
     if (shouldConvertMIDIToHz()) {
@@ -737,18 +741,18 @@
 }
 
 void
-NoteLayer::paint(View *v, QPainter &paint, QRect rect) const
+NoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
-    int sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("NoteLayer::paint", true);
 
     int x0 = rect.left(), x1 = rect.right();
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1);
+    sv_frame_t frame0 = v->getFrameForX(x0);
+    sv_frame_t frame1 = v->getFrameForX(x1);
 
     NoteModel::PointList points(m_model->getPoints(frame0, frame1));
     if (points.empty()) return;
@@ -761,8 +765,8 @@
 //    SVDEBUG << "NoteLayer::paint: resolution is "
 //	      << m_model->getResolution() << " frames" << endl;
 
-    float min = m_model->getValueMinimum();
-    float max = m_model->getValueMaximum();
+    double min = m_model->getValueMinimum();
+    double max = m_model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
     QPoint localPos;
@@ -826,7 +830,7 @@
 }
 
 int
-NoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
     if (!m_model || shouldAutoAlign()) {
         return 0;
@@ -840,16 +844,16 @@
 }
 
 void
-NoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+NoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model) return;
+    if (!m_model || m_model->getPoints().empty()) return;
 
     QString unit;
-    float min, max;
+    double min, max;
     bool logarithmic;
 
     int w = getVerticalScaleWidth(v, false, paint);
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -878,19 +882,19 @@
 }
 
 void
-NoteLayer::drawStart(View *v, QMouseEvent *e)
+NoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
     if (!m_model) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float value = getValueForY(v, e->y());
+    double value = getValueForY(v, e->y());
 
-    m_editingPoint = NoteModel::Point(frame, value, 0, 0.8, tr("New Point"));
+    m_editingPoint = NoteModel::Point(frame, float(value), 0, 0.8f, tr("New Point"));
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
@@ -902,20 +906,20 @@
 }
 
 void
-NoteLayer::drawDrag(View *v, QMouseEvent *e)
+NoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float newValue = getValueForY(v, e->y());
+    double newValue = getValueForY(v, e->y());
 
-    long newFrame = m_editingPoint.frame;
-    long newDuration = frame - newFrame;
+    sv_frame_t newFrame = m_editingPoint.frame;
+    sv_frame_t newDuration = frame - newFrame;
     if (newDuration < 0) {
         newFrame = frame;
         newDuration = -newDuration;
@@ -925,13 +929,13 @@
 
     m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = newFrame;
-    m_editingPoint.value = newValue;
+    m_editingPoint.value = float(newValue);
     m_editingPoint.duration = newDuration;
     m_editingCommand->addPoint(m_editingPoint);
 }
 
 void
-NoteLayer::drawEnd(View *, QMouseEvent *)
+NoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -941,7 +945,7 @@
 }
 
 void
-NoteLayer::eraseStart(View *v, QMouseEvent *e)
+NoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -956,12 +960,12 @@
 }
 
 void
-NoteLayer::eraseDrag(View *v, QMouseEvent *e)
+NoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-NoteLayer::eraseEnd(View *v, QMouseEvent *e)
+NoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -981,7 +985,7 @@
 }
 
 void
-NoteLayer::editStart(View *v, QMouseEvent *e)
+NoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -1004,7 +1008,7 @@
 }
 
 void
-NoteLayer::editDrag(View *v, QMouseEvent *e)
+NoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -1015,11 +1019,11 @@
     int newx = m_dragPointX + xdist;
     int newy = m_dragPointY + ydist;
 
-    long frame = v->getFrameForX(newx);
+    sv_frame_t frame = v->getFrameForX(newx);
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float value = getValueForY(v, newy);
+    double value = getValueForY(v, newy);
 
     if (!m_editingCommand) {
 	m_editingCommand = new NoteModel::EditCommand(m_model,
@@ -1028,12 +1032,12 @@
 
     m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = frame;
-    m_editingPoint.value = value;
+    m_editingPoint.value = float(value);
     m_editingCommand->addPoint(m_editingPoint);
 }
 
 void
-NoteLayer::editEnd(View *, QMouseEvent *)
+NoteLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -1061,7 +1065,7 @@
 }
 
 bool
-NoteLayer::editOpen(View *v, QMouseEvent *e)
+NoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -1103,7 +1107,7 @@
 }
 
 void
-NoteLayer::moveSelection(Selection s, size_t newStartFrame)
+NoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
     if (!m_model) return;
 
@@ -1147,13 +1151,13 @@
 
 	if (s.contains(i->frame)) {
 
-	    double targetStart = i->frame;
-	    targetStart = newSize.getStartFrame() + 
-		double(targetStart - s.getStartFrame()) * ratio;
+	    double targetStart = double(i->frame);
+	    targetStart = double(newSize.getStartFrame()) +
+		targetStart - double(s.getStartFrame()) * ratio;
 
-	    double targetEnd = i->frame + i->duration;
-	    targetEnd = newSize.getStartFrame() +
-		double(targetEnd - s.getStartFrame()) * ratio;
+	    double targetEnd = double(i->frame + i->duration);
+	    targetEnd = double(newSize.getStartFrame()) +
+		targetEnd - double(s.getStartFrame()) * ratio;
 
 	    NoteModel::Point newPoint(*i);
 	    newPoint.frame = lrint(targetStart);
@@ -1189,7 +1193,7 @@
 }    
 
 void
-NoteLayer::copy(View *v, Selection s, Clipboard &to)
+NoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1207,7 +1211,7 @@
 }
 
 bool
-NoteLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
+NoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -1218,7 +1222,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
                                   tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
                                   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
                                   QMessageBox::Yes);
@@ -1239,7 +1243,7 @@
          i != points.end(); ++i) {
         
         if (!i->haveFrame()) continue;
-        size_t frame = 0;
+        sv_frame_t frame = 0;
 
         if (!realign) {
             
@@ -1264,7 +1268,7 @@
         if (i->haveLevel()) newPoint.level = i->getLevel();
         if (i->haveDuration()) newPoint.duration = i->getDuration();
         else {
-            size_t nextFrame = frame;
+            sv_frame_t nextFrame = frame;
             Clipboard::PointList::const_iterator j = i;
             for (; j != points.end(); ++j) {
                 if (!j->haveFrame()) continue;
@@ -1288,13 +1292,13 @@
 }
 
 void
-NoteLayer::addNoteOn(long frame, int pitch, int velocity)
+NoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
 {
-    m_pendingNoteOns.insert(Note(frame, pitch, 0, float(velocity) / 127.0, ""));
+    m_pendingNoteOns.insert(Note(frame, float(pitch), 0, float(velocity) / 127.f, ""));
 }
 
 void
-NoteLayer::addNoteOff(long frame, int pitch)
+NoteLayer::addNoteOff(sv_frame_t frame, int pitch)
 {
     for (NoteSet::iterator i = m_pendingNoteOns.begin();
          i != m_pendingNoteOns.end(); ++i) {
--- a/layer/NoteLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/NoteLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -35,37 +35,37 @@
 public:
     NoteLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *v, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void moveSelection(Selection s, size_t newStartFrame);
+    virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -91,17 +91,17 @@
     void setVerticalScale(VerticalScale scale);
     VerticalScale getVerticalScale() const { return m_verticalScale; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const;
 
-    virtual bool getDisplayExtents(float &min, float &max) const;
-    virtual bool setDisplayExtents(float min, float max);
+    virtual bool getDisplayExtents(double &min, double &max) const;
+    virtual bool setDisplayExtents(double min, double max);
 
     virtual int getVerticalZoomSteps(int &defaultStep) const;
     virtual int getCurrentVerticalZoomStep() const;
@@ -113,13 +113,13 @@
      * not be finally added to the layer until the corresponding
      * note-off.
      */
-    void addNoteOn(long frame, int pitch, int velocity);
+    void addNoteOn(sv_frame_t frame, int pitch, int velocity);
     
     /**
      * Add a note-off.  This will cause a note to appear, if and only
      * if there is a matching pending note-on.
      */
-    void addNoteOff(long frame, int pitch);
+    void addNoteOff(sv_frame_t frame, int pitch);
 
     /**
      * Abandon all pending note-on events.
@@ -132,19 +132,19 @@
     void setProperties(const QXmlAttributes &attributes);
 
     /// VerticalScaleLayer methods
-    virtual int getYForValue(View *v, float value) const;
-    virtual float getValueForY(View *v, int y) const;
+    virtual int getYForValue(LayerGeometryProvider *v, double value) const;
+    virtual double getValueForY(LayerGeometryProvider *v, int y) const;
     virtual QString getScaleUnits() const;
 
 protected:
-    void getScaleExtents(View *, float &min, float &max, bool &log) const;
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
     bool shouldConvertMIDIToHz() const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    NoteModel::PointList getLocalPoints(View *v, int) const;
+    NoteModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
 
-    bool getPointToDrag(View *v, int x, int y, NoteModel::Point &) const;
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, NoteModel::Point &) const;
 
     NoteModel *m_model;
     bool m_editing;
@@ -160,8 +160,8 @@
     typedef std::set<NoteModel::Point, NoteModel::Point::Comparator> NoteSet;
     NoteSet m_pendingNoteOns;
 
-    mutable float m_scaleMinimum;
-    mutable float m_scaleMaximum;
+    mutable double m_scaleMinimum;
+    mutable double m_scaleMaximum;
 
     bool shouldAutoAlign() const;
 
--- a/layer/PaintAssistant.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/PaintAssistant.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -25,11 +25,11 @@
 
 void
 PaintAssistant::paintVerticalLevelScale(QPainter &paint, QRect rect,
-					float minVal, float maxVal,
+					double minVal, double maxVal,
                                         Scale scale, int &mult,
                                         std::vector<int> *vy)
 {
-    static float meterdbs[] = { -40, -30, -20, -15, -10,
+    static double meterdbs[] = { -40, -30, -20, -15, -10,
                                 -5, -3, -2, -1, -0.5, 0 };
 
     int h = rect.height(), w = rect.width();
@@ -41,7 +41,7 @@
     int n = 10;
     if (vy) vy->clear();
 
-    float step = 0;
+    double step = 0;
     mult = 1;
     if (scale == LinearScale) {
         step = (maxVal - minVal) / n;
@@ -53,8 +53,8 @@
         if (round) {
             mult /= 10;
 //            cerr << "\n\nstep goes from " << step;
-            step = float(round) / mult;
-            n = lrintf((maxVal - minVal) / step);
+            step = double(round) / mult;
+            n = int(lrint((maxVal - minVal) / step));
             if (mult > 1) {
                 mult /= 10;
             }
@@ -64,7 +64,7 @@
 
     for (int i = 0; i <= n; ++i) {
         
-        float val = 0.0, nval = 0.0;
+        double val = 0.0, nval = 0.0;
         QString text = "";
 
         switch (scale) {
@@ -166,20 +166,20 @@
 }
 
 static int
-dBscale(float sample, int m, float maxVal, float minVal) 
+dBscale(double sample, int m, double maxVal, double minVal) 
 {
     if (sample < 0.0) return dBscale(-sample, m, maxVal, minVal);
-    float dB = AudioLevel::multiplier_to_dB(sample);
-    float mindB = AudioLevel::multiplier_to_dB(minVal);
-    float maxdB = AudioLevel::multiplier_to_dB(maxVal);
+    double dB = AudioLevel::multiplier_to_dB(sample);
+    double mindB = AudioLevel::multiplier_to_dB(minVal);
+    double maxdB = AudioLevel::multiplier_to_dB(maxVal);
     if (dB < mindB) return 0;
     if (dB > 0.0) return m;
     return int(((dB - mindB) * m) / (maxdB - mindB) + 0.1);
 }
 
 int
-PaintAssistant::getYForValue(Scale scale, float value, 
-                             float minVal, float maxVal,
+PaintAssistant::getYForValue(Scale scale, double value, 
+                             double minVal, double maxVal,
                              int minY, int height)
 {
     int vy = 0;
--- a/layer/PaintAssistant.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/PaintAssistant.h	Wed Apr 20 12:06:28 2016 +0100
@@ -27,12 +27,12 @@
     enum Scale { LinearScale, MeterScale, dBScale };
 
     static void paintVerticalLevelScale(QPainter &p, QRect rect,
-                                        float minVal, float maxVal,
+                                        double minVal, double maxVal,
                                         Scale scale, int &multRtn,
                                         std::vector<int> *markCoordRtns = 0);
 
-    static int getYForValue(Scale scale, float value,
-                            float minVal, float maxVal,
+    static int getYForValue(Scale scale, double value,
+                            double minVal, double maxVal,
                             int minY, int height);
 };
 
--- a/layer/PianoScale.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/PianoScale.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,14 +21,14 @@
 
 #include "base/Pitch.h"
 
-#include "view/View.h"
+#include "view/LayerGeometryProvider.h"
 
 void
-PianoScale::paintPianoVertical(View *v,
+PianoScale::paintPianoVertical(LayerGeometryProvider *v,
 			       QPainter &paint,
 			       QRect r,
-			       float minf,
-			       float maxf)
+			       double minf,
+			       double maxf)
 {
     int x0 = r.x(), y0 = r.y(), x1 = r.x() + r.width(), y1 = r.y() + r.height();
 
@@ -39,8 +39,8 @@
 
     for (int i = 0; i < 128; ++i) {
 
-	float f = Pitch::getFrequencyForPitch(i);
-	int y = lrintf(v->getYForFrequency(f, minf, maxf, true));
+	double f = Pitch::getFrequencyForPitch(i);
+	int y = int(lrint(v->getYForFrequency(f, minf, maxf, true)));
 
 	if (y < y0 - 2) break;
 	if (y > y1 + 2) {
--- a/layer/PianoScale.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/PianoScale.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,13 +19,13 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 
 class PianoScale
 {
 public:
     void paintPianoVertical
-    (View *v, QPainter &paint, QRect rect, float minf, float maxf);
+    (LayerGeometryProvider *v, QPainter &paint, QRect rect, double minf, double maxf);
 };
 
 #endif
--- a/layer/RegionLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/RegionLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -47,6 +47,10 @@
     SingleColourLayer(),
     m_model(0),
     m_editing(false),
+    m_dragPointX(0),
+    m_dragPointY(0),
+    m_dragStartX(0),
+    m_dragStartY(0),
     m_originalPoint(0, 0.0, 0, tr("New Region")),
     m_editingPoint(0, 0.0, 0, tr("New Region")),
     m_editingCommand(0),
@@ -240,7 +244,7 @@
 }
 
 bool
-RegionLayer::isLayerScrollable(const View *v) const
+RegionLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
@@ -271,7 +275,7 @@
 }
 
 bool
-RegionLayer::getValueExtents(float &min, float &max,
+RegionLayer::getValueExtents(double &min, double &max,
                            bool &logarithmic, QString &unit) const
 {
     if (!m_model) return false;
@@ -285,7 +289,7 @@
 }
 
 bool
-RegionLayer::getDisplayExtents(float &min, float &max) const
+RegionLayer::getDisplayExtents(double &min, double &max) const
 {
     if (!m_model ||
         m_verticalScale == AutoAlignScale ||
@@ -298,11 +302,11 @@
 }
 
 RegionModel::PointList
-RegionLayer::getLocalPoints(View *v, int x) const
+RegionLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     if (!m_model) return RegionModel::PointList();
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     RegionModel::PointList onPoints =
 	m_model->getPoints(frame);
@@ -341,11 +345,11 @@
 }
 
 bool
-RegionLayer::getPointToDrag(View *v, int x, int y, RegionModel::Point &p) const
+RegionLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, RegionModel::Point &p) const
 {
     if (!m_model) return false;
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     RegionModel::PointList onPoints = m_model->getPoints(frame);
     if (onPoints.empty()) return false;
@@ -367,7 +371,7 @@
 }
 
 QString
-RegionLayer::getLabelPreceding(size_t frame) const
+RegionLayer::getLabelPreceding(sv_frame_t frame) const
 {
     if (!m_model) return "";
     RegionModel::PointList points = m_model->getPreviousPoints(frame);
@@ -379,7 +383,7 @@
 }
 
 QString
-RegionLayer::getFeatureDescription(View *v, QPoint &pos) const
+RegionLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -449,8 +453,8 @@
 }
 
 bool
-RegionLayer::snapToFeatureFrame(View *v, int &frame,
-                                size_t &resolution,
+RegionLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+                                int &resolution,
                                 SnapType snap) const
 {
     if (!m_model) {
@@ -469,7 +473,7 @@
     }    
 
     points = m_model->getPoints(frame, frame);
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
 
     for (RegionModel::PointList::const_iterator i = points.begin();
@@ -532,8 +536,8 @@
 }
 
 bool
-RegionLayer::snapToSimilarFeature(View *v, int &frame,
-                                  size_t &resolution,
+RegionLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
+                                  int &resolution,
                                   SnapType snap) const
 {
     if (!m_model) {
@@ -547,8 +551,8 @@
 
     RegionModel::PointList::const_iterator i;
 
-    int matchframe = frame;
-    float matchvalue = 0.f;
+    sv_frame_t matchframe = frame;
+    double matchvalue = 0.f;
 
     for (i = close.begin(); i != close.end(); ++i) {
         if (i->frame > frame) break;
@@ -556,27 +560,35 @@
         matchframe = i->frame;
     }
 
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
     bool distant = false;
-    float epsilon = 0.0001;
+    double epsilon = 0.0001;
 
     i = close.begin();
 
     // Scan through the close points first, then the more distant ones
-    // if no suitable close one is found
+    // if no suitable close one is found. So the while-termination
+    // condition here can only happen once i has passed through the
+    // whole of the close container and then the whole of the separate
+    // points container. The two iterators are totally distinct, but
+    // have the same type so we cheekily use the same variable and a
+    // single loop for both.
 
     while (i != points.end()) {
 
-        if (i == close.end()) {
-            i = points.begin();
-            distant = true;
+        if (!distant) {
+            if (i == close.end()) {
+                // switch from the close container to the points container
+                i = points.begin();
+                distant = true;
+            }
         }
 
 	if (snap == SnapRight) {
 
 	    if (i->frame > matchframe &&
-                fabsf(i->value - matchvalue) < epsilon) {
+                fabs(i->value - matchvalue) < epsilon) {
 		snapped = i->frame;
 		found = true;
 		break;
@@ -585,7 +597,7 @@
 	} else if (snap == SnapLeft) {
 
 	    if (i->frame < matchframe) {
-                if (fabsf(i->value - matchvalue) < epsilon) {
+                if (fabs(i->value - matchvalue) < epsilon) {
                     snapped = i->frame;
                     found = true; // don't break, as the next may be better
                 }
@@ -612,7 +624,7 @@
 }
 
 void
-RegionLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
+RegionLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
 {
     min = 0.0;
     max = 0.0;
@@ -664,33 +676,33 @@
 }
 
 int
-RegionLayer::spacingIndexToY(View *v, int i) const
+RegionLayer::spacingIndexToY(LayerGeometryProvider *v, int i) const
 {
-    int h = v->height();
-    int n = m_spacingMap.size();
+    int h = v->getPaintHeight();
+    int n = int(m_spacingMap.size());
     // this maps from i (spacing of the value from the spacing
     // map) and n (number of region types) to y
     int y = h - (((h * i) / n) + (h / (2 * n)));
     return y;
 }
 
-float
-RegionLayer::yToSpacingIndex(View *v, int y) const
+double
+RegionLayer::yToSpacingIndex(LayerGeometryProvider *v, int y) const
 {
-    // we return an inexact result here (float rather than int)
-    int h = v->height();
-    int n = m_spacingMap.size();
+    // we return an inexact result here (double rather than int)
+    int h = v->getPaintHeight();
+    int n = int(m_spacingMap.size());
     // from y = h - ((h * i) / n) + (h / (2 * n)) as above (vh taking place of i)
-    float vh = float(2*h*n - h - 2*n*y) / float(2*h);
+    double vh = double(2*h*n - h - 2*n*y) / double(2*h);
     return vh;
 }
 
 int
-RegionLayer::getYForValue(View *v, float val) const
+RegionLayer::getYForValue(LayerGeometryProvider *v, double val) const
 {
-    float min = 0.0, max = 0.0;
+    double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     if (m_verticalScale == EqualSpaced) {
 
@@ -720,18 +732,18 @@
     }
 }
 
-float
-RegionLayer::getValueForY(View *v, int y) const
+double
+RegionLayer::getValueForY(LayerGeometryProvider *v, int y) const
 {
     return getValueForY(v, y, -1);
 }
 
-float
-RegionLayer::getValueForY(View *v, int y, int avoid) const
+double
+RegionLayer::getValueForY(LayerGeometryProvider *v, int y, int avoid) const
 {
-    float min = 0.0, max = 0.0;
+    double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     if (m_verticalScale == EqualSpaced) {
 
@@ -745,14 +757,14 @@
         // one of the m/n divisions in the y scale, we should snap to
         // the value of the mth region.
 
-        float vh = yToSpacingIndex(v, y);
+        double vh = yToSpacingIndex(v, y);
 
         // spacings in the map are integral, so find the closest one,
         // map it back to its y coordinate, and see how far we are
         // from it
 
-        int n = m_spacingMap.size();
-        int ivh = lrintf(vh);
+        int n = int(m_spacingMap.size());
+        int ivh = int(lrint(vh));
         if (ivh < 0) ivh = 0;
         if (ivh > n-1) ivh = n-1;
         int iy = spacingIndexToY(v, ivh);
@@ -771,7 +783,7 @@
 
 //        cerr << "nearest existing value = " << i->first << " at " << iy << endl;
 
-        float val = 0;
+        double val = 0;
 
 //        cerr << "note: avoid = " << avoid << ", i->second = " << i->second << endl;
 
@@ -813,10 +825,10 @@
 
         getScaleExtents(v, min, max, logarithmic);
 
-        float val = min + (float(h - y) * float(max - min)) / h;
+        double val = min + (double(h - y) * double(max - min)) / h;
 
         if (logarithmic) {
-            val = powf(10.f, val);
+            val = pow(10.0, val);
         }
 
         return val;
@@ -824,9 +836,9 @@
 }
 
 QColor
-RegionLayer::getColourForValue(View *v, float val) const
+RegionLayer::getColourForValue(LayerGeometryProvider *v, double val) const
 {
-    float min, max;
+    double min, max;
     bool log;
     getScaleExtents(v, min, max, log);
 
@@ -854,18 +866,18 @@
 }
 
 void
-RegionLayer::paint(View *v, QPainter &paint, QRect rect) const
+RegionLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
-    int sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("RegionLayer::paint", true);
 
     int x0 = rect.left() - 40, x1 = rect.right();
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1);
+    sv_frame_t frame0 = v->getFrameForX(x0);
+    sv_frame_t frame1 = v->getFrameForX(x1);
 
     RegionModel::PointList points(m_model->getPoints(frame0, frame1));
     if (points.empty()) return;
@@ -878,8 +890,8 @@
 //    SVDEBUG << "RegionLayer::paint: resolution is "
 //	      << m_model->getResolution() << " frames" << endl;
 
-    float min = m_model->getValueMinimum();
-    float max = m_model->getValueMaximum();
+    double min = m_model->getValueMinimum();
+    double max = m_model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
     QPoint localPos;
@@ -901,7 +913,6 @@
     //!!! coord is never completely flat on the top or bottom
 
     int fontHeight = paint.fontMetrics().height();
-    int fontAscent = paint.fontMetrics().ascent();
 
     for (RegionModel::PointList::const_iterator i = points.begin();
 	 i != points.end(); ++i) {
@@ -931,7 +942,7 @@
 	if (w < 1) w = 1;
 
 	if (m_plotStyle == PlotSegmentation) {
-            paint.setPen(getForegroundQColor(v));
+            paint.setPen(getForegroundQColor(v->getView()));
             paint.setBrush(getColourForValue(v, p.value));
         } else {
             paint.setPen(getBaseQColor());
@@ -947,15 +958,15 @@
                 RegionModel::Point::Comparator()(illuminatePoint, p) ||
                 RegionModel::Point::Comparator()(p, illuminatePoint)) {
 
-                paint.setPen(QPen(getForegroundQColor(v), 1));
-                paint.drawLine(x, 0, x, v->height());
+                paint.setPen(QPen(getForegroundQColor(v->getView()), 1));
+                paint.drawLine(x, 0, x, v->getPaintHeight());
                 paint.setPen(Qt::NoPen);
 
             } else {
-                paint.setPen(QPen(getForegroundQColor(v), 2));
+                paint.setPen(QPen(getForegroundQColor(v->getView()), 2));
             }
 
-	    paint.drawRect(x, -1, ex - x, v->height() + 2);
+	    paint.drawRect(x, -1, ex - x, v->getPaintHeight() + 2);
 
 	} else {
 
@@ -1029,7 +1040,7 @@
                 labelX = x + 5;
                 labelY = v->getTextLabelHeight(this, paint);
                 if (labelX < nextLabelMinX) {
-                    if (lastLabelY < v->height()/2) {
+                    if (lastLabelY < v->getPaintHeight()/2) {
                         labelY = lastLabelY + fontHeight;
                     }
                 }
@@ -1045,7 +1056,7 @@
 }
 
 int
-RegionLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+RegionLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
     if (!m_model || 
         m_verticalScale == AutoAlignScale || 
@@ -1067,16 +1078,15 @@
 }
 
 void
-RegionLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+RegionLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model) return;
+    if (!m_model || m_model->getPoints().empty()) return;
 
     QString unit;
-    float min, max;
+    double min, max;
     bool logarithmic;
 
     int w = getVerticalScaleWidth(v, false, paint);
-    int h = v->height();
 
     if (m_plotStyle == PlotSegmentation) {
 
@@ -1111,17 +1121,17 @@
 }
 
 void
-RegionLayer::drawStart(View *v, QMouseEvent *e)
+RegionLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float value = getValueForY(v, e->y());
+    double value = getValueForY(v, e->y());
 
-    m_editingPoint = RegionModel::Point(frame, value, 0, "");
+    m_editingPoint = RegionModel::Point(frame, float(value), 0, "");
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
@@ -1135,19 +1145,19 @@
 }
 
 void
-RegionLayer::drawDrag(View *v, QMouseEvent *e)
+RegionLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float newValue = m_editingPoint.value;
+    double newValue = m_editingPoint.value;
     if (m_verticalScale != EqualSpaced) newValue = getValueForY(v, e->y());
 
-    long newFrame = m_editingPoint.frame;
-    long newDuration = frame - newFrame;
+    sv_frame_t newFrame = m_editingPoint.frame;
+    sv_frame_t newDuration = frame - newFrame;
     if (newDuration < 0) {
         newFrame = frame;
         newDuration = -newDuration;
@@ -1157,7 +1167,7 @@
 
     m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = newFrame;
-    m_editingPoint.value = newValue;
+    m_editingPoint.value = float(newValue);
     m_editingPoint.duration = newDuration;
     m_editingCommand->addPoint(m_editingPoint);
 
@@ -1165,7 +1175,7 @@
 }
 
 void
-RegionLayer::drawEnd(View *, QMouseEvent *)
+RegionLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
     if (!m_model || !m_editing) return;
     finish(m_editingCommand);
@@ -1176,7 +1186,7 @@
 }
 
 void
-RegionLayer::eraseStart(View *v, QMouseEvent *e)
+RegionLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -1192,12 +1202,12 @@
 }
 
 void
-RegionLayer::eraseDrag(View *v, QMouseEvent *e)
+RegionLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-RegionLayer::eraseEnd(View *v, QMouseEvent *e)
+RegionLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1219,7 +1229,7 @@
 }
 
 void
-RegionLayer::editStart(View *v, QMouseEvent *e)
+RegionLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -1244,7 +1254,7 @@
 }
 
 void
-RegionLayer::editDrag(View *v, QMouseEvent *e)
+RegionLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1253,7 +1263,7 @@
     int newx = m_dragPointX + xdist;
     int newy = m_dragPointY + ydist;
 
-    long frame = v->getFrameForX(newx);
+    sv_frame_t frame = v->getFrameForX(newx);
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -1264,7 +1274,7 @@
     // ... unless there are other points with the same value
     if (m_distributionMap[m_editingPoint.value] > 1) avoid = -1;
 
-    float value = getValueForY(v, newy, avoid);
+    double value = getValueForY(v, newy, avoid);
 
     if (!m_editingCommand) {
 	m_editingCommand = new RegionModel::EditCommand(m_model,
@@ -1273,13 +1283,13 @@
 
     m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = frame;
-    m_editingPoint.value = value;
+    m_editingPoint.value = float(value);
     m_editingCommand->addPoint(m_editingPoint);
     recalcSpacing();
 }
 
 void
-RegionLayer::editEnd(View *, QMouseEvent *e)
+RegionLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
     if (!m_model || !m_editing) return;
 
@@ -1307,7 +1317,7 @@
 }
 
 bool
-RegionLayer::editOpen(View *v, QMouseEvent *e)
+RegionLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -1348,7 +1358,7 @@
 }
 
 void
-RegionLayer::moveSelection(Selection s, size_t newStartFrame)
+RegionLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
     if (!m_model) return;
 
@@ -1393,13 +1403,13 @@
 
 	if (s.contains(i->frame)) {
 
-	    double targetStart = i->frame;
-	    targetStart = newSize.getStartFrame() + 
-		double(targetStart - s.getStartFrame()) * ratio;
+	    double targetStart = double(i->frame);
+	    targetStart = double(newSize.getStartFrame()) +
+		targetStart - double(s.getStartFrame()) * ratio;
 
-	    double targetEnd = i->frame + i->duration;
-	    targetEnd = newSize.getStartFrame() +
-		double(targetEnd - s.getStartFrame()) * ratio;
+	    double targetEnd = double(i->frame + i->duration);
+	    targetEnd = double(newSize.getStartFrame()) +
+		targetEnd - double(s.getStartFrame()) * ratio;
 
 	    RegionModel::Point newPoint(*i);
 	    newPoint.frame = lrint(targetStart);
@@ -1437,7 +1447,7 @@
 }    
 
 void
-RegionLayer::copy(View *v, Selection s, Clipboard &to)
+RegionLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1455,7 +1465,7 @@
 }
 
 bool
-RegionLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
+RegionLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -1466,7 +1476,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
                                   tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
                                   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
                                   QMessageBox::Yes);
@@ -1487,7 +1497,7 @@
          i != points.end(); ++i) {
         
         if (!i->haveFrame()) continue;
-        size_t frame = 0;
+        sv_frame_t frame = 0;
 
         if (!realign) {
             
@@ -1511,7 +1521,7 @@
                                m_model->getValueMaximum()) / 2;
         if (i->haveDuration()) newPoint.duration = i->getDuration();
         else {
-            size_t nextFrame = frame;
+            sv_frame_t nextFrame = frame;
             Clipboard::PointList::const_iterator j = i;
             for (; j != points.end(); ++j) {
                 if (!j->haveFrame()) continue;
--- a/layer/RegionLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/RegionLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -39,41 +39,41 @@
 public:
     RegionLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
-    virtual QString getLabelPreceding(size_t) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
+    virtual QString getLabelPreceding(sv_frame_t) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				    int &resolution,
 				    SnapType snap) const;
-    virtual bool snapToSimilarFeature(View *v, int &frame,
-                                      size_t &resolution,
+    virtual bool snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
+                                      int &resolution,
                                       SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *v, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void moveSelection(Selection s, size_t newStartFrame);
+    virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -110,16 +110,16 @@
     void setPlotStyle(PlotStyle style);
     PlotStyle getPlotStyle() const { return m_plotStyle; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const;
 
-    virtual bool getDisplayExtents(float &min, float &max) const;
+    virtual bool getDisplayExtents(double &min, double &max) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
@@ -127,23 +127,23 @@
     void setProperties(const QXmlAttributes &attributes);
 
     /// VerticalScaleLayer and ColourScaleLayer methods
-    int getYForValue(View *v, float value) const;
-    float getValueForY(View *v, int y) const;
+    int getYForValue(LayerGeometryProvider *v, double value) const;
+    double getValueForY(LayerGeometryProvider *v, int y) const;
     virtual QString getScaleUnits() const;
-    QColor getColourForValue(View *v, float value) const;
+    QColor getColourForValue(LayerGeometryProvider *v, double value) const;
 
 protected slots:
     void recalcSpacing();
 
 protected:
-    float getValueForY(View *v, int y, int avoid) const;
-    void getScaleExtents(View *, float &min, float &max, bool &log) const;
+    double getValueForY(LayerGeometryProvider *v, int y, int avoid) const;
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    RegionModel::PointList getLocalPoints(View *v, int x) const;
+    RegionModel::PointList getLocalPoints(LayerGeometryProvider *v, int x) const;
 
-    bool getPointToDrag(View *v, int x, int y, RegionModel::Point &) const;
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, RegionModel::Point &) const;
 
     RegionModel *m_model;
     bool m_editing;
@@ -158,7 +158,7 @@
     int m_colourMap;
     PlotStyle m_plotStyle;
 
-    typedef std::map<float, int> SpacingMap;
+    typedef std::map<double, int> SpacingMap;
 
     // region value -> ordering
     SpacingMap m_spacingMap;
@@ -166,8 +166,8 @@
     // region value -> number of regions with this value
     SpacingMap m_distributionMap;
 
-    int spacingIndexToY(View *v, int i) const;
-    float yToSpacingIndex(View *v, int y) const;
+    int spacingIndexToY(LayerGeometryProvider *v, int i) const;
+    double yToSpacingIndex(LayerGeometryProvider *v, int y) const;
 
     void finish(RegionModel::EditCommand *command) {
         Command *c = command->finish();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ScrollableImageCache.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,206 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "ScrollableImageCache.h"
+
+#include <iostream>
+using namespace std;
+
+//#define DEBUG_SCROLLABLE_IMAGE_CACHE 1
+
+void
+ScrollableImageCache::scrollTo(sv_frame_t newStartFrame)
+{
+    if (!m_v) throw std::logic_error("ScrollableImageCache: not associated with a LayerGeometryProvider");
+	
+    int dx = (m_v->getXForFrame(m_startFrame) -
+	      m_v->getXForFrame(newStartFrame));
+
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+    cerr << "ScrollableImageCache::scrollTo: start frame " << m_startFrame
+	 << " -> " << newStartFrame << ", dx = " << dx << endl;
+#endif
+    
+    m_startFrame = newStartFrame;
+	
+    if (!isValid()) {
+	return;
+    }
+
+    int w = m_image.width();
+
+    if (dx == 0) {
+	// haven't moved
+	return;
+    }
+
+    if (dx <= -w || dx >= w) {
+	// scrolled entirely off
+	invalidate();
+	return;
+    }
+	
+    // dx is in range, cache is scrollable
+
+    int dxp = dx;
+    if (dxp < 0) dxp = -dxp;
+
+    int copylen = (w - dxp) * int(sizeof(QRgb));
+    for (int y = 0; y < m_image.height(); ++y) {
+	QRgb *line = (QRgb *)m_image.scanLine(y);
+	if (dx < 0) {
+	    memmove(line, line + dxp, copylen);
+	} else {
+	    memmove(line + dxp, line, copylen);
+	}
+    }
+	
+    // update valid area
+        
+    int px = m_left;
+    int pw = m_width;
+	
+    px += dx;
+	
+    if (dx < 0) {
+	// we scrolled left
+	if (px < 0) {
+	    pw += px;
+	    px = 0;
+	    if (pw < 0) {
+		pw = 0;
+	    }
+	}
+    } else {
+	// we scrolled right
+	if (px + pw > w) {
+	    pw = w - px;
+	    if (pw < 0) {
+		pw = 0;
+	    }
+	}
+    }
+
+    m_left = px;
+    m_width = pw;
+}
+
+void
+ScrollableImageCache::adjustToTouchValidArea(int &left, int &width,
+					     bool &isLeftOfValidArea) const
+{
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+    cerr << "ScrollableImageCache::adjustToTouchValidArea: left " << left
+         << ", width " << width << endl;
+    cerr << "ScrollableImageCache: my left " << m_left
+         << ", width " << m_width << " so right " << (m_left + m_width) << endl;
+#endif
+    if (left < m_left) {
+	isLeftOfValidArea = true;
+	if (left + width <= m_left + m_width) {
+	    width = m_left - left;
+	}
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+        cerr << "ScrollableImageCache: we're left of valid area, adjusted width to " << width << endl;
+#endif
+    } else {
+	isLeftOfValidArea = false;
+	width = left + width - (m_left + m_width);
+	left = m_left + m_width;
+	if (width < 0) width = 0;
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+        cerr << "ScrollableImageCache: we're right of valid area, adjusted left to " << left << ", width to " << width << endl;
+#endif
+    }
+}
+    
+void
+ScrollableImageCache::drawImage(int left,
+				int width,
+				QImage image,
+				int imageLeft,
+				int imageWidth)
+{
+    if (image.height() != m_image.height()) {
+	cerr << "ScrollableImageCache::drawImage: ERROR: Supplied image height "
+	     << image.height() << " does not match cache height "
+	     << m_image.height() << endl;
+	throw std::logic_error("Image height must match cache height in ScrollableImageCache::drawImage");
+    }
+    if (left < 0 || width < 0 || left + width > m_image.width()) {
+	cerr << "ScrollableImageCache::drawImage: ERROR: Target area (left = "
+	     << left << ", width = " << width << ", so right = " << left + width
+             << ") out of bounds for cache of width " << m_image.width() << endl;
+	throw std::logic_error("Target area out of bounds in ScrollableImageCache::drawImage");
+    }
+    if (imageLeft < 0 || imageWidth < 0 ||
+	imageLeft + imageWidth > image.width()) {
+	cerr << "ScrollableImageCache::drawImage: ERROR: Source area (left = "
+	     << imageLeft << ", width = " << imageWidth << ", so right = "
+             << imageLeft + imageWidth << ") out of bounds for image of "
+	     << "width " << image.width() << endl;
+	throw std::logic_error("Source area out of bounds in ScrollableImageCache::drawImage");
+    }
+	
+    QPainter painter(&m_image);
+    painter.drawImage(QRect(left, 0, width, m_image.height()),
+		      image,
+		      QRect(imageLeft, 0, imageWidth, image.height()));
+    painter.end();
+
+    if (!isValid()) {
+	m_left = left;
+	m_width = width;
+	return;
+    }
+	
+    if (left < m_left) {
+	if (left + width > m_left + m_width) {
+	    // new image completely contains the old valid area --
+	    // use the new area as is
+	    m_left = left;
+	    m_width = width;
+	} else if (left + width < m_left) {
+	    // new image completely off left of old valid area --
+	    // we can't extend the valid area because the bit in
+	    // between is not valid, so must use the new area only
+	    m_left = left;
+	    m_width = width;
+	} else {
+	    // new image overlaps old valid area on left side --
+	    // use new left edge, and extend width to existing
+	    // right edge
+	    m_width = (m_left + m_width) - left;
+	    m_left = left;
+	}
+    } else {
+	if (left > m_left + m_width) {
+	    // new image completely off right of old valid area --
+	    // we can't extend the valid area because the bit in
+	    // between is not valid, so must use the new area only
+	    m_left = left;
+	    m_width = width;
+	} else if (left + width > m_left + m_width) {
+	    // new image overlaps old valid area on right side --
+	    // use existing left edge, and extend width to new
+	    // right edge
+	    m_width = (left + width) - m_left;
+	    // (m_left unchanged)
+	} else {
+	    // new image completely contained within old valid
+	    // area -- leave the old area unchanged
+	}
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ScrollableImageCache.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,145 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SCROLLABLE_IMAGE_CACHE_H
+#define SCROLLABLE_IMAGE_CACHE_H
+
+#include "base/BaseTypes.h"
+
+#include "view/LayerGeometryProvider.h"
+
+#include <QImage>
+#include <QRect>
+#include <QPainter>
+
+/**
+ * A cached image for a view that scrolls horizontally, primarily the
+ * spectrogram. The cache object holds an image, reports the size of
+ * the image (likely the same as the underlying view, but it's the
+ * caller's responsibility to set the size appropriately), can scroll
+ * the image, and can report and update which contiguous horizontal
+ * range of the image is valid.
+ *
+ * The only way to *update* the valid area in a cache is to draw to it
+ * using the drawImage call.
+ */
+class ScrollableImageCache
+{
+public:
+    ScrollableImageCache(const LayerGeometryProvider *v = 0) :
+	m_v(v),
+	m_left(0),
+	m_width(0),
+	m_startFrame(0),
+	m_zoomLevel(0)
+    {}
+
+    void invalidate() {
+	m_width = 0;
+    }
+    
+    bool isValid() const {
+	return m_width > 0;
+    }
+
+    QSize getSize() const {
+	return m_image.size();
+    }
+    
+    void resize(QSize newSize) {
+	m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied);
+	invalidate();
+    }
+	
+    int getValidLeft() const {
+	return m_left;
+    }
+    
+    int getValidWidth() const {
+	return m_width;
+    }
+
+    int getValidRight() const {
+	return m_left + m_width;
+    }
+
+    QRect getValidArea() const {
+	return QRect(m_left, 0, m_width, m_image.height());
+    }
+    
+    int getZoomLevel() const {
+	return m_zoomLevel;
+    }
+    
+    void setZoomLevel(int zoom) {
+	m_zoomLevel = zoom;
+	invalidate();
+    }
+
+    sv_frame_t getStartFrame() const {
+	return m_startFrame;
+    }
+
+    /**
+     * Set the start frame and invalidate the cache. To scroll,
+     * i.e. to set the start frame while retaining cache validity
+     * where possible, use scrollTo() instead.
+     */
+    void setStartFrame(sv_frame_t frame) {
+	m_startFrame = frame;
+	invalidate();
+    }
+    
+    const QImage &getImage() const {
+	return m_image;
+    }
+
+    /**
+     * Set the new start frame for the cache, if possible also moving
+     * along any existing valid data within the cache so that it
+     * continues to be valid for the new start frame.
+     */
+    void scrollTo(sv_frame_t newStartFrame);
+
+    /**
+     * Take a left coordinate and width describing a region, and
+     * adjust them so that they are contiguous with the cache valid
+     * region and so that the union of the adjusted region with the
+     * cache valid region contains the supplied region.
+     */
+    void adjustToTouchValidArea(int &left, int &width,
+				bool &isLeftOfValidArea) const;
+    /**
+     * Draw from an image onto the cache. The supplied image must have
+     * the same height as the cache and the full height is always
+     * drawn. The left and width parameters determine the target
+     * region of the cache, the imageLeft and imageWidth parameters
+     * the source region of the image.
+     */
+    void drawImage(int left,
+		   int width,
+		   QImage image,
+		   int imageLeft,
+		   int imageWidth);
+    
+private:
+    const LayerGeometryProvider *m_v;
+    QImage m_image;
+    int m_left;  // of valid region
+    int m_width; // of valid region
+    sv_frame_t m_startFrame;
+    int m_zoomLevel;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ShowLayerCommand.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,42 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _SHOW_LAYER_COMMAND_H_
+#define _SHOW_LAYER_COMMAND_H_
+
+#include "base/Command.h"
+
+class ShowLayerCommand : public Command
+{
+public:
+    ShowLayerCommand(View *view, Layer *layer, bool show, QString commandName) :
+        m_view(view), m_layer(layer), m_show(show), m_name(commandName) { }
+    void execute() {
+        m_layer->showLayer(m_view, m_show);
+    }
+    void unexecute() {
+        m_layer->showLayer(m_view, !m_show);
+    }
+    QString getName() const {
+        return m_name;
+    }
+protected:
+    View *m_view;
+    Layer *m_layer;
+    bool m_show;
+    QString m_name;
+};
+
+#endif
--- a/layer/SingleColourLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SingleColourLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -32,9 +32,17 @@
     m_colourExplicitlySet(false),
     m_defaultColourSet(false)
 {
+    // Reference current colour because setDefaulColourFor
+    // will unreference it before (possibly) changing it.
+    refColor();
     setDefaultColourFor(0);
 }
 
+SingleColourLayer::~SingleColourLayer()
+{
+    unrefColor();
+}
+
 QPixmap
 SingleColourLayer::getLayerPresentationPixmap(QSize size) const
 {
@@ -107,7 +115,7 @@
 {
     if (name == "Colour") {
         ColourDatabase *db = ColourDatabase::getInstance();
-        if (value >= 0 && size_t(value) < db->getColourCount()) {
+        if (value >= 0 && value < db->getColourCount()) {
             return db->getColourName(value);
         }
     }
@@ -129,7 +137,7 @@
 }
 
 void
-SingleColourLayer::setDefaultColourFor(View *v)
+SingleColourLayer::setDefaultColourFor(LayerGeometryProvider *v)
 {
 #ifdef DEBUG_COLOUR_SELECTION
     SVDEBUG << "SingleColourLayer::setDefaultColourFor: m_colourExplicitlySet = " << m_colourExplicitlySet << ", m_defaultColourSet " << m_defaultColourSet << endl;
@@ -152,10 +160,6 @@
     int hint = -1;
     bool impose = false;
     if (v) {
-        if (m_colourRefCount.find(m_colour) != m_colourRefCount.end() &&
-            m_colourRefCount[m_colour] > 0) {
-            m_colourRefCount[m_colour]--;
-        }
         // We don't want to call this if !v because that probably
         // means we're being called from the constructor, and this is
         // a virtual function
@@ -174,6 +178,8 @@
         return;
     }
 
+    unrefColor();
+
     int bestCount = 0, bestColour = -1;
     
     for (int i = 0; i < cdb->getColourCount(); ++i) {
@@ -203,15 +209,11 @@
         cerr << endl;
 #endif
     }
-    
+
     if (bestColour < 0) m_colour = 0;
     else m_colour = bestColour;
 
-    if (m_colourRefCount.find(m_colour) == m_colourRefCount.end()) {
-        m_colourRefCount[m_colour] = 1;
-    } else {
-        m_colourRefCount[m_colour]++;
-    }
+    refColor();
 }
 
 void
@@ -221,18 +223,9 @@
 
     if (m_colour == colour) return;
 
-    if (m_colourRefCount.find(m_colour) != m_colourRefCount.end() &&
-        m_colourRefCount[m_colour] > 0) {
-        m_colourRefCount[m_colour]--;
-    }
-
+    refColor();
     m_colour = colour;
-
-    if (m_colourRefCount.find(m_colour) == m_colourRefCount.end()) {
-        m_colourRefCount[m_colour] = 1;
-    } else {
-        m_colourRefCount[m_colour]++;
-    }
+    unrefColor();
 
     flagBaseColourChanged();
     emit layerParametersChanged();
@@ -251,19 +244,19 @@
 }
 
 QColor
-SingleColourLayer::getBackgroundQColor(View *v) const
+SingleColourLayer::getBackgroundQColor(LayerGeometryProvider *v) const
 {
     return v->getBackground();
 }
 
 QColor
-SingleColourLayer::getForegroundQColor(View *v) const
+SingleColourLayer::getForegroundQColor(LayerGeometryProvider *v) const
 {
     return v->getForeground();
 }
 
 std::vector<QColor>
-SingleColourLayer::getPartialShades(View *v) const
+SingleColourLayer::getPartialShades(LayerGeometryProvider *v) const
 {
     std::vector<QColor> s;
     QColor base = getBaseQColor();
@@ -307,6 +300,9 @@
     int colour = ColourDatabase::getInstance()->putStringValues
         (colourName, colourSpec, darkbg);
 
+    if (colour == -1)
+      return;
+
     m_colourExplicitlySet = true;
 
     if (m_colour != colour) {
@@ -315,20 +311,27 @@
         SVDEBUG << "SingleColourLayer::setProperties: changing colour from " << m_colour << " to " << colour << endl;
 #endif
 
-        if (m_colourRefCount.find(m_colour) != m_colourRefCount.end() &&
-            m_colourRefCount[m_colour] > 0) {
-            m_colourRefCount[m_colour]--;
-        }
-
+        unrefColor();
         m_colour = colour;
-
-        if (m_colourRefCount.find(m_colour) == m_colourRefCount.end()) {
-            m_colourRefCount[m_colour] = 1;
-        } else {
-            m_colourRefCount[m_colour]++;
-        }
+        refColor();
 
         flagBaseColourChanged();
     }
 }
 
+void SingleColourLayer::refColor()
+{
+    if (m_colourRefCount.find(m_colour) == m_colourRefCount.end()) {
+        m_colourRefCount[m_colour] = 1;
+    } else {
+        m_colourRefCount[m_colour]++;
+    }
+}
+
+void SingleColourLayer::unrefColor()
+{
+    if (m_colourRefCount.find(m_colour) != m_colourRefCount.end() &&
+        m_colourRefCount[m_colour] > 0) {
+        m_colourRefCount[m_colour]--;
+    }
+}
--- a/layer/SingleColourLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SingleColourLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -71,15 +71,16 @@
 
     virtual void setProperties(const QXmlAttributes &attributes);
 
-    virtual void setDefaultColourFor(View *v);
+    virtual void setDefaultColourFor(LayerGeometryProvider *v);
 
 protected:
     SingleColourLayer();
+    virtual ~SingleColourLayer();
 
     virtual QColor getBaseQColor() const;
-    virtual QColor getBackgroundQColor(View *v) const;
-    virtual QColor getForegroundQColor(View *v) const;
-    std::vector<QColor> getPartialShades(View *v) const;
+    virtual QColor getBackgroundQColor(LayerGeometryProvider *v) const;
+    virtual QColor getForegroundQColor(LayerGeometryProvider *v) const;
+    std::vector<QColor> getPartialShades(LayerGeometryProvider *v) const;
 
     virtual void flagBaseColourChanged() { }
     virtual int getDefaultColourHint(bool /* darkBackground */,
@@ -91,6 +92,10 @@
     int m_colour;
     bool m_colourExplicitlySet;
     bool m_defaultColourSet;
+
+private:
+    void refColor();
+    void unrefColor();
 };
 
 #endif
--- a/layer/SliceLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SliceLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -92,23 +92,23 @@
 }
 
 QString
-SliceLayer::getFeatureDescription(View *v, QPoint &p) const
+SliceLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
 {
     int minbin, maxbin, range;
-    return getFeatureDescription(v, p, true, minbin, maxbin, range);
+    return getFeatureDescriptionAux(v, p, true, minbin, maxbin, range);
 }
 
 QString
-SliceLayer::getFeatureDescription(View *v, QPoint &p,
-                                  bool includeBinDescription,
-                                  int &minbin, int &maxbin, int &range) const
+SliceLayer::getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &p,
+                                     bool includeBinDescription,
+                                     int &minbin, int &maxbin, int &range) const
 {
     minbin = 0;
     maxbin = 0;
     if (!m_sliceableModel) return "";
 
     int xorigin = m_xorigins[v];
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
     
     int mh = m_sliceableModel->getHeight();
     minbin = getBinForX(p.x() - xorigin, mh, w);
@@ -119,21 +119,21 @@
     if (minbin < 0) minbin = 0;
     if (maxbin < 0) maxbin = 0;
     
-    int sampleRate = m_sliceableModel->getSampleRate();
+    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
 
-    size_t f0 = m_currentf0;
-    size_t f1 = m_currentf1;
+    sv_frame_t f0 = m_currentf0;
+    sv_frame_t f1 = m_currentf1;
 
     RealTime rt0 = RealTime::frame2RealTime(f0, sampleRate);
     RealTime rt1 = RealTime::frame2RealTime(f1, sampleRate);
     
-    range = f1 - f0 + 1;
+    range = int(f1 - f0 + 1);
 
     QString rtrangestr = QString("%1 s").arg((rt1 - rt0).toText().c_str());
 
     if (includeBinDescription) {
 
-        float minvalue = 0.f;
+        float minvalue = 0.0;
         if (minbin < int(m_values.size())) minvalue = m_values[minbin];
 
         float maxvalue = minvalue;
@@ -179,23 +179,23 @@
     }
 }
 
-float
-SliceLayer::getXForBin(int bin, int count, float w) const
+double
+SliceLayer::getXForBin(int bin, int count, double w) const
 {
-    float x = 0;
+    double x = 0;
 
     switch (m_binScale) {
 
     case LinearBins:
-        x = (float(w) * bin) / count;
+        x = (w * bin) / count;
         break;
         
     case LogBins:
-        x = (float(w) * log10f(bin + 1)) / log10f(count + 1);
+        x = (w * log10(bin + 1)) / log10(count + 1);
         break;
         
     case InvertedLogBins:
-        x = w - (float(w) * log10f(count - bin - 1)) / log10f(count);
+        x = w - (w * log10(count - bin - 1)) / log10(count);
         break;
     }
 
@@ -203,7 +203,7 @@
 }
 
 int
-SliceLayer::getBinForX(float x, int count, float w) const
+SliceLayer::getBinForX(double x, int count, double w) const
 {
     int bin = 0;
 
@@ -214,21 +214,21 @@
         break;
         
     case LogBins:
-        bin = int(powf(10.f, (x * log10f(count + 1)) / w) - 1 + 0.0001);
+        bin = int(pow(10.0, (x * log10(count + 1)) / w) - 1 + 0.0001);
         break;
 
     case InvertedLogBins:
-        bin = count + 1 - int(powf(10.f, (log10f(count) * (w - x)) / float(w)) + 0.0001);
+        bin = count + 1 - int(pow(10.0, (log10(count) * (w - x)) / double(w)) + 0.0001);
         break;
     }
 
     return bin;
 }
 
-float
-SliceLayer::getYForValue(float value, const View *v, float &norm) const
+double
+SliceLayer::getYForValue(double value, const LayerGeometryProvider *v, double &norm) const
 {
-    norm = 0.f;
+    norm = 0.0;
 
     if (m_yorigins.find(v) == m_yorigins.end()) return 0;
 
@@ -236,9 +236,9 @@
 
     int yorigin = m_yorigins[v];
     int h = m_heights[v];
-    float thresh = getThresholdDb();
+    double thresh = getThresholdDb();
 
-    float y = 0.f;
+    double y = 0.0;
 
     if (h <= 0) return y;
 
@@ -246,44 +246,45 @@
 
     case dBScale:
     {
-        float db = thresh;
-        if (value > 0.f) db = 10.f * log10f(fabsf(value));
+        double db = thresh;
+        if (value > 0.0) db = 10.0 * log10(fabs(value));
         if (db < thresh) db = thresh;
         norm = (db - thresh) / -thresh;
-        y = yorigin - (float(h) * norm);
+        y = yorigin - (double(h) * norm);
         break;
     }
     
     case MeterScale:
         y = AudioLevel::multiplier_to_preview(value, h);
-        norm = float(y) / float(h);
+        norm = double(y) / double(h);
         y = yorigin - y;
         break;
         
     case AbsoluteScale:
-        value = fabsf(value);
+        value = fabs(value);
         // and fall through
         
+    case LinearScale:
     default:
         norm = (value - m_threshold);
         if (norm < 0) norm = 0;
-        y = yorigin - (float(h) * norm);
+        y = yorigin - (double(h) * norm);
         break;
     }
     
     return y;
 }
 
-float
-SliceLayer::getValueForY(float y, const View *v) const
+double
+SliceLayer::getValueForY(double y, const LayerGeometryProvider *v) const
 {
-    float value = 0.f;
+    double value = 0.0;
 
     if (m_yorigins.find(v) == m_yorigins.end()) return value;
 
     int yorigin = m_yorigins[v];
     int h = m_heights[v];
-    float thresh = getThresholdDb();
+    double thresh = getThresholdDb();
 
     if (h <= 0) return value;
 
@@ -293,15 +294,17 @@
 
     case dBScale:
     {
-        float db = ((y / h) * -thresh) + thresh;
-        value = powf(10.f, db/10.f);
+        double db = ((y / h) * -thresh) + thresh;
+        value = pow(10.0, db/10.0);
         break;
     }
 
     case MeterScale:
-        value = AudioLevel::preview_to_multiplier(lrintf(y), h);
+        value = AudioLevel::preview_to_multiplier(int(lrint(y)), h);
         break;
-    
+
+    case LinearScale:
+    case AbsoluteScale:
     default:
         value = y / h + m_threshold;
     }
@@ -310,7 +313,7 @@
 }
 
 void
-SliceLayer::paint(View *v, QPainter &paint, QRect rect) const
+SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_sliceableModel || !m_sliceableModel->isOK() ||
         !m_sliceableModel->isReady()) return;
@@ -322,7 +325,7 @@
     if (v->getViewManager() && v->getViewManager()->shouldShowScaleGuides()) {
         if (!m_scalePoints.empty()) {
             paint.setPen(QColor(240, 240, 240)); //!!! and dark background?
-            for (size_t i = 0; i < m_scalePoints.size(); ++i) {
+            for (int i = 0; i < (int)m_scalePoints.size(); ++i) {
                 paint.drawLine(0, m_scalePoints[i], rect.width(), m_scalePoints[i]);
             }
         }
@@ -331,11 +334,11 @@
     paint.setPen(getBaseQColor());
 
     int xorigin = getVerticalScaleWidth(v, true, paint) + 1;
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
 
     m_xorigins[v] = xorigin; // for use in getFeatureDescription
     
-    int yorigin = v->height() - 20 - paint.fontMetrics().height() - 7;
+    int yorigin = v->getPaintHeight() - 20 - paint.fontMetrics().height() - 7;
     int h = yorigin - paint.fontMetrics().height() - 8;
 
     m_yorigins[v] = yorigin; // for getYForValue etc
@@ -345,27 +348,27 @@
 
     QPainterPath path;
 
-    size_t mh = m_sliceableModel->getHeight();
+    int mh = m_sliceableModel->getHeight();
 
     int divisor = 0;
 
     m_values.clear();
-    for (size_t bin = 0; bin < mh; ++bin) {
-        m_values.push_back(0.f);
+    for (int bin = 0; bin < mh; ++bin) {
+        m_values.push_back(0.0);
     }
 
-    size_t f0 = v->getCentreFrame();
+    sv_frame_t f0 = v->getCentreFrame();
     int f0x = v->getXForFrame(f0);
     f0 = v->getFrameForX(f0x);
-    size_t f1 = v->getFrameForX(f0x + 1);
+    sv_frame_t f1 = v->getFrameForX(f0x + 1);
     if (f1 > f0) --f1;
 
 //    cerr << "centre frame " << v->getCentreFrame() << ", x " << f0x << ", f0 " << f0 << ", f1 " << f1 << endl;
 
-    size_t res = m_sliceableModel->getResolution();
-    size_t col0 = f0 / res;
-    size_t col1 = col0;
-    if (m_samplingMode != NearestSample) col1 = f1 / res;
+    int res = m_sliceableModel->getResolution();
+    int col0 = int(f0 / res);
+    int col1 = col0;
+    if (m_samplingMode != NearestSample) col1 = int(f1 / res);
     f0 = col0 * res;
     f1 = (col1 + 1) * res - 1;
 
@@ -376,10 +379,10 @@
 
     BiasCurve curve;
     getBiasCurve(curve);
-    size_t cs = curve.size();
+    int cs = int(curve.size());
 
-    for (size_t col = col0; col <= col1; ++col) {
-        for (size_t bin = 0; bin < mh; ++bin) {
+    for (int col = col0; col <= col1; ++col) {
+        for (int bin = 0; bin < mh; ++bin) {
             float value = m_sliceableModel->getValueAt(col, bin);
             if (bin < cs) value *= curve[bin];
             if (m_samplingMode == SamplePeak) {
@@ -391,30 +394,31 @@
         ++divisor;
     }
 
-    float max = 0.f;
-    for (size_t bin = 0; bin < mh; ++bin) {
-        if (m_samplingMode == SampleMean) m_values[bin] /= divisor;
+    float max = 0.0;
+    for (int bin = 0; bin < mh; ++bin) {
+        if (m_samplingMode == SampleMean && divisor > 0) {
+            m_values[bin] /= float(divisor);
+        }
         if (m_values[bin] > max) max = m_values[bin];
     }
-    if (max != 0.f && m_normalize) {
-        for (size_t bin = 0; bin < mh; ++bin) {
+    if (max != 0.0 && m_normalize) {
+        for (int bin = 0; bin < mh; ++bin) {
             m_values[bin] /= max;
         }
     }
 
-    float py = 0;
-    float nx = xorigin;
+    double nx = xorigin;
 
     ColourMapper mapper(m_colourMap, 0, 1);
 
-    for (size_t bin = 0; bin < mh; ++bin) {
+    for (int bin = 0; bin < mh; ++bin) {
 
-        float x = nx;
+        double x = nx;
         nx = xorigin + getXForBin(bin + 1, mh, w);
 
-        float value = m_values[bin];
-        float norm = 0.f;
-        float y = getYForValue(value, v, norm);
+        double value = m_values[bin];
+        double norm = 0.0;
+        double y = getYForValue(value, v, norm);
 
         if (m_plotStyle == PlotLines) {
 
@@ -446,7 +450,6 @@
             paint.fillRect(QRectF(x, y, nx - x, yorigin - y), mapper.map(norm));
         }
 
-        py = y;
     }
 
     if (m_plotStyle != PlotFilledBlocks) {
@@ -498,7 +501,7 @@
 }
 
 int
-SliceLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const
+SliceLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
     if (m_energyScale == LinearScale || m_energyScale == AbsoluteScale) {
 	return std::max(paint.fontMetrics().width("0.0") + 13,
@@ -510,9 +513,9 @@
 }
 
 void
-SliceLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const
+SliceLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
-    float thresh = m_threshold;
+    double thresh = m_threshold;
     if (m_energyScale != LinearScale && m_energyScale != AbsoluteScale) {
         thresh = AudioLevel::dB_to_multiplier(getThresholdDb());
     }
@@ -520,7 +523,7 @@
 //    int h = (rect.height() * 3) / 4;
 //    int y = (rect.height() / 2) - (h / 2);
     
-    int yorigin = v->height() - 20 - paint.fontMetrics().height() - 6;
+    int yorigin = v->getPaintHeight() - 20 - paint.fontMetrics().height() - 6;
     int h = yorigin - paint.fontMetrics().height() - 8;
     if (h < 0) return;
 
@@ -535,7 +538,7 @@
          const_cast<std::vector<int> *>(&m_scalePoints));
 
     if (mult != 1 && mult != 0) {
-        int log = lrintf(log10f(mult));
+        int log = int(lrint(log10(mult)));
         QString a = tr("x10");
         QString b = QString("%1").arg(-log);
         paint.drawText(3, 8 + paint.fontMetrics().ascent(), a);
@@ -624,7 +627,7 @@
 
         cerr << "gain is " << m_gain << ", mode is " << m_samplingMode << endl;
 
-	val = lrint(log10(m_gain) * 20.0);
+	val = int(lrint(log10(m_gain) * 20.0));
 	if (val < *min) val = *min;
 	if (val > *max) val = *max;
 
@@ -633,11 +636,11 @@
 	*min = -80;
 	*max = 0;
 
-        *deflt = lrintf(AudioLevel::multiplier_to_dB(m_initialThreshold));
+        *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold)));
 	if (*deflt < *min) *deflt = *min;
 	if (*deflt > *max) *deflt = *max;
 
-	val = lrintf(AudioLevel::multiplier_to_dB(m_threshold));
+	val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold)));
 	if (val < *min) val = *min;
 	if (val > *max) val = *max;
 
@@ -754,10 +757,10 @@
 SliceLayer::setProperty(const PropertyName &name, int value)
 {
     if (name == "Gain") {
-	setGain(pow(10, float(value)/20.0));
+	setGain(powf(10, float(value)/20.0f));
     } else if (name == "Threshold") {
-	if (value == -80) setThreshold(0.0);
-	else setThreshold(AudioLevel::dB_to_multiplier(value));
+	if (value == -80) setThreshold(0.0f);
+	else setThreshold(float(AudioLevel::dB_to_multiplier(value)));
     } else if (name == "Colour" && m_plotStyle == PlotFilledBlocks) {
         setFillColourMap(value);
     } else if (name == "Scale") {
@@ -864,7 +867,7 @@
 SliceLayer::getThresholdDb() const
 {
     if (m_threshold == 0.0) return -80.f;
-    float db = AudioLevel::multiplier_to_dB(m_threshold);
+    float db = float(AudioLevel::multiplier_to_dB(m_threshold));
     return db;
 }
 
@@ -939,7 +942,7 @@
 }
 
 bool
-SliceLayer::getValueExtents(float &, float &, bool &, QString &) const
+SliceLayer::getValueExtents(double &, double &, bool &, QString &) const
 {
     return false;
 }
--- a/layer/SliceLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SliceLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -38,12 +38,12 @@
 
     void setSliceableModel(const Model *model);    
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourAndBackgroundSignificant;
@@ -62,12 +62,12 @@
     virtual void setProperty(const PropertyName &, int value);
     virtual void setProperties(const QXmlAttributes &);
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
     virtual bool hasTimeXAxis() const { return false; }
 
-    virtual bool isLayerScrollable(const View *) const { return false; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; }
 
     enum EnergyScale { LinearScale, MeterScale, dBScale, AbsoluteScale };
 
@@ -93,7 +93,7 @@
     BinScale getBinScale() const { return m_binScale; }
 
     void setThreshold(float);
-    int getThreshold() const { return m_threshold; }
+    float getThreshold() const { return m_threshold; }
 
     void setGain(float gain);
     float getGain() const;
@@ -109,16 +109,16 @@
     void modelAboutToBeDeleted(Model *);
 
 protected:
-    virtual float getXForBin(int bin, int totalBins, float w) const;
-    virtual int getBinForX(float x, int totalBins, float w) const;
+    virtual double getXForBin(int bin, int totalBins, double w) const;
+    virtual int getBinForX(double x, int totalBins, double w) const;
 
-    virtual float getYForValue(float value, const View *v, float &norm) const;
-    virtual float getValueForY(float y, const View *v) const;
+    virtual double getYForValue(double value, const LayerGeometryProvider *v, double &norm) const;
+    virtual double getValueForY(double y, const LayerGeometryProvider *v) const;
     
-    virtual QString getFeatureDescription(View *v, QPoint &,
-                                          bool includeBinDescription,
-                                          int &minbin, int &maxbin,
-                                          int &range) const;
+    virtual QString getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &,
+                                             bool includeBinDescription,
+                                             int &minbin, int &maxbin,
+                                             int &range) const;
 
     // This curve may, of course, be flat -- the spectrum uses it for
     // normalizing the fft results by the fft size (with 1/(fftsize/2)
@@ -141,11 +141,11 @@
     float                             m_initialThreshold;
     float                             m_gain;
     mutable std::vector<int>          m_scalePoints;
-    mutable std::map<const View *, int> m_xorigins;
-    mutable std::map<const View *, int> m_yorigins;
-    mutable std::map<const View *, int> m_heights;
-    mutable size_t                    m_currentf0;
-    mutable size_t                    m_currentf1;
+    mutable std::map<const LayerGeometryProvider *, int> m_xorigins;
+    mutable std::map<const LayerGeometryProvider *, int> m_yorigins;
+    mutable std::map<const LayerGeometryProvider *, int> m_heights;
+    mutable sv_frame_t                m_currentf0;
+    mutable sv_frame_t                m_currentf1;
     mutable std::vector<float>        m_values;
 };
 
--- a/layer/SpectrogramLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SpectrogramLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -33,16 +33,14 @@
 #include <QImage>
 #include <QPixmap>
 #include <QRect>
-#include <QTimer>
 #include <QApplication>
 #include <QMessageBox>
 #include <QMouseEvent>
 #include <QTextStream>
+#include <QSettings>
 
 #include <iostream>
 
-
-
 #include <cassert>
 #include <cmath>
 
@@ -50,7 +48,9 @@
 #include <alloca.h>
 #endif
 
-//#define DEBUG_SPECTROGRAM_REPAINT 1
+#define DEBUG_SPECTROGRAM_REPAINT 1
+
+using namespace std;
 
 SpectrogramLayer::SpectrogramLayer(Configuration config) :
     m_model(0),
@@ -73,17 +73,16 @@
     m_colourMap(0),
     m_frequencyScale(LinearFrequencyScale),
     m_binDisplay(AllBins),
-    m_normalizeColumns(false),
-    m_normalizeVisibleArea(false),
+    m_normalization(NoNormalization),
     m_lastEmittedZoomStep(-1),
     m_synchronous(false),
     m_haveDetailedScale(false),
-    m_lastPaintBlockWidth(0),
-    m_updateTimer(0),
-    m_candidateFillStartFrame(0),
     m_exiting(false),
     m_sliceableModel(0)
 {
+    QString colourConfigName = "spectrogram-colour";
+    int colourConfigDefault = int(ColourMapper::Green);
+    
     if (config == FullRangeDb) {
         m_initialMaxFrequency = 0;
         setMaxFrequency(0);
@@ -96,6 +95,8 @@
 	setColourScale(LinearColourScale);
         setColourMap(ColourMapper::Sunset);
         setFrequencyScale(LogFrequencyScale);
+        colourConfigName = "spectrogram-melodic-colour";
+        colourConfigDefault = int(ColourMapper::Sunset);
 //        setGain(20);
     } else if (config == MelodicPeaks) {
 	setWindowSize(4096);
@@ -106,9 +107,16 @@
 	setFrequencyScale(LogFrequencyScale);
 	setColourScale(LinearColourScale);
 	setBinDisplay(PeakFrequencies);
-	setNormalizeColumns(true);
+        setNormalization(NormalizeColumns);
+        colourConfigName = "spectrogram-melodic-colour";
+        colourConfigDefault = int(ColourMapper::Sunset);
     }
 
+    QSettings settings;
+    settings.beginGroup("Preferences");
+    setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt());
+    settings.endGroup();
+    
     Preferences *prefs = Preferences::getInstance();
     connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
             this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
@@ -119,9 +127,6 @@
 
 SpectrogramLayer::~SpectrogramLayer()
 {
-    delete m_updateTimer;
-    m_updateTimer = 0;
-    
     invalidateFFTModels();
 }
 
@@ -140,8 +145,8 @@
     connectSignals(m_model);
 
     connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
-    connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
-	    this, SLOT(cacheInvalid(size_t, size_t)));
+    connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+	    this, SLOT(cacheInvalid(sv_frame_t, sv_frame_t)));
 
     emit modelReplaced();
 }
@@ -154,8 +159,7 @@
     list.push_back("Colour Scale");
     list.push_back("Window Size");
     list.push_back("Window Increment");
-    list.push_back("Normalize Columns");
-    list.push_back("Normalize Visible Area");
+    list.push_back("Normalization");
     list.push_back("Bin Display");
     list.push_back("Threshold");
     list.push_back("Gain");
@@ -174,8 +178,7 @@
     if (name == "Colour Scale") return tr("Colour Scale");
     if (name == "Window Size") return tr("Window Size");
     if (name == "Window Increment") return tr("Window Overlap");
-    if (name == "Normalize Columns") return tr("Normalize Columns");
-    if (name == "Normalize Visible Area") return tr("Normalize Visible Area");
+    if (name == "Normalization") return tr("Normalization");
     if (name == "Bin Display") return tr("Bin Display");
     if (name == "Threshold") return tr("Threshold");
     if (name == "Gain") return tr("Gain");
@@ -188,10 +191,8 @@
 }
 
 QString
-SpectrogramLayer::getPropertyIconName(const PropertyName &name) const
+SpectrogramLayer::getPropertyIconName(const PropertyName &) const
 {
-    if (name == "Normalize Columns") return "normalise-columns";
-    if (name == "Normalize Visible Area") return "normalise";
     return "";
 }
 
@@ -200,8 +201,6 @@
 {
     if (name == "Gain") return RangeProperty;
     if (name == "Colour Rotation") return RangeProperty;
-    if (name == "Normalize Columns") return ToggleProperty;
-    if (name == "Normalize Visible Area") return ToggleProperty;
     if (name == "Threshold") return RangeProperty;
     if (name == "Zero Padding") return ToggleProperty;
     return ValueProperty;
@@ -218,8 +217,7 @@
     if (name == "Colour" ||
 	name == "Threshold" ||
 	name == "Colour Rotation") return tr("Colour");
-    if (name == "Normalize Columns" ||
-        name == "Normalize Visible Area" ||
+    if (name == "Normalization" ||
         name == "Gain" ||
 	name == "Colour Scale") return tr("Scale");
     return QString();
@@ -241,11 +239,11 @@
 	*min = -50;
 	*max = 50;
 
-        *deflt = lrintf(log10(m_initialGain) * 20.0);;
+        *deflt = int(lrint(log10(m_initialGain) * 20.0));
 	if (*deflt < *min) *deflt = *min;
 	if (*deflt > *max) *deflt = *max;
 
-	val = lrintf(log10(m_gain) * 20.0);
+	val = int(lrint(log10(m_gain) * 20.0));
 	if (val < *min) val = *min;
 	if (val > *max) val = *max;
 
@@ -254,11 +252,11 @@
 	*min = -50;
 	*max = 0;
 
-        *deflt = lrintf(AudioLevel::multiplier_to_dB(m_initialThreshold));
+        *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold)));
 	if (*deflt < *min) *deflt = *min;
 	if (*deflt > *max) *deflt = *max;
 
-	val = lrintf(AudioLevel::multiplier_to_dB(m_threshold));
+	val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold)));
 	if (val < *min) val = *min;
 	if (val > *max) val = *max;
 
@@ -364,15 +362,12 @@
         *deflt = int(AllBins);
 	val = (int)m_binDisplay;
 
-    } else if (name == "Normalize Columns") {
+    } else if (name == "Normalization") {
 	
-        *deflt = 0;
-	val = (m_normalizeColumns ? 1 : 0);
-
-    } else if (name == "Normalize Visible Area") {
-	
-        *deflt = 0;
-	val = (m_normalizeVisibleArea ? 1 : 0);
+        *min = 0;
+        *max = 3;
+        *deflt = int(NoNormalization);
+        val = (int)m_normalization;
 
     } else {
 	val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
@@ -398,6 +393,9 @@
 	case 4: return tr("Phase");
 	}
     }
+    if (name == "Normalization") {
+        return ""; // icon only
+    }
     if (name == "Window Size") {
 	return QString("%1").arg(32 << value);
     }
@@ -464,6 +462,22 @@
     return tr("<unknown>");
 }
 
+QString
+SpectrogramLayer::getPropertyValueIconName(const PropertyName &name,
+                                           int value) const
+{
+    if (name == "Normalization") {
+        switch(value) {
+        default:
+        case 0: return "normalise-none";
+        case 1: return "normalise-columns";
+        case 2: return "normalise";
+        case 3: return "normalise-hybrid";
+        }
+    }
+    return "";
+}
+
 RangeMapper *
 SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const
 {
@@ -480,10 +494,10 @@
 SpectrogramLayer::setProperty(const PropertyName &name, int value)
 {
     if (name == "Gain") {
-	setGain(pow(10, float(value)/20.0));
+	setGain(float(pow(10, float(value)/20.0)));
     } else if (name == "Threshold") {
 	if (value == -50) setThreshold(0.0);
-	else setThreshold(AudioLevel::dB_to_multiplier(value));
+	else setThreshold(float(AudioLevel::dB_to_multiplier(value)));
     } else if (name == "Colour Rotation") {
 	setColourRotation(value);
     } else if (name == "Colour") {
@@ -554,86 +568,26 @@
 	case 1: setBinDisplay(PeakBins); break;
 	case 2: setBinDisplay(PeakFrequencies); break;
 	}
-    } else if (name == "Normalize Columns") {
-	setNormalizeColumns(value ? true : false);
-    } else if (name == "Normalize Visible Area") {
-	setNormalizeVisibleArea(value ? true : false);
+    } else if (name == "Normalization") {
+        switch (value) {
+        default:
+        case 0: setNormalization(NoNormalization); break;
+        case 1: setNormalization(NormalizeColumns); break;
+        case 2: setNormalization(NormalizeVisibleArea); break;
+        case 3: setNormalization(NormalizeHybrid); break;
+        }
     }
 }
 
 void
 SpectrogramLayer::invalidateImageCaches()
 {
+#ifdef DEBUG_SPECTROGRAM
+    cerr << "SpectrogramLayer::invalidateImageCaches called" << endl;
+#endif
     for (ViewImageCache::iterator i = m_imageCaches.begin();
          i != m_imageCaches.end(); ++i) {
-        i->second.validArea = QRect();
-    }
-}
-
-void
-SpectrogramLayer::invalidateImageCaches(size_t startFrame, size_t endFrame)
-{
-    for (ViewImageCache::iterator i = m_imageCaches.begin();
-         i != m_imageCaches.end(); ++i) {
-
-        //!!! when are views removed from the map? on setLayerDormant?
-        const View *v = i->first;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "SpectrogramLayer::invalidateImageCaches(" 
-                  << startFrame << ", " << endFrame << "): view range is "
-                  << v->getStartFrame() << ", " << v->getEndFrame()
-                  << endl;
-
-        cerr << "Valid area was: " << i->second.validArea.x() << ", "
-                  << i->second.validArea.y() << " "
-                  << i->second.validArea.width() << "x"
-                  << i->second.validArea.height() << endl;
-#endif
-
-        if (long(startFrame) > v->getStartFrame()) {
-            if (startFrame >= v->getEndFrame()) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                cerr << "Modified start frame is off right of view" << endl;
-#endif
-                return;
-            }
-            int x = v->getXForFrame(startFrame);
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "clipping from 0 to " << x-1 << endl;
-#endif
-            if (x > 1) {
-                i->second.validArea &=
-                    QRect(0, 0, x-1, v->height());
-            } else {
-                i->second.validArea = QRect();
-            }
-        } else {
-            if (long(endFrame) < v->getStartFrame()) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                cerr << "Modified end frame is off left of view" << endl;
-#endif
-                return;
-            }
-            int x = v->getXForFrame(endFrame);
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "clipping from " << x+1 << " to " << v->width()
-                      << endl;
-#endif
-            if (x < v->width()) {
-                i->second.validArea &=
-                    QRect(x+1, 0, v->width()-(x+1), v->height());
-            } else {
-                i->second.validArea = QRect();
-            }
-        }
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Valid area is now: " << i->second.validArea.x() << ", "
-                  << i->second.validArea.y() << " "
-                  << i->second.validArea.width() << "x"
-                  << i->second.validArea.height() << endl;
-#endif
+        i->second.invalidate();
     }
 }
 
@@ -680,7 +634,7 @@
 }
 
 void
-SpectrogramLayer::setWindowSize(size_t ws)
+SpectrogramLayer::setWindowSize(int ws)
 {
     if (m_windowSize == ws) return;
 
@@ -694,14 +648,14 @@
     emit layerParametersChanged();
 }
 
-size_t
+int
 SpectrogramLayer::getWindowSize() const
 {
     return m_windowSize;
 }
 
 void
-SpectrogramLayer::setWindowHopLevel(size_t v)
+SpectrogramLayer::setWindowHopLevel(int v)
 {
     if (m_windowHopLevel == v) return;
 
@@ -716,14 +670,14 @@
 //    fillCache();
 }
 
-size_t
+int
 SpectrogramLayer::getWindowHopLevel() const
 {
     return m_windowHopLevel;
 }
 
 void
-SpectrogramLayer::setZeroPadLevel(size_t v)
+SpectrogramLayer::setZeroPadLevel(int v)
 {
     if (m_zeroPadLevel == v) return;
 
@@ -737,7 +691,7 @@
     emit layerParametersChanged();
 }
 
-size_t
+int
 SpectrogramLayer::getZeroPadLevel() const
 {
     return m_zeroPadLevel;
@@ -803,7 +757,7 @@
 }
 
 void
-SpectrogramLayer::setMinFrequency(size_t mf)
+SpectrogramLayer::setMinFrequency(int mf)
 {
     if (m_minFrequency == mf) return;
 
@@ -817,14 +771,14 @@
     emit layerParametersChanged();
 }
 
-size_t
+int
 SpectrogramLayer::getMinFrequency() const
 {
     return m_minFrequency;
 }
 
 void
-SpectrogramLayer::setMaxFrequency(size_t mf)
+SpectrogramLayer::setMaxFrequency(int mf)
 {
     if (m_maxFrequency == mf) return;
 
@@ -838,7 +792,7 @@
     emit layerParametersChanged();
 }
 
-size_t
+int
 SpectrogramLayer::getMaxFrequency() const
 {
     return m_maxFrequency;
@@ -933,51 +887,30 @@
 }
 
 void
-SpectrogramLayer::setNormalizeColumns(bool n)
+SpectrogramLayer::setNormalization(Normalization n)
 {
-    if (m_normalizeColumns == n) return;
+    if (m_normalization == n) return;
 
     invalidateImageCaches();
     invalidateMagnitudes();
-    m_normalizeColumns = n;
+    m_normalization = n;
 
     emit layerParametersChanged();
 }
 
-bool
-SpectrogramLayer::getNormalizeColumns() const
+SpectrogramLayer::Normalization
+SpectrogramLayer::getNormalization() const
 {
-    return m_normalizeColumns;
+    return m_normalization;
 }
 
 void
-SpectrogramLayer::setNormalizeVisibleArea(bool n)
-{
-    SVDEBUG << "SpectrogramLayer::setNormalizeVisibleArea(" << n
-              << ") (from " << m_normalizeVisibleArea << ")" << endl;
-
-    if (m_normalizeVisibleArea == n) return;
-
-    invalidateImageCaches();
-    invalidateMagnitudes();
-    m_normalizeVisibleArea = n;
-
-    emit layerParametersChanged();
-}
-
-bool
-SpectrogramLayer::getNormalizeVisibleArea() const
-{
-    return m_normalizeVisibleArea;
-}
-
-void
-SpectrogramLayer::setLayerDormant(const View *v, bool dormant)
+SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     if (dormant) {
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "SpectrogramLayer::setLayerDormant(" << dormant << ")"
+        cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")"
                   << endl;
 #endif
 
@@ -987,17 +920,20 @@
 
         Layer::setLayerDormant(v, true);
 
+        const View *view = v->getView();
+        
 	invalidateImageCaches();
-        m_imageCaches.erase(v);
-
-        if (m_fftModels.find(v) != m_fftModels.end()) {
-
-            if (m_sliceableModel == m_fftModels[v].first) {
+
+        m_imageCaches.erase(view->getId());
+
+        if (m_fftModels.find(view->getId()) != m_fftModels.end()) {
+
+            if (m_sliceableModel == m_fftModels[view->getId()]) {
                 bool replaced = false;
                 for (ViewFFTMap::iterator i = m_fftModels.begin();
                      i != m_fftModels.end(); ++i) {
-                    if (i->second.first != m_sliceableModel) {
-                        emit sliceableModelReplaced(m_sliceableModel, i->second.first);
+                    if (i->second != m_sliceableModel) {
+                        emit sliceableModelReplaced(m_sliceableModel, i->second);
                         replaced = true;
                         break;
                     }
@@ -1005,11 +941,11 @@
                 if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0);
             }
 
-            delete m_fftModels[v].first;
-            m_fftModels.erase(v);
-
-            delete m_peakCaches[v];
-            m_peakCaches.erase(v);
+            delete m_fftModels[view->getId()];
+            m_fftModels.erase(view->getId());
+
+            delete m_peakCaches[view->getId()];
+            m_peakCaches.erase(view->getId());
         }
 	
     } else {
@@ -1022,7 +958,7 @@
 SpectrogramLayer::cacheInvalid()
 {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::cacheInvalid()" << endl;
+    cerr << "SpectrogramLayer::cacheInvalid()" << endl;
 #endif
 
     invalidateImageCaches();
@@ -1030,84 +966,28 @@
 }
 
 void
-SpectrogramLayer::cacheInvalid(size_t from, size_t to)
+SpectrogramLayer::cacheInvalid(
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    sv_frame_t from, sv_frame_t to
+#else 
+    sv_frame_t     , sv_frame_t
+#endif
+    )
 {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
+    cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
 #endif
 
-    invalidateImageCaches(from, to);
+    // We used to call invalidateMagnitudes(from, to) to invalidate
+    // only those caches whose views contained some of the (from, to)
+    // range. That's the right thing to do; it has been lost in
+    // pulling out the image cache code, but it might not matter very
+    // much, since the underlying models for spectrogram layers don't
+    // change very often. Let's see.
+    invalidateImageCaches();
     invalidateMagnitudes();
 }
 
-void
-SpectrogramLayer::fillTimerTimedOut()
-{
-    if (!m_model) return;
-
-    bool allDone = true;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::fillTimerTimedOut: have " << m_fftModels.size() << " FFT models associated with views" << endl;
-#endif
-
-    for (ViewFFTMap::iterator i = m_fftModels.begin();
-         i != m_fftModels.end(); ++i) {
-
-        const FFTModel *model = i->second.first;
-        size_t lastFill = i->second.second;
-
-        if (model) {
-
-            size_t fill = model->getFillExtent();
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::fillTimerTimedOut: extent for " << model << ": " << fill << ", last " << lastFill << ", total " << m_model->getEndFrame() << endl;
-#endif
-
-            if (fill >= lastFill) {
-                if (fill >= m_model->getEndFrame() && lastFill > 0) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "complete!" << endl;
-#endif
-                    invalidateImageCaches();
-                    i->second.second = -1;
-                    emit modelChanged();
-
-                } else if (fill > lastFill) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "SpectrogramLayer: emitting modelChanged("
-                              << lastFill << "," << fill << ")" << endl;
-#endif
-                    invalidateImageCaches(lastFill, fill);
-                    i->second.second = fill;
-                    emit modelChanged(lastFill, fill);
-                }
-            } else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                cerr << "SpectrogramLayer: going backwards, emitting modelChanged("
-                          << m_model->getStartFrame() << "," << m_model->getEndFrame() << ")" << endl;
-#endif
-                invalidateImageCaches();
-                i->second.second = fill;
-                emit modelChanged(m_model->getStartFrame(), m_model->getEndFrame());
-            }
-
-            if (i->second.second >= 0) {
-                allDone = false;
-            }
-        }
-    }
-
-    if (allDone) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "SpectrogramLayer: all complete!" << endl;
-#endif
-        delete m_updateTimer;
-        m_updateTimer = 0;
-    }
-}
-
 bool
 SpectrogramLayer::hasLightBackground() const 
 {
@@ -1128,7 +1008,7 @@
     ColourMapper mapper(m_colourMap, 1.f, 255.f);
     
     for (int pixel = 1; pixel < 256; ++pixel) {
-        m_palette.setColour(pixel, mapper.map(pixel));
+        m_palette.setColour((unsigned char)pixel, mapper.map(pixel));
     }
 
     m_crosshairColour = mapper.getContrastingColour();
@@ -1151,45 +1031,45 @@
 	int target = pixel + distance;
 	while (target < 1) target += 255;
 	while (target > 255) target -= 255;
-	newPixels[target] = m_palette.getColour(pixel);
+	newPixels[target] = m_palette.getColour((unsigned char)pixel);
     }
 
     for (int pixel = 0; pixel < 256; ++pixel) {
-	m_palette.setColour(pixel, newPixels[pixel]);
+	m_palette.setColour((unsigned char)pixel, newPixels[pixel]);
     }
 
     m_drawBuffer = QImage();
 }
 
 unsigned char
-SpectrogramLayer::getDisplayValue(View *v, float input) const
+SpectrogramLayer::getDisplayValue(LayerGeometryProvider *v, double input) const
 {
     int value;
 
-    float min = 0.f;
-    float max = 1.f;
-
-    if (m_normalizeVisibleArea) {
-        min = m_viewMags[v].getMin();
-        max = m_viewMags[v].getMax();
-    } else if (!m_normalizeColumns) {
+    double min = 0.0;
+    double max = 1.0;
+
+    if (m_normalization == NormalizeVisibleArea) {
+        min = m_viewMags[v->getId()].getMin();
+        max = m_viewMags[v->getId()].getMax();
+    } else if (m_normalization != NormalizeColumns) {
         if (m_colourScale == LinearColourScale //||
 //            m_colourScale == MeterColourScale) {
             ) {
-            max = 0.1f;
+            max = 0.1;
         }
     }
 
-    float thresh = -80.f;
-
-    if (max == 0.f) max = 1.f;
-    if (max == min) min = max - 0.0001f;
+    double thresh = -80.0;
+
+    if (max == 0.0) max = 1.0;
+    if (max == min) min = max - 0.0001;
 
     switch (m_colourScale) {
 	
     default:
     case LinearColourScale:
-        value = int(((input - min) / (max - min)) * 255.f) + 1;
+        value = int(((input - min) / (max - min)) * 255.0) + 1;
 	break;
 	
     case MeterColourScale:
@@ -1199,19 +1079,19 @@
 
     case dBSquaredColourScale:
         input = ((input - min) * (input - min)) / ((max - min) * (max - min));
-        if (input > 0.f) {
-            input = 10.f * log10f(input);
+        if (input > 0.0) {
+            input = 10.0 * log10(input);
         } else {
             input = thresh;
         }
-        if (min > 0.f) {
-            thresh = 10.f * log10f(min * min);
-            if (thresh < -80.f) thresh = -80.f;
+        if (min > 0.0) {
+            thresh = 10.0 * log10(min * min);
+            if (thresh < -80.0) thresh = -80.0;
         }
 	input = (input - thresh) / (-thresh);
-	if (input < 0.f) input = 0.f;
-	if (input > 1.f) input = 1.f;
-	value = int(input * 255.f) + 1;
+	if (input < 0.0) input = 0.0;
+	if (input > 1.0) input = 1.0;
+	value = int(input * 255.0) + 1;
 	break;
 	
     case dBColourScale:
@@ -1219,19 +1099,19 @@
         //In any case, we need to have some indication of what the dB
         //scale is relative to.
         input = (input - min) / (max - min);
-        if (input > 0.f) {
-            input = 10.f * log10f(input);
+        if (input > 0.0) {
+            input = 10.0 * log10(input);
         } else {
             input = thresh;
         }
-        if (min > 0.f) {
-            thresh = 10.f * log10f(min);
-            if (thresh < -80.f) thresh = -80.f;
+        if (min > 0.0) {
+            thresh = 10.0 * log10(min);
+            if (thresh < -80.0) thresh = -80.0;
         }
 	input = (input - thresh) / (-thresh);
-	if (input < 0.f) input = 0.f;
-	if (input > 1.f) input = 1.f;
-	value = int(input * 255.f) + 1;
+	if (input < 0.0) input = 0.0;
+	if (input > 1.0) input = 1.0;
+	value = int(input * 255.0) + 1;
 	break;
 	
     case PhaseColourScale:
@@ -1241,61 +1121,17 @@
 
     if (value > UCHAR_MAX) value = UCHAR_MAX;
     if (value < 0) value = 0;
-    return value;
+    return (unsigned char)value;
 }
 
-float
-SpectrogramLayer::getInputForDisplayValue(unsigned char uc) const
-{
-    //!!! unused
-
-    int value = uc;
-    float input;
-
-    //!!! incorrect for normalizing visible area (and also out of date)
-    
-    switch (m_colourScale) {
-	
-    default:
-    case LinearColourScale:
-	input = float(value - 1) / 255.0 / (m_normalizeColumns ? 1 : 50);
-	break;
-    
-    case MeterColourScale:
-	input = AudioLevel::preview_to_multiplier(value - 1, 255)
-	    / (m_normalizeColumns ? 1.0 : 50.0);
-	break;
-
-    case dBSquaredColourScale:
-	input = float(value - 1) / 255.0;
-	input = (input * 80.0) - 80.0;
-	input = powf(10.0, input) / 20.0;
-	value = int(input);
-	break;
-
-    case dBColourScale:
-	input = float(value - 1) / 255.0;
-	input = (input * 80.0) - 80.0;
-	input = powf(10.0, input) / 20.0;
-	value = int(input);
-	break;
-
-    case PhaseColourScale:
-	input = float(value - 128) * M_PI / 127.0;
-	break;
-    }
-
-    return input;
-}
-
-float
+double
 SpectrogramLayer::getEffectiveMinFrequency() const
 {
-    int sr = m_model->getSampleRate();
-    float minf = float(sr) / m_fftSize;
+    sv_samplerate_t sr = m_model->getSampleRate();
+    double minf = double(sr) / m_fftSize;
 
     if (m_minFrequency > 0.0) {
-	size_t minbin = size_t((double(m_minFrequency) * m_fftSize) / sr + 0.01);
+	int minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.01);
 	if (minbin < 1) minbin = 1;
 	minf = minbin * sr / m_fftSize;
     }
@@ -1303,14 +1139,14 @@
     return minf;
 }
 
-float
+double
 SpectrogramLayer::getEffectiveMaxFrequency() const
 {
-    int sr = m_model->getSampleRate();
-    float maxf = float(sr) / 2;
+    sv_samplerate_t sr = m_model->getSampleRate();
+    double maxf = double(sr) / 2;
 
     if (m_maxFrequency > 0.0) {
-	size_t maxbin = size_t((double(m_maxFrequency) * m_fftSize) / sr + 0.1);
+	int maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1);
 	if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2;
 	maxf = maxbin * sr / m_fftSize;
     }
@@ -1319,16 +1155,16 @@
 }
 
 bool
-SpectrogramLayer::getYBinRange(View *v, int y, float &q0, float &q1) const
+SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
 {
     Profiler profiler("SpectrogramLayer::getYBinRange");
     
-    int h = v->height();
+    int h = v->getPaintHeight();
     if (y < 0 || y >= h) return false;
 
-    int sr = m_model->getSampleRate();
-    float minf = getEffectiveMinFrequency();
-    float maxf = getEffectiveMaxFrequency();
+    sv_samplerate_t sr = m_model->getSampleRate();
+    double minf = getEffectiveMinFrequency();
+    double maxf = getEffectiveMaxFrequency();
 
     bool logarithmic = (m_frequencyScale == LogFrequencyScale);
 
@@ -1345,16 +1181,16 @@
 }
 
 bool
-SpectrogramLayer::getSmoothedYBinRange(View *v, int y, float &q0, float &q1) const
+SpectrogramLayer::getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
 {
     Profiler profiler("SpectrogramLayer::getSmoothedYBinRange");
 
-    int h = v->height();
+    int h = v->getPaintHeight();
     if (y < 0 || y >= h) return false;
 
-    int sr = m_model->getSampleRate();
-    float minf = getEffectiveMinFrequency();
-    float maxf = getEffectiveMaxFrequency();
+    sv_samplerate_t sr = m_model->getSampleRate();
+    double minf = getEffectiveMinFrequency();
+    double maxf = getEffectiveMaxFrequency();
 
     bool logarithmic = (m_frequencyScale == LogFrequencyScale);
 
@@ -1371,14 +1207,14 @@
 }
     
 bool
-SpectrogramLayer::getXBinRange(View *v, int x, float &s0, float &s1) const
+SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const
 {
-    size_t modelStart = m_model->getStartFrame();
-    size_t modelEnd = m_model->getEndFrame();
+    sv_frame_t modelStart = m_model->getStartFrame();
+    sv_frame_t modelEnd = m_model->getEndFrame();
 
     // Each pixel column covers an exact range of sample frames:
-    int f0 = v->getFrameForX(x) - modelStart;
-    int f1 = v->getFrameForX(x + 1) - modelStart - 1;
+    sv_frame_t f0 = v->getFrameForX(x) - modelStart;
+    sv_frame_t f1 = v->getFrameForX(x + 1) - modelStart - 1;
 
     if (f1 < int(modelStart) || f0 > int(modelEnd)) {
 	return false;
@@ -1387,17 +1223,17 @@
     // And that range may be drawn from a possibly non-integral
     // range of spectrogram windows:
 
-    size_t windowIncrement = getWindowIncrement();
-    s0 = float(f0) / windowIncrement;
-    s1 = float(f1) / windowIncrement;
+    int windowIncrement = getWindowIncrement();
+    s0 = double(f0) / windowIncrement;
+    s1 = double(f1) / windowIncrement;
 
     return true;
 }
  
 bool
-SpectrogramLayer::getXBinSourceRange(View *v, int x, RealTime &min, RealTime &max) const
+SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
 {
-    float s0 = 0, s1 = 0;
+    double s0 = 0, s1 = 0;
     if (!getXBinRange(v, x, s0, s1)) return false;
     
     int s0i = int(s0 + 0.001);
@@ -1414,16 +1250,16 @@
 }
 
 bool
-SpectrogramLayer::getYBinSourceRange(View *v, int y, float &freqMin, float &freqMax)
+SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
 const
 {
-    float q0 = 0, q1 = 0;
+    double q0 = 0, q1 = 0;
     if (!getYBinRange(v, y, q0, q1)) return false;
 
     int q0i = int(q0 + 0.001);
     int q1i = int(q1);
 
-    int sr = m_model->getSampleRate();
+    sv_samplerate_t sr = m_model->getSampleRate();
 
     for (int q = q0i; q <= q1i; ++q) {
 	if (q == q0i) freqMin = (sr * q) / m_fftSize;
@@ -1433,9 +1269,9 @@
 }
 
 bool
-SpectrogramLayer::getAdjustedYBinSourceRange(View *v, int x, int y,
-					     float &freqMin, float &freqMax,
-					     float &adjFreqMin, float &adjFreqMax)
+SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y,
+					     double &freqMin, double &freqMax,
+					     double &adjFreqMin, double &adjFreqMax)
 const
 {
     if (!m_model || !m_model->isOK() || !m_model->isReady()) {
@@ -1445,10 +1281,10 @@
     FFTModel *fft = getFFTModel(v);
     if (!fft) return false;
 
-    float s0 = 0, s1 = 0;
+    double s0 = 0, s1 = 0;
     if (!getXBinRange(v, x, s0, s1)) return false;
 
-    float q0 = 0, q1 = 0;
+    double q0 = 0, q1 = 0;
     if (!getYBinRange(v, y, q0, q1)) return false;
 
     int s0i = int(s0 + 0.001);
@@ -1457,10 +1293,7 @@
     int q0i = int(q0 + 0.001);
     int q1i = int(q1);
 
-    int sr = m_model->getSampleRate();
-
-    size_t windowSize = m_windowSize;
-    size_t windowIncrement = getWindowIncrement();
+    sv_samplerate_t sr = m_model->getSampleRate();
 
     bool haveAdj = false;
 
@@ -1471,18 +1304,15 @@
 
 	for (int s = s0i; s <= s1i; ++s) {
 
-            if (!fft->isColumnAvailable(s)) continue;
-
-	    float binfreq = (sr * q) / m_windowSize;
+	    double binfreq = (double(sr) * q) / m_windowSize;
 	    if (q == q0i) freqMin = binfreq;
 	    if (q == q1i) freqMax = binfreq;
 
 	    if (peaksOnly && !fft->isLocalPeak(s, q)) continue;
 
-	    if (!fft->isOverThreshold(s, q, m_threshold * (m_fftSize/2))) continue;
-
-	    float freq = binfreq;
-	    bool steady = false;
+	    if (!fft->isOverThreshold(s, q, float(m_threshold * double(m_fftSize)/2.0))) continue;
+
+            double freq = binfreq;
 	    
 	    if (s < int(fft->getWidth()) - 1) {
 
@@ -1504,18 +1334,18 @@
 }
     
 bool
-SpectrogramLayer::getXYBinSourceRange(View *v, int x, int y,
-				      float &min, float &max,
-				      float &phaseMin, float &phaseMax) const
+SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y,
+				      double &min, double &max,
+				      double &phaseMin, double &phaseMax) const
 {
     if (!m_model || !m_model->isOK() || !m_model->isReady()) {
 	return false;
     }
 
-    float q0 = 0, q1 = 0;
+    double q0 = 0, q1 = 0;
     if (!getYBinRange(v, y, q0, q1)) return false;
 
-    float s0 = 0, s1 = 0;
+    double s0 = 0, s1 = 0;
     if (!getXBinRange(v, x, s0, s1)) return false;
     
     int q0i = int(q0 + 0.001);
@@ -1526,7 +1356,7 @@
 
     bool rv = false;
 
-    size_t zp = getZeroPadLevel(v);
+    int zp = getZeroPadLevel(v);
     q0i *= zp + 1;
     q1i *= zp + 1;
 
@@ -1547,15 +1377,13 @@
             for (int s = s0i; s <= s1i; ++s) {
                 if (s >= 0 && q >= 0 && s < cw && q < ch) {
 
-                    if (!fft->isColumnAvailable(s)) continue;
-                    
-                    float value;
+                    double value;
 
                     value = fft->getPhaseAt(s, q);
                     if (!have || value < phaseMin) { phaseMin = value; }
                     if (!have || value > phaseMax) { phaseMax = value; }
 
-                    value = fft->getMagnitudeAt(s, q) / (m_fftSize/2);
+                    value = fft->getMagnitudeAt(s, q) / (m_fftSize/2.0);
                     if (!have || value < min) { min = value; }
                     if (!have || value > max) { max = value; }
                     
@@ -1572,8 +1400,8 @@
     return rv;
 }
    
-size_t
-SpectrogramLayer::getZeroPadLevel(const View *v) const
+int
+SpectrogramLayer::getZeroPadLevel(const LayerGeometryProvider *v) const
 {
     //!!! tidy all this stuff
 
@@ -1587,24 +1415,24 @@
 
     if (m_frequencyScale == LogFrequencyScale) return 3;
 
-    int sr = m_model->getSampleRate();
+    sv_samplerate_t sr = m_model->getSampleRate();
     
-    size_t maxbin = m_fftSize / 2;
+    int maxbin = m_fftSize / 2;
     if (m_maxFrequency > 0) {
 	maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1);
 	if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2;
     }
 
-    size_t minbin = 1;
+    int minbin = 1;
     if (m_minFrequency > 0) {
 	minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1);
 	if (minbin < 1) minbin = 1;
 	if (minbin >= maxbin) minbin = maxbin - 1;
     }
 
-    float perPixel =
-        float(v->height()) /
-        float((maxbin - minbin) / (m_zeroPadLevel + 1));
+    double perPixel =
+        double(v->getPaintHeight()) /
+        double((maxbin - minbin) / (m_zeroPadLevel + 1));
 
     if (perPixel > 2.8) {
         return 3; // 4x oversampling
@@ -1615,53 +1443,52 @@
     }
 }
 
-size_t
-SpectrogramLayer::getFFTSize(const View *v) const
+int
+SpectrogramLayer::getFFTSize(const LayerGeometryProvider *v) const
 {
     return m_fftSize * (getZeroPadLevel(v) + 1);
 }
 	
 FFTModel *
-SpectrogramLayer::getFFTModel(const View *v) const
+SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const
 {
     if (!m_model) return 0;
 
-    size_t fftSize = getFFTSize(v);
-
-    if (m_fftModels.find(v) != m_fftModels.end()) {
-        if (m_fftModels[v].first == 0) {
+    int fftSize = getFFTSize(v);
+
+    const View *view = v->getView();
+    
+    if (m_fftModels.find(view->getId()) != m_fftModels.end()) {
+        if (m_fftModels[view->getId()] == 0) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl;
+            cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl;
 #endif
             return 0;
         }
-        if (m_fftModels[v].first->getHeight() != fftSize / 2 + 1) {
+        if (m_fftModels[view->getId()]->getHeight() != fftSize / 2 + 1) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[v].first->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl;
+            cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view->getId()]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl;
 #endif
-            delete m_fftModels[v].first;
-            m_fftModels.erase(v);
-            delete m_peakCaches[v];
-            m_peakCaches.erase(v);
+            delete m_fftModels[view->getId()];
+            m_fftModels.erase(view->getId());
+            delete m_peakCaches[view->getId()];
+            m_peakCaches.erase(view->getId());
         } else {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[v].first->getHeight() << endl;
+            cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view->getId()]->getHeight() << endl;
 #endif
-            return m_fftModels[v].first;
+            return m_fftModels[view->getId()];
         }
     }
 
-    if (m_fftModels.find(v) == m_fftModels.end()) {
+    if (m_fftModels.find(view->getId()) == m_fftModels.end()) {
 
         FFTModel *model = new FFTModel(m_model,
                                        m_channel,
                                        m_windowType,
                                        m_windowSize,
                                        getWindowIncrement(),
-                                       fftSize,
-                                       true, // polar
-                                       StorageAdviser::SpeedCritical,
-                                       m_candidateFillStartFrame);
+                                       fftSize);
 
         if (!model->isOK()) {
             QMessageBox::critical
@@ -1669,7 +1496,7 @@
                  tr("Failed to create the FFT model for this spectrogram.\n"
                     "There may be insufficient memory or disc space to continue."));
             delete model;
-            m_fftModels[v] = FFTFillPair(0, 0);
+            m_fftModels[view->getId()] = 0;
             return 0;
         }
 
@@ -1681,29 +1508,22 @@
             m_sliceableModel = model;
         }
 
-        m_fftModels[v] = FFTFillPair(model, 0);
-
-        model->resume();
-        
-        delete m_updateTimer;
-        m_updateTimer = new QTimer((SpectrogramLayer *)this);
-        connect(m_updateTimer, SIGNAL(timeout()),
-                this, SLOT(fillTimerTimedOut()));
-        m_updateTimer->start(200);
+        m_fftModels[view->getId()] = model;
     }
 
-    return m_fftModels[v].first;
+    return m_fftModels[view->getId()];
 }
 
 Dense3DModelPeakCache *
-SpectrogramLayer::getPeakCache(const View *v) const
+SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const
 {
-    if (!m_peakCaches[v]) {
+    const View *view = v->getView();
+    if (!m_peakCaches[view->getId()]) {
         FFTModel *f = getFFTModel(v);
         if (!f) return 0;
-        m_peakCaches[v] = new Dense3DModelPeakCache(f, 8);
+        m_peakCaches[view->getId()] = new Dense3DModelPeakCache(f, 8);
     }
-    return m_peakCaches[v];
+    return m_peakCaches[view->getId()];
 }
 
 const Model *
@@ -1711,16 +1531,19 @@
 {
     if (m_sliceableModel) return m_sliceableModel;
     if (m_fftModels.empty()) return 0;
-    m_sliceableModel = m_fftModels.begin()->second.first;
+    m_sliceableModel = m_fftModels.begin()->second;
     return m_sliceableModel;
 }
 
 void
 SpectrogramLayer::invalidateFFTModels()
 {
+#ifdef DEBUG_SPECTROGRAM
+    cerr << "SpectrogramLayer::invalidateFFTModels called" << endl;
+#endif
     for (ViewFFTMap::iterator i = m_fftModels.begin();
          i != m_fftModels.end(); ++i) {
-        delete i->second.first;
+        delete i->second;
     }
     for (PeakCacheMap::iterator i = m_peakCaches.begin();
          i != m_peakCaches.end(); ++i) {
@@ -1740,31 +1563,34 @@
 void
 SpectrogramLayer::invalidateMagnitudes()
 {
+#ifdef DEBUG_SPECTROGRAM
+    cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl;
+#endif
     m_viewMags.clear();
-    for (std::vector<MagnitudeRange>::iterator i = m_columnMags.begin();
+    for (vector<MagnitudeRange>::iterator i = m_columnMags.begin();
          i != m_columnMags.end(); ++i) {
         *i = MagnitudeRange();
     }
 }
 
 bool
-SpectrogramLayer::updateViewMagnitudes(View *v) const
+SpectrogramLayer::updateViewMagnitudes(LayerGeometryProvider *v) const
 {
     MagnitudeRange mag;
 
-    int x0 = 0, x1 = v->width();
-    float s00 = 0, s01 = 0, s10 = 0, s11 = 0;
+    int x0 = 0, x1 = v->getPaintWidth();
+    double s00 = 0, s01 = 0, s10 = 0, s11 = 0;
     
     if (!getXBinRange(v, x0, s00, s01)) {
-        s00 = s01 = m_model->getStartFrame() / getWindowIncrement();
+        s00 = s01 = double(m_model->getStartFrame()) / getWindowIncrement();
     }
 
     if (!getXBinRange(v, x1, s10, s11)) {
-        s10 = s11 = m_model->getEndFrame() / getWindowIncrement();
+        s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement();
     }
 
-    int s0 = int(std::min(s00, s10) + 0.0001);
-    int s1 = int(std::max(s01, s11) + 0.0001);
+    int s0 = int(min(s00, s10) + 0.0001);
+    int s1 = int(max(s01, s11) + 0.0001);
 
 //    SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl;
 
@@ -1779,13 +1605,15 @@
     }
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::updateViewMagnitudes returning from cols "
-              << s0 << " -> " << s1 << " inclusive" << endl;
+    cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols "
+         << s0 << " -> " << s1 << " inclusive" << endl;
+    cerr << "SpectrogramLayer::updateViewMagnitudes: for view id " << v->getId()
+         << ": min is " << mag.getMin() << ", max is " << mag.getMax() << endl;
 #endif
 
     if (!mag.isSet()) return false;
-    if (mag == m_viewMags[v]) return false;
-    m_viewMags[v] = mag;
+    if (mag == m_viewMags[v->getId()]) return false;
+    m_viewMags[v->getId()] = mag;
     return true;
 }
 
@@ -1795,23 +1623,27 @@
     m_synchronous = synchronous;
 }
 
+ScrollableImageCache &
+SpectrogramLayer::getImageCacheReference(const LayerGeometryProvider *view) const
+{
+    if (m_imageCaches.find(view->getId()) == m_imageCaches.end()) {
+        m_imageCaches[view->getId()] = ScrollableImageCache(view);
+    }
+    return m_imageCaches.at(view->getId());
+}
+
 void
-SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const
+SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    // What a lovely, old-fashioned function this is.
-    // It's practically FORTRAN 77 in its clarity and linearity.
-
     Profiler profiler("SpectrogramLayer::paint", false);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << endl;
+    cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl;
     
-    cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
+    cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
 #endif
 
-    long startFrame = v->getStartFrame();
-    if (startFrame < 0) m_candidateFillStartFrame = 0;
-    else m_candidateFillStartFrame = startFrame;
+    sv_frame_t startFrame = v->getStartFrame();
 
     if (!m_model || !m_model->isOK() || !m_model->isReady()) {
 	return;
@@ -1825,317 +1657,142 @@
     // is not in the dormancy map at all -- we need it to be present
     // and accountable for when determining whether we need the cache
     // in the cache-fill thread above.
-    //!!! no longer use cache-fill thread
+    //!!! no inter use cache-fill thread
     const_cast<SpectrogramLayer *>(this)->Layer::setLayerDormant(v, false);
 
-    size_t fftSize = getFFTSize(v);
-/*
-    FFTModel *fft = getFFTModel(v);
-    if (!fft) {
-	cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << endl;
-	return;
+    int fftSize = getFFTSize(v);
+
+    const View *view = v->getView();
+    ScrollableImageCache &cache = getImageCacheReference(view);
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    cerr << "SpectrogramLayer::paint(): image cache valid area from " << cache.getValidLeft() << " width " << cache.getValidWidth() << ", height " << cache.getSize().height() << endl;
+    if (rect.x() + rect.width() + 1 < cache.getValidLeft() ||
+        rect.x() > cache.getValidRight()) {
+        cerr << "SpectrogramLayer: NOTE: requested rect is not contiguous with cache valid area" << endl;
     }
-*/
-    ImageCache &cache = m_imageCaches[v];
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::paint(): image cache valid area " << cache.
-
-validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl;
 #endif
 
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    bool stillCacheing = (m_updateTimer != 0);
-    SVDEBUG << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << endl;
-#endif
-
     int zoomLevel = v->getZoomLevel();
 
-    int x0 = 0;
-    int x1 = v->width();
-
-    bool recreateWholeImageCache = true;
-
-    x0 = rect.left();
-    x1 = rect.right() + 1;
-/*
-    float xPixelRatio = float(fft->getResolution()) / float(zoomLevel);
-    cerr << "xPixelRatio = " << xPixelRatio << endl;
-    if (xPixelRatio < 1.f) xPixelRatio = 1.f;
-*/
-    if (cache.validArea.width() > 0) {
-
-        int cw = cache.image.width();
-        int ch = cache.image.height();
-        
-	if (int(cache.zoomLevel) == zoomLevel &&
-	    cw == v->width() &&
-	    ch == v->height()) {
-
-	    if (v->getXForFrame(cache.startFrame) ==
-		v->getXForFrame(startFrame) &&
-                cache.validArea.x() <= x0 &&
-                cache.validArea.x() + cache.validArea.width() >= x1) {
-	    
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-		cerr << "SpectrogramLayer: image cache good" << endl;
-#endif
-
-		paint.drawImage(rect, cache.image, rect);
-                //!!!
-//                paint.drawImage(v->rect(), cache.image,
-//                                QRect(QPoint(0, 0), cache.image.size()));
-
-                illuminateLocalFeatures(v, paint);
-		return;
-
-	    } else {
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-		cerr << "SpectrogramLayer: image cache partially OK" << endl;
-#endif
-
-		recreateWholeImageCache = false;
-
-		int dx = v->getXForFrame(cache.startFrame) -
-		         v->getXForFrame(startFrame);
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-		cerr << "SpectrogramLayer: dx = " << dx << " (image cache " << cw << "x" << ch << ")" << endl;
-#endif
-
-		if (dx != 0 &&
-                    dx > -cw &&
-                    dx <  cw) {
-                    
-                    int dxp = dx;
-                    if (dxp < 0) dxp = -dxp;
-                    int copy = (cw - dxp) * sizeof(QRgb);
-                    for (int y = 0; y < ch; ++y) {
-                        QRgb *line = (QRgb *)cache.image.scanLine(y);
-                        if (dx < 0) {
-                            memmove(line, line + dxp, copy);
-                        } else {
-                            memmove(line + dxp, line, copy);
-                        }
-                    }
-
-                    int px = cache.validArea.x();
-                    int pw = cache.validArea.width();
-
-		    if (dx < 0) {
-			x0 = cw + dx;
-			x1 = cw;
-                        px += dx;
-                        if (px < 0) {
-                            pw += px;
-                            px = 0;
-                            if (pw < 0) pw = 0;
-                        }
-		    } else {
-			x0 = 0;
-			x1 = dx;
-                        px += dx;
-                        if (px + pw > cw) {
-                            pw = int(cw) - px;
-                            if (pw < 0) pw = 0;
-                        }
-		    }
-                    
-                    cache.validArea =
-                        QRect(px, cache.validArea.y(),
-                              pw, cache.validArea.height());
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "valid area now "
-                              << px << "," << cache.validArea.y()
-                              << " " << pw << "x" << cache.validArea.height()
-                              << endl;
-#endif
-/*
-		    paint.drawImage(rect & cache.validArea,
-                                     cache.image,
-                                     rect & cache.validArea);
-*/
-                } else if (dx != 0) {
-
-                    // we scrolled too far to be of use
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "dx == " << dx << ": scrolled too far for cache to be useful" << endl;
-#endif
-
-                    cache.validArea = QRect();
-                    recreateWholeImageCache = true;
-                }
-	    }
-	} else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-	    cerr << "SpectrogramLayer: image cache useless" << endl;
-            if (int(cache.zoomLevel) != zoomLevel) {
-                cerr << "(cache zoomLevel " << cache.zoomLevel
-                          << " != " << zoomLevel << ")" << endl;
-            }
-            if (cw != v->width()) {
-                cerr << "(cache width " << cw
-                          << " != " << v->width();
-            }
-            if (ch != v->height()) {
-                cerr << "(cache height " << ch
-                          << " != " << v->height();
-            }
-#endif
-            cache.validArea = QRect();
-//            recreateWholeImageCache = true;
-	}
-    }
+    int x0 = v->getXForViewX(rect.x());
+    int x1 = v->getXForViewX(rect.x() + rect.width());
+    if (x0 < 0) x0 = 0;
+    if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth();
 
     if (updateViewMagnitudes(v)) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl;
+        cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "]" << endl;
 #endif
-        if (m_normalizeVisibleArea) {
-            cache.validArea = QRect();
-            recreateWholeImageCache = true;
+        if (m_normalization == NormalizeVisibleArea) {
+            cache.invalidate();
+        }
+    }
+
+    if (cache.getZoomLevel() != zoomLevel ||
+        cache.getSize() != v->getPaintSize()) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        cerr << "SpectrogramLayer: resizing image cache from "
+             << cache.getSize().width() << "x" << cache.getSize().height()
+             << " to "
+             << v->getPaintSize().width() << "x" << v->getPaintSize().height()
+             << " and updating zoom level from " << cache.getZoomLevel()
+             << " to " << zoomLevel
+             << endl;
+#endif
+        cache.resize(v->getPaintSize());
+        cache.setZoomLevel(zoomLevel);
+        cache.setStartFrame(startFrame);
+    }
+    
+    if (cache.isValid()) {
+        
+        if (v->getXForFrame(cache.getStartFrame()) ==
+            v->getXForFrame(startFrame) &&
+            cache.getValidLeft() <= x0 &&
+            cache.getValidRight() >= x1) {
+                
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+            cerr << "SpectrogramLayer: image cache hit!" << endl;
+#endif
+
+            paint.drawImage(rect, cache.getImage(), rect);
+
+            illuminateLocalFeatures(v, paint);
+            return;
+
+        } else {
+
+            // cache doesn't begin at the right frame or doesn't
+            // contain the complete view, but might be scrollable or
+            // partially usable
+                
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+            cerr << "SpectrogramLayer: scrolling the image cache if applicable" << endl;
+#endif
+
+            cache.scrollTo(startFrame);
+            
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+            cerr << "SpectrogramLayer: after scrolling, cache valid from "
+                 << cache.getValidLeft() << " width " << cache.getValidWidth()
+                 << endl;
+#endif
+        }
+    }
+
+    bool rightToLeft = false;
+    
+    if (!cache.isValid()) {
+        if (!m_synchronous) {
+            // When rendering the whole thing, start from somewhere near
+            // the middle so that the region of interest appears first
+
+            //!!! (perhaps we should have some cunning test to avoid
+            //!!! doing this if past repaints have appeared fast
+            //!!! enough to do the whole width in one shot)
+            if (x0 == 0 && x1 == v->getPaintWidth()) {
+                x0 = int(x1 * 0.3);
+            }
         }
     } else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl;
-#endif
+        // When rendering only a part of the cache, we need to make
+        // sure that the part we're rendering is adjacent to (or
+        // overlapping) a valid area of cache, if we have one. The
+        // alternative is to ditch the valid area of cache and render
+        // only the requested area, but that's risky because this can
+        // happen when just waving the pointer over a small part of
+        // the view -- if we lose the partly-built cache every time
+        // the user does that, we'll never finish building it.
+        int left = x0;
+        int width = x1 - x0;
+        bool isLeftOfValidArea = false;
+        cache.adjustToTouchValidArea(left, width, isLeftOfValidArea);
+        x0 = left;
+        x1 = x0 + width;
+
+        // That call also told us whether we should be painting
+        // sub-regions of our target region in right-to-left order in
+        // order to ensure contiguity
+        rightToLeft = isLeftOfValidArea;
     }
-
-    if (recreateWholeImageCache) {
-        x0 = 0;
-        x1 = v->width();
-    }
-
-    struct timeval tv;
-    (void)gettimeofday(&tv, 0);
-    RealTime mainPaintStart = RealTime::fromTimeval(tv);
-
-    int paintBlockWidth = m_lastPaintBlockWidth;
-
-    if (m_synchronous) {
-        if (paintBlockWidth < x1 - x0) {
-            // always paint full width
-            paintBlockWidth = x1 - x0;
-        }
-    } else {
-        if (paintBlockWidth == 0) {
-            paintBlockWidth = (300000 / zoomLevel);
-        } else {
-            RealTime lastTime = m_lastPaintTime;
-            while (lastTime > RealTime::fromMilliseconds(200) &&
-                   paintBlockWidth > 50) {
-                paintBlockWidth /= 2;
-                lastTime = lastTime / 2;
-            }
-            while (lastTime < RealTime::fromMilliseconds(90) &&
-                   paintBlockWidth < 1500) {
-                paintBlockWidth *= 2;
-                lastTime = lastTime * 2;
-            }
-        }
-        
-        if (paintBlockWidth < 20) paintBlockWidth = 20;
-    }
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "[" << this << "]: last paint width: " << m_lastPaintBlockWidth << ", last paint time: " << m_lastPaintTime << ", new paint width: " << paintBlockWidth << endl;
-#endif
-
+    
     // We always paint the full height when refreshing the cache.
     // Smaller heights can be used when painting direct from cache
     // (further up in this function), but we want to ensure the cache
     // is coherent without having to worry about vertical matching of
     // required and valid areas as well as horizontal.
-
-    int h = v->height();
-
-    if (cache.validArea.width() > 0) {
-
-        // If part of the cache is known to be valid, select a strip
-        // immediately to left or right of the valid part
-
-        //!!! this really needs to be coordinated with the selection
-        //!!! of m_drawBuffer boundaries in the bufferBinResolution
-        //!!! case below
-
-        int vx0 = 0, vx1 = 0;
-        vx0 = cache.validArea.x();
-        vx1 = cache.validArea.x() + cache.validArea.width();
-        
+    int h = v->getPaintHeight();
+    
+    int repaintWidth = x1 - x0;
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "x0 " << x0 << ", x1 " << x1 << ", vx0 " << vx0 << ", vx1 " << vx1 << ", paintBlockWidth " << paintBlockWidth << endl;
-#endif         
-        if (x0 < vx0) {
-            if (x0 + paintBlockWidth < vx0) {
-                x0 = vx0 - paintBlockWidth;
-            }
-            x1 = vx0;
-        } else if (x0 >= vx1) {
-            x0 = vx1;
-            if (x1 > x0 + paintBlockWidth) {
-                x1 = x0 + paintBlockWidth;
-            }
-        } else {
-            // x0 is within the valid area
-            if (x1 > vx1) {
-                x0 = vx1;
-                if (x0 + paintBlockWidth < x1) {
-                    x1 = x0 + paintBlockWidth;
-                }
-            } else {
-                x1 = x0; // it's all valid, paint nothing
-            }
-        }
-         
-        cache.validArea = QRect
-            (std::min(vx0, x0), cache.validArea.y(),
-             std::max(vx1 - std::min(vx0, x0),
-                       x1 - std::min(vx0, x0)),
-             cache.validArea.height());
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Valid area becomes " << cache.validArea.x()
-                  << ", " << cache.validArea.y() << ", "
-                  << cache.validArea.width() << "x"
-                  << cache.validArea.height() << endl;
+    cerr << "SpectrogramLayer: x0 " << x0 << ", x1 " << x1
+         << ", repaintWidth " << repaintWidth << ", h " << h
+         << ", rightToLeft " << rightToLeft << endl;
 #endif
-            
-    } else {
-        if (x1 > x0 + paintBlockWidth) {
-            int sfx = x1;
-            if (startFrame < 0) sfx = v->getXForFrame(0);
-            if (sfx >= x0 && sfx + paintBlockWidth <= x1) {
-                x0 = sfx;
-                x1 = x0 + paintBlockWidth;
-            } else {
-                int mid = (x1 + x0) / 2;
-                x0 = mid - paintBlockWidth/2;
-                x1 = x0 + paintBlockWidth;
-            }
-        }
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Valid area becomes " << x0 << ", 0, " << (x1-x0)
-                  << "x" << h << endl;
-#endif
-        cache.validArea = QRect(x0, 0, x1 - x0, h);
-    }
-
-/*
-    if (xPixelRatio != 1.f) {
-        x0 = int((int(x0 / xPixelRatio) - 4) * xPixelRatio + 0.0001);
-        x1 = int((int(x1 / xPixelRatio) + 4) * xPixelRatio + 0.0001);
-    }
-*/
-    int w = x1 - x0;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << endl;
-#endif
-
-    int sr = m_model->getSampleRate();
+
+    sv_samplerate_t sr = m_model->getSampleRate();
 
     // Set minFreq and maxFreq to the frequency extents of the possibly
     // zero-padded visible bin range, and displayMinFreq and displayMaxFreq
@@ -2148,13 +1805,13 @@
     // Note fftSize is the actual zero-padded fft size, m_fftSize the
     // nominal fft size.
     
-    size_t maxbin = m_fftSize / 2;
+    int maxbin = m_fftSize / 2;
     if (m_maxFrequency > 0) {
 	maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001);
 	if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2;
     }
 
-    size_t minbin = 1;
+    int minbin = 1;
     if (m_minFrequency > 0) {
 	minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.001);
 //        cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl;
@@ -2166,11 +1823,11 @@
     minbin = minbin * zpl;
     maxbin = (maxbin + 1) * zpl - 1;
 
-    float minFreq = (float(minbin) * sr) / fftSize;
-    float maxFreq = (float(maxbin) * sr) / fftSize;
-
-    float displayMinFreq = minFreq;
-    float displayMaxFreq = maxFreq;
+    double minFreq = (double(minbin) * sr) / fftSize;
+    double maxFreq = (double(maxbin) * sr) / fftSize;
+
+    double displayMinFreq = minFreq;
+    double displayMaxFreq = maxFreq;
 
     if (fftSize != m_fftSize) {
         displayMinFreq = getEffectiveMinFrequency();
@@ -2182,34 +1839,17 @@
     int increment = getWindowIncrement();
     
     bool logarithmic = (m_frequencyScale == LogFrequencyScale);
-/*
-    float yforbin[maxbin - minbin + 1];
-
-    for (size_t q = minbin; q <= maxbin; ++q) {
-        float f0 = (float(q) * sr) / fftSize;
-        yforbin[q - minbin] =
-            v->getYForFrequency(f0, displayMinFreq, displayMaxFreq,
-                                logarithmic);
+
+    MagnitudeRange overallMag = m_viewMags[v->getId()];
+    bool overallMagChanged = false;
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    cerr << "SpectrogramLayer: " << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl;
+#endif
+
+    if (repaintWidth == 0) {
+        SVDEBUG << "*** NOTE: repaintWidth == 0" << endl;
     }
-*/
-    MagnitudeRange overallMag = m_viewMags[v];
-    bool overallMagChanged = false;
-
-    bool fftSuspended = false;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << ((float(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl;
-#endif
-
-    bool runOutOfData = false;
-
-    if (w == 0) {
-        SVDEBUG << "*** NOTE: w == 0" << endl;
-    }
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    size_t pixels = 0;
-#endif
 
     Profiler outerprof("SpectrogramLayer::paint: all cols");
 
@@ -2226,28 +1866,34 @@
     // such boundaries at either side of the draw buffer -- one which
     // we draw up to, and one which we subsequently crop at.
 
-    bool bufferBinResolution = false;
-    if (increment > zoomLevel) bufferBinResolution = true;
-
-    long leftBoundaryFrame = -1, leftCropFrame = -1;
-    long rightBoundaryFrame = -1, rightCropFrame = -1;
+    bool bufferIsBinResolution = false;
+    if (increment > zoomLevel) bufferIsBinResolution = true;
+
+    sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
+    sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
 
     int bufwid;
 
-    if (bufferBinResolution) {
+    if (bufferIsBinResolution) {
 
         for (int x = x0; ; --x) {
-            long f = v->getFrameForX(x);
+            sv_frame_t f = v->getFrameForX(x);
             if ((f / increment) * increment == f) {
                 if (leftCropFrame == -1) leftCropFrame = f;
-                else if (x < x0 - 2) { leftBoundaryFrame = f; break; }
+                else if (x < x0 - 2) {
+                    leftBoundaryFrame = f;
+                    break;
+                }
             }
         }
-        for (int x = x0 + w; ; ++x) {
-            long f = v->getFrameForX(x);
+        for (int x = x0 + repaintWidth; ; ++x) {
+            sv_frame_t f = v->getFrameForX(x);
             if ((f / increment) * increment == f) {
                 if (rightCropFrame == -1) rightCropFrame = f;
-                else if (x > x0 + w + 2) { rightBoundaryFrame = f; break; }
+                else if (x > x0 + repaintWidth + 2) {
+                    rightBoundaryFrame = f;
+                    break;
+                }
             }
         }
 #ifdef DEBUG_SPECTROGRAM_REPAINT
@@ -2255,107 +1901,135 @@
         cerr << "Right: crop: " << rightCropFrame << " (bin " << rightCropFrame/increment << "); boundary: " << rightBoundaryFrame << " (bin " << rightBoundaryFrame/increment << ")" << endl;
 #endif
 
-        bufwid = (rightBoundaryFrame - leftBoundaryFrame) / increment;
+        bufwid = int((rightBoundaryFrame - leftBoundaryFrame) / increment);
 
     } else {
         
-        bufwid = w;
+        bufwid = repaintWidth;
     }
 
-#ifdef __GNUC__
-    int binforx[bufwid];
-    float binfory[h];
-#else
-    int *binforx = (int *)alloca(bufwid * sizeof(int));
-    float *binfory = (float *)alloca(h * sizeof(float));
-#endif
-
+    vector<int> binforx(bufwid);
+    vector<double> binfory(h);
+    
     bool usePeaksCache = false;
 
-    if (bufferBinResolution) {
+    if (bufferIsBinResolution) {
         for (int x = 0; x < bufwid; ++x) {
-            binforx[x] = (leftBoundaryFrame / increment) + x;
-//            cerr << "binforx[" << x << "] = " << binforx[x] << endl;
+            binforx[x] = int(leftBoundaryFrame / increment) + x;
         }
         m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8);
     } else {
         for (int x = 0; x < bufwid; ++x) {
-            float s0 = 0, s1 = 0;
+            double s0 = 0, s1 = 0;
             if (getXBinRange(v, x + x0, s0, s1)) {
                 binforx[x] = int(s0 + 0.0001);
             } else {
                 binforx[x] = -1; //???
             }
         }
-        if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() < h) {
+        if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() != h) {
             m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8);
         }
         usePeaksCache = (increment * 8) < zoomLevel;
         if (m_colourScale == PhaseColourScale) usePeaksCache = false;
     }
 
-// No longer exists in Qt5:    m_drawBuffer.setNumColors(256);
     for (int pixel = 0; pixel < 256; ++pixel) {
-        m_drawBuffer.setColor(pixel, m_palette.getColour(pixel).rgb());
+        m_drawBuffer.setColor((unsigned char)pixel,
+                              m_palette.getColour((unsigned char)pixel).rgb());
     }
 
     m_drawBuffer.fill(0);
-    
+    int attainedBufwid = bufwid;
+
+    double softTimeLimit;
+
+    if (m_synchronous) {
+
+        // must paint the whole thing for synchronous mode, so give
+        // "no timeout"
+        softTimeLimit = 0.0;
+        
+    } else if (bufferIsBinResolution) {
+        
+        // calculating boundaries later will be too fiddly for partial
+        // paints, and painting should be fast anyway when this is the
+        // case because it means we're well zoomed in
+        softTimeLimit = 0.0;
+
+    } else {
+
+        // neither limitation applies, so use a short soft limit
+
+        if (m_binDisplay == PeakFrequencies) {
+            softTimeLimit = 0.15;
+        } else {
+            softTimeLimit = 0.1;
+        }
+    }
+
     if (m_binDisplay != PeakFrequencies) {
 
         for (int y = 0; y < h; ++y) {
-            float q0 = 0, q1 = 0;
+            double q0 = 0, q1 = 0;
             if (!getSmoothedYBinRange(v, h-y-1, q0, q1)) {
                 binfory[y] = -1;
             } else {
                 binfory[y] = q0;
-//                cerr << "binfory[" << y << "] = " << binfory[y] << endl;
             }
         }
 
-        paintDrawBuffer(v, bufwid, h, binforx, binfory, usePeaksCache,
-                        overallMag, overallMagChanged);
+        attainedBufwid = 
+            paintDrawBuffer(v, bufwid, h, binforx, binfory,
+                            usePeaksCache,
+                            overallMag, overallMagChanged,
+                            rightToLeft,
+                            softTimeLimit);
 
     } else {
 
-        paintDrawBufferPeakFrequencies(v, bufwid, h, binforx,
-                                       minbin, maxbin,
-                                       displayMinFreq, displayMaxFreq,
-                                       logarithmic,
-                                       overallMag, overallMagChanged);
+        attainedBufwid = 
+            paintDrawBufferPeakFrequencies(v, bufwid, h, binforx,
+                                           minbin, maxbin,
+                                           displayMinFreq, displayMaxFreq,
+                                           logarithmic,
+                                           overallMag, overallMagChanged,
+                                           rightToLeft,
+                                           softTimeLimit);
     }
 
-/*
-    for (int x = 0; x < w / xPixelRatio; ++x) {
-
-        Profiler innerprof("SpectrogramLayer::paint: 1 pixel column");
-
-        runOutOfData = !paintColumnValues(v, fft, x0, x,
-                                          minbin, maxbin,
-                                          displayMinFreq, displayMaxFreq,
-                                          xPixelRatio,
-                                          h, yforbin);
-
-        if (runOutOfData) {
+    int failedToRepaint = bufwid - attainedBufwid;
+
+    int paintedLeft = x0;
+    int paintedWidth = x1 - x0;
+    
+    if (failedToRepaint > 0) {
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            cerr << "Run out of data -- dropping out of loop" << endl;
+        cerr << "SpectrogramLayer::paint(): Failed to repaint " << failedToRepaint << " of " << bufwid
+             << " columns in time (so managed to repaint " << bufwid - failedToRepaint << ")" << endl;
 #endif
-            break;
+
+        if (rightToLeft) {
+            paintedLeft += failedToRepaint;
         }
+
+        paintedWidth -= failedToRepaint;
+
+        if (paintedWidth < 0) {
+            paintedWidth = 0;
+        }
+        
+    } else if (failedToRepaint < 0) {
+        cerr << "WARNING: failedToRepaint < 0 (= " << failedToRepaint << ")"
+             << endl;
+        failedToRepaint = 0;
     }
-*/
+
+    if (overallMagChanged) {
+        m_viewMags[v->getId()] = overallMag;
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-//    cerr << pixels << " pixels drawn" << endl;
-#endif
-
-    if (overallMagChanged) {
-        m_viewMags[v] = overallMag;
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << endl;
-#endif
-    } else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl;
+        cerr << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "] - will be updating" << endl;
 #endif
     }
 
@@ -2363,156 +2037,180 @@
 
     Profiler profiler2("SpectrogramLayer::paint: draw image");
 
-    if (recreateWholeImageCache) {
+    if (paintedWidth > 0) {
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "Recreating image cache: width = " << v->width()
-                  << ", height = " << h << endl;
-#endif
-	cache.image = QImage(v->width(), h, QImage::Format_ARGB32_Premultiplied);
-    }
-
-    if (w > 0) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "Painting " << w << "x" << h
-                  << " from draw buffer at " << 0 << "," << 0
-                  << " to " << w << "x" << h << " on cache at "
+        cerr << "SpectrogramLayer: Copying " << paintedWidth << "x" << h
+                  << " from draw buffer at " << paintedLeft - x0 << "," << 0
+                  << " to " << paintedWidth << "x" << h << " on cache at "
                   << x0 << "," << 0 << endl;
 #endif
 
-        QPainter cachePainter(&cache.image);
-
-        if (bufferBinResolution) {
+        if (bufferIsBinResolution) {
+
             int scaledLeft = v->getXForFrame(leftBoundaryFrame);
             int scaledRight = v->getXForFrame(rightBoundaryFrame);
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "Rescaling image from " << bufwid
+            cerr << "SpectrogramLayer: Rescaling image from " << bufwid
                  << "x" << h << " to "
                  << scaledRight-scaledLeft << "x" << h << endl;
 #endif
+
             Preferences::SpectrogramXSmoothing xsmoothing = 
                 Preferences::getInstance()->getSpectrogramXSmoothing();
-//            SVDEBUG << "xsmoothing == " << xsmoothing << endl;
+
             QImage scaled = m_drawBuffer.scaled
                 (scaledRight - scaledLeft, h,
                  Qt::IgnoreAspectRatio,
                  ((xsmoothing == Preferences::SpectrogramXInterpolated) ?
                   Qt::SmoothTransformation : Qt::FastTransformation));
+            
             int scaledLeftCrop = v->getXForFrame(leftCropFrame);
             int scaledRightCrop = v->getXForFrame(rightCropFrame);
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to "
+            cerr << "SpectrogramLayer: Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to "
                  << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl;
 #endif
-            cachePainter.drawImage
-                (QRect(scaledLeftCrop, 0,
-                       scaledRightCrop - scaledLeftCrop, h),
-                 scaled,
-                 QRect(scaledLeftCrop - scaledLeft, 0,
-                       scaledRightCrop - scaledLeftCrop, h));
+
+            int targetLeft = scaledLeftCrop;
+            if (targetLeft < 0) {
+                targetLeft = 0;
+            }
+
+            int targetWidth = scaledRightCrop - targetLeft;
+            if (targetLeft + targetWidth > cache.getSize().width()) {
+                targetWidth = cache.getSize().width() - targetLeft;
+            }
+            
+            int sourceLeft = targetLeft - scaledLeft;
+            if (sourceLeft < 0) {
+                sourceLeft = 0;
+            }
+            
+            int sourceWidth = targetWidth;
+
+            if (targetWidth > 0) {
+                cache.drawImage
+                    (targetLeft,
+                     targetWidth,
+                     scaled,
+                     sourceLeft,
+                     sourceWidth);
+            }
+
         } else {
-            cachePainter.drawImage(QRect(x0, 0, w, h),
-                                   m_drawBuffer,
-                                   QRect(0, 0, w, h));
+
+            cache.drawImage(paintedLeft, paintedWidth,
+                            m_drawBuffer,
+                            paintedLeft - x0, paintedWidth);
         }
-
-        cachePainter.end();
     }
 
-    QRect pr = rect & cache.validArea;
-
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "Painting " << pr.width() << "x" << pr.height()
+    cerr << "SpectrogramLayer: Cache valid area now from " << cache.getValidLeft()
+         << " width " << cache.getValidWidth() << ", height "
+         << cache.getSize().height() << endl;
+#endif
+
+    QRect pr = rect & cache.getValidArea();
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    cerr << "SpectrogramLayer: Copying " << pr.width() << "x" << pr.height()
               << " from cache at " << pr.x() << "," << pr.y()
               << " to window" << endl;
 #endif
 
-    paint.drawImage(pr.x(), pr.y(), cache.image,
+    paint.drawImage(pr.x(), pr.y(), cache.getImage(),
                     pr.x(), pr.y(), pr.width(), pr.height());
-    //!!!
-//    paint.drawImage(v->rect(), cache.image,
-//                    QRect(QPoint(0, 0), cache.image.size()));
-
-    cache.startFrame = startFrame;
-    cache.zoomLevel = zoomLevel;
 
     if (!m_synchronous) {
 
-        if (!m_normalizeVisibleArea || !overallMagChanged) {
-    
-            if (cache.validArea.x() > 0) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                SVDEBUG << "SpectrogramLayer::paint() updating left (0, "
-                          << cache.validArea.x() << ")" << endl;
-#endif
-                v->update(0, 0, cache.validArea.x(), h);
+        if ((m_normalization != NormalizeVisibleArea) || !overallMagChanged) {
+
+            QRect areaLeft(0, 0, cache.getValidLeft(), h);
+            QRect areaRight(cache.getValidRight(), 0,
+                            cache.getSize().width() - cache.getValidRight(), h);
+
+            bool haveSpaceLeft = (areaLeft.width() > 0);
+            bool haveSpaceRight = (areaRight.width() > 0);
+
+            bool updateLeft = haveSpaceLeft;
+            bool updateRight = haveSpaceRight;
+            
+            if (updateLeft && updateRight) {
+                if (rightToLeft) {
+                    // we just did something adjoining the cache on
+                    // its left side, so now do something on its right
+                    updateLeft = false;
+                } else {
+                    updateRight = false;
+                }
             }
             
-            if (cache.validArea.x() + cache.validArea.width() <
-                cache.image.width()) {
+            if (updateLeft) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-                SVDEBUG << "SpectrogramLayer::paint() updating right ("
-                          << cache.validArea.x() + cache.validArea.width()
-                          << ", "
-                          << cache.image.width() - (cache.validArea.x() +
-                                                     cache.validArea.width())
-                          << ")" << endl;
+                cerr << "SpectrogramLayer::paint() updating left ("
+                     << areaLeft.x() << ", "
+                     << areaLeft.width() << ")" << endl;
 #endif
-                v->update(cache.validArea.x() + cache.validArea.width(),
-                          0,
-                          cache.image.width() - (cache.validArea.x() +
-                                                  cache.validArea.width()),
-                          h);
+                v->updatePaintRect(areaLeft);
             }
+            
+            if (updateRight) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                cerr << "SpectrogramLayer::paint() updating right ("
+                     << areaRight.x() << ", "
+                     << areaRight.width() << ")" << endl;
+#endif
+                v->updatePaintRect(areaRight);
+            }
+            
         } else {
             // overallMagChanged
             cerr << "\noverallMagChanged - updating all\n" << endl;
-            cache.validArea = QRect();
-            v->update();
+            cache.invalidate();
+            v->updatePaintRect(v->getPaintRect());
         }
     }
 
     illuminateLocalFeatures(v, paint);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::paint() returning" << endl;
+    cerr << "SpectrogramLayer::paint() returning" << endl;
 #endif
-
-    if (!m_synchronous) {
-        m_lastPaintBlockWidth = paintBlockWidth;
-        (void)gettimeofday(&tv, 0);
-        m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart;
-    }
-
-//!!!    if (fftSuspended) fft->resume();
 }
 
-bool
-SpectrogramLayer::paintDrawBufferPeakFrequencies(View *v,
+int
+SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v,
                                                  int w,
                                                  int h,
-                                                 int *binforx,
+                                                 const vector<int> &binforx,
                                                  int minbin,
                                                  int maxbin,
-                                                 float displayMinFreq,
-                                                 float displayMaxFreq,
+                                                 double displayMinFreq,
+                                                 double displayMaxFreq,
                                                  bool logarithmic,
                                                  MagnitudeRange &overallMag,
-                                                 bool &overallMagChanged) const
+                                                 bool &overallMagChanged,
+                                                 bool rightToLeft,
+                                                 double softTimeLimit) const
 {
     Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies");
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
+    cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
 #endif
     if (minbin < 0) minbin = 0;
     if (maxbin < 0) maxbin = minbin+1;
 
     FFTModel *fft = getFFTModel(v);
-    if (!fft) return false;
+    if (!fft) return 0;
 
     FFTModel::PeakSet peakfreqs;
 
-    int px = -1, psx = -1;
+    int psx = -1;
 
 #ifdef __GNUC__
     float values[maxbin - minbin + 1];
@@ -2520,12 +2218,30 @@
     float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float));
 #endif
 
-    for (int x = 0; x < w; ++x) {
+    int minColumns = 4;
+    bool haveTimeLimits = (softTimeLimit > 0.0);
+    double hardTimeLimit = softTimeLimit * 2.0;
+    bool overridingSoftLimit = false;
+    auto startTime = chrono::steady_clock::now();
+    
+    int start = 0;
+    int finish = w;
+    int step = 1;
+
+    if (rightToLeft) {
+        start = w-1;
+        finish = -1;
+        step = -1;
+    }
+    
+    int columnCount = 0;
+    
+    for (int x = start; x != finish; x += step) {
+        
+        ++columnCount;
         
         if (binforx[x] < 0) continue;
 
-        float columnMax = 0.f;
-
         int sx0 = binforx[x];
         int sx1 = sx0;
         if (x+1 < w) sx1 = binforx[x+1];
@@ -2535,18 +2251,8 @@
 
         for (int sx = sx0; sx < sx1; ++sx) {
 
-            if (x == px && sx == psx) continue;
             if (sx < 0 || sx >= int(fft->getWidth())) continue;
 
-            if (!m_synchronous) {
-                if (!fft->isColumnAvailable(sx)) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "Met unavailable column at col " << sx << endl;
-#endif
-                    return false;
-                }
-            }
-
             MagnitudeRange mag;
 
             if (sx != psx) {
@@ -2554,8 +2260,16 @@
                                                     minbin, maxbin - 1);
                 if (m_colourScale == PhaseColourScale) {
                     fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1);
-                } else if (m_normalizeColumns) {
+                } else if (m_normalization == NormalizeColumns) {
                     fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
+                } else if (m_normalization == NormalizeHybrid) {
+                    float max = fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
+                    if (max > 0.f) {
+                        for (int i = minbin; i <= maxbin; ++i) {
+                            values[i - minbin] = float(values[i - minbin] *
+                                                       log10f(max));
+                        }
+                    }
                 } else {
                     fft->getMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
                 }
@@ -2566,22 +2280,22 @@
                  pi != peakfreqs.end(); ++pi) {
 
                 int bin = pi->first;
-                int freq = pi->second;
+                double freq = pi->second;
 
                 if (bin < minbin) continue;
                 if (bin > maxbin) break;
 
-                float value = values[bin - minbin];
+                double value = values[bin - minbin];
 
                 if (m_colourScale != PhaseColourScale) {
-                    if (!m_normalizeColumns) {
-                        value /= (m_fftSize/2.f);
+                    if (m_normalization != NormalizeColumns) {
+                        value /= (m_fftSize/2.0);
                     }
-                    mag.sample(value);
+                    mag.sample(float(value));
                     value *= m_gain;
                 }
 
-                float y = v->getYForFrequency
+                double y = v->getYForFrequency
                     (freq, displayMinFreq, displayMaxFreq, logarithmic);
 
                 int iy = int(y + 0.5);
@@ -2604,28 +2318,58 @@
                 }
             }
         }
+
+        if (haveTimeLimits) {
+            if (columnCount >= minColumns) {
+                auto t = chrono::steady_clock::now();
+                double diff = chrono::duration<double>(t - startTime).count();
+                if (diff > hardTimeLimit) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                    cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: hard limit " << hardTimeLimit << " sec exceeded after "
+                         << columnCount << " columns with time " << diff << endl;
+#endif
+                    return columnCount;
+                } else if (diff > softTimeLimit && !overridingSoftLimit) {
+                    // If we're more than half way through by the time
+                    // we reach the soft limit, ignore it (though
+                    // still respect the hard limit, above). Otherwise
+                    // respect the soft limit and return now.
+                    if (columnCount > w/2) {
+                        overridingSoftLimit = true;
+                    } else {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                        cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: soft limit " << softTimeLimit << " sec exceeded after "
+                             << columnCount << " columns with time " << diff << endl;
+#endif
+                        return columnCount;
+                    }
+                }                        
+            }
+        }
     }
 
-    return true;
+    return columnCount;
 }
 
-bool
-SpectrogramLayer::paintDrawBuffer(View *v,
+int
+SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v,
                                   int w,
                                   int h,
-                                  int *binforx,
-                                  float *binfory,
+                                  const vector<int> &binforx,
+                                  const vector<double> &binfory,
                                   bool usePeaksCache,
                                   MagnitudeRange &overallMag,
-                                  bool &overallMagChanged) const
+                                  bool &overallMagChanged,
+                                  bool rightToLeft,
+                                  double softTimeLimit) const
 {
     Profiler profiler("SpectrogramLayer::paintDrawBuffer");
 
     int minbin = int(binfory[0] + 0.0001);
-    int maxbin = binfory[h-1];
+    int maxbin = int(binfory[h-1]);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
+    cerr << "SpectrogramLayer::paintDrawBuffer: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
 #endif
     if (minbin < 0) minbin = 0;
     if (maxbin < 0) maxbin = minbin+1;
@@ -2634,7 +2378,7 @@
     FFTModel *fft = 0;
     int divisor = 1;
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl;
+    cerr << "SpectrogramLayer::paintDrawBuffer: Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl;
 #endif
     if (usePeaksCache) { //!!!
         sourceModel = getPeakCache(v);
@@ -2645,7 +2389,7 @@
         sourceModel = fft = getFFTModel(v);
     }
 
-    if (!sourceModel) return false;
+    if (!sourceModel) return 0;
 
     bool interpolate = false;
     Preferences::SpectrogramSmoothing smoothing = 
@@ -2671,7 +2415,27 @@
     const float *values = autoarray;
     DenseThreeDimensionalModel::Column c;
 
-    for (int x = 0; x < w; ++x) {
+    int minColumns = 4;
+    bool haveTimeLimits = (softTimeLimit > 0.0);
+    double hardTimeLimit = softTimeLimit * 2.0;
+    bool overridingSoftLimit = false;
+    auto startTime = chrono::steady_clock::now();
+    
+    int start = 0;
+    int finish = w;
+    int step = 1;
+
+    if (rightToLeft) {
+        start = w-1;
+        finish = -1;
+        step = -1;
+    }
+
+    int columnCount = 0;
+    
+    for (int x = start; x != finish; x += step) {
+
+        ++columnCount;
         
         if (binforx[x] < 0) continue;
 
@@ -2695,85 +2459,85 @@
 
             if (sx < 0 || sx >= int(sourceModel->getWidth())) continue;
 
-            if (!m_synchronous) {
-                if (!sourceModel->isColumnAvailable(sx)) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "Met unavailable column at col " << sx << endl;
-#endif
-                    return false;
-                }
-            }
-
             MagnitudeRange mag;
 
             if (sx != psx) {
                 if (fft) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-                    SVDEBUG << "Retrieving column " << sx << " from fft directly" << endl;
+//                    cerr << "Retrieving column " << sx << " from fft directly" << endl;
 #endif
                     if (m_colourScale == PhaseColourScale) {
                         fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1);
-                    } else if (m_normalizeColumns) {
+                    } else if (m_normalization == NormalizeColumns) {
                         fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
+                    } else if (m_normalization == NormalizeHybrid) {
+                        float max = fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
+                        float scale = log10f(max + 1.f);
+//                        cout << "sx = " << sx << ", max = " << max << ", log10(max) = " << log10(max) << ", scale = " << scale << endl;
+                        for (int i = minbin; i <= maxbin; ++i) {
+                            autoarray[i - minbin] *= scale;
+                        }
                     } else {
                         fft->getMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
                     }
                 } else {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-                    SVDEBUG << "Retrieving column " << sx << " from peaks cache" << endl;
+//                    cerr << "Retrieving column " << sx << " from peaks cache" << endl;
 #endif
                     c = sourceModel->getColumn(sx);
-                    if (m_normalizeColumns) {
+                    if (m_normalization == NormalizeColumns ||
+                        m_normalization == NormalizeHybrid) {
                         for (int y = 0; y < h; ++y) {
                             if (c[y] > columnMax) columnMax = c[y];
                         }
                     }
-                    values = c.constData() + minbin;
+                    values = c.data() + minbin;
                 }
                 psx = sx;
             }
 
             for (int y = 0; y < h; ++y) {
 
-                float sy0 = binfory[y];
-                float sy1 = sy0 + 1;
+                double sy0 = binfory[y];
+                double sy1 = sy0 + 1;
                 if (y+1 < h) sy1 = binfory[y+1];
 
-                float value = 0.f;
-
-                if (interpolate && fabsf(sy1 - sy0) < 1.f) {
-
-                    float centre = (sy0 + sy1) / 2;
-                    float dist = (centre - 0.5) - lrintf(centre - 0.5);
+                double value = 0.0;
+
+                if (interpolate && fabs(sy1 - sy0) < 1.0) {
+
+                    double centre = (sy0 + sy1) / 2;
+                    double dist = (centre - 0.5) - rint(centre - 0.5);
                     int bin = int(centre);
                     int other = (dist < 0 ? (bin-1) : (bin+1));
                     if (bin < minbin) bin = minbin;
                     if (bin > maxbin) bin = maxbin;
                     if (other < minbin || other > maxbin) other = bin;
-                    float prop = 1.f - fabsf(dist);
-
-                    float v0 = values[bin - minbin];
-                    float v1 = values[other - minbin];
+                    double prop = 1.0 - fabs(dist);
+
+                    double v0 = values[bin - minbin];
+                    double v1 = values[other - minbin];
                     if (m_binDisplay == PeakBins) {
                         if (bin == minbin || bin == maxbin ||
                             v0 < values[bin-minbin-1] ||
-                            v0 < values[bin-minbin+1]) v0 = 0.f;
+                            v0 < values[bin-minbin+1]) v0 = 0.0;
                         if (other == minbin || other == maxbin ||
                             v1 < values[other-minbin-1] ||
-                            v1 < values[other-minbin+1]) v1 = 0.f;
+                            v1 < values[other-minbin+1]) v1 = 0.0;
                     }
-                    if (v0 == 0.f && v1 == 0.f) continue;
-                    value = prop * v0 + (1.f - prop) * v1;
+                    if (v0 == 0.0 && v1 == 0.0) continue;
+                    value = prop * v0 + (1.0 - prop) * v1;
 
                     if (m_colourScale != PhaseColourScale) {
-                        if (!m_normalizeColumns) {
-                            value /= (m_fftSize/2.f);
+                        if (m_normalization != NormalizeColumns &&
+                            m_normalization != NormalizeHybrid) {
+                            value /= (m_fftSize/2.0);
                         }
-                        mag.sample(value);
+                        mag.sample(float(value));
                         value *= m_gain;
                     }
 
-                    peaks[y] = value;
+                    peaks[y] = float(value);
 
                 } else {                    
 
@@ -2791,14 +2555,17 @@
                         }
 
                         if (m_colourScale != PhaseColourScale) {
-                            if (!m_normalizeColumns) {
-                                value /= (m_fftSize/2.f);
+                            if (m_normalization != NormalizeColumns &&
+                                m_normalization != NormalizeHybrid) {
+                                value /= (m_fftSize/2.0);
                             }
-                            mag.sample(value);
+                            mag.sample(float(value));
                             value *= m_gain;
                         }
 
-                        if (value > peaks[y]) peaks[y] = value; //!!! not right for phase!
+                        if (value > peaks[y]) {
+                            peaks[y] = float(value); //!!! not right for phase!
+                        }
                     }
                 }
             }
@@ -2820,25 +2587,57 @@
 
         for (int y = 0; y < h; ++y) {
 
-            float peak = peaks[y];
+            double peak = peaks[y];
             
             if (m_colourScale != PhaseColourScale &&
-                m_normalizeColumns && 
+                (m_normalization == NormalizeColumns ||
+                 m_normalization == NormalizeHybrid) &&
                 columnMax > 0.f) {
                 peak /= columnMax;
+                if (m_normalization == NormalizeHybrid) {
+                    peak *= log10(columnMax + 1.f);
+                }
             }
             
             unsigned char peakpix = getDisplayValue(v, peak);
 
             m_drawBuffer.setPixel(x, h-y-1, peakpix);
         }
+
+        if (haveTimeLimits) {
+            if (columnCount >= minColumns) {
+                auto t = chrono::steady_clock::now();
+                double diff = chrono::duration<double>(t - startTime).count();
+                if (diff > hardTimeLimit) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                    cerr << "SpectrogramLayer::paintDrawBuffer: hard limit " << hardTimeLimit << " sec exceeded after "
+                         << columnCount << " columns with time " << diff << endl;
+#endif
+                    return columnCount;
+                } else if (diff > softTimeLimit && !overridingSoftLimit) {
+                    // If we're more than half way through by the time
+                    // we reach the soft limit, ignore it (though
+                    // still respect the hard limit, above). Otherwise
+                    // respect the soft limit and return now.
+                    if (columnCount > w/2) {
+                        overridingSoftLimit = true;
+                    } else {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                        cerr << "SpectrogramLayer::paintDrawBuffer: soft limit " << softTimeLimit << " sec exceeded after "
+                             << columnCount << " columns with time " << diff << endl;
+#endif
+                        return columnCount;
+                    }
+                }                        
+            }
+        }
     }
 
-    return true;
+    return columnCount;
 }
 
 void
-SpectrogramLayer::illuminateLocalFeatures(View *v, QPainter &paint) const
+SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const
 {
     Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
 
@@ -2850,8 +2649,8 @@
 //    cerr << "SpectrogramLayer: illuminateLocalFeatures("
 //              << localPos.x() << "," << localPos.y() << ")" << endl;
 
-    float s0, s1;
-    float f0, f1;
+    double s0, s1;
+    double f0, f1;
 
     if (getXBinRange(v, localPos.x(), s0, s1) &&
         getYBinSourceRange(v, localPos.y(), f0, f1)) {
@@ -2876,8 +2675,8 @@
     }
 }
 
-float
-SpectrogramLayer::getYForFrequency(const View *v, float frequency) const
+double
+SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const
 {
     return v->getYForFrequency(frequency,
 			       getEffectiveMinFrequency(),
@@ -2885,8 +2684,8 @@
 			       m_frequencyScale == LogFrequencyScale);
 }
 
-float
-SpectrogramLayer::getFrequencyForY(const View *v, int y) const
+double
+SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const
 {
     return v->getFrequencyForY(y,
 			       getEffectiveMinFrequency(),
@@ -2895,34 +2694,36 @@
 }
 
 int
-SpectrogramLayer::getCompletion(View *v) const
+SpectrogramLayer::getCompletion(LayerGeometryProvider *v) const
 {
-    if (m_updateTimer == 0) return 100;
-    if (m_fftModels.find(v) == m_fftModels.end()) return 100;
-
-    size_t completion = m_fftModels[v].first->getCompletion();
+    const View *view = v->getView();
+    
+    if (m_fftModels.find(view->getId()) == m_fftModels.end()) return 100;
+
+    int completion = m_fftModels[view->getId()]->getCompletion();
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
+    cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
 #endif
     return completion;
 }
 
 QString
-SpectrogramLayer::getError(View *v) const
+SpectrogramLayer::getError(LayerGeometryProvider *v) const
 {
-    if (m_fftModels.find(v) == m_fftModels.end()) return "";
-    return m_fftModels[v].first->getError();
+    const View *view = v->getView();
+    if (m_fftModels.find(view->getId()) == m_fftModels.end()) return "";
+    return m_fftModels[view->getId()]->getError();
 }
 
 bool
-SpectrogramLayer::getValueExtents(float &min, float &max,
+SpectrogramLayer::getValueExtents(double &min, double &max,
                                   bool &logarithmic, QString &unit) const
 {
     if (!m_model) return false;
 
-    int sr = m_model->getSampleRate();
-    min = float(sr) / m_fftSize;
-    max = float(sr) / 2;
+    sv_samplerate_t sr = m_model->getSampleRate();
+    min = double(sr) / m_fftSize;
+    max = double(sr) / 2;
     
     logarithmic = (m_frequencyScale == LogFrequencyScale);
     unit = "Hz";
@@ -2930,7 +2731,7 @@
 }
 
 bool
-SpectrogramLayer::getDisplayExtents(float &min, float &max) const
+SpectrogramLayer::getDisplayExtents(double &min, double &max) const
 {
     min = getEffectiveMinFrequency();
     max = getEffectiveMaxFrequency();
@@ -2940,17 +2741,17 @@
 }    
 
 bool
-SpectrogramLayer::setDisplayExtents(float min, float max)
+SpectrogramLayer::setDisplayExtents(double min, double max)
 {
     if (!m_model) return false;
 
 //    SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl;
 
     if (min < 0) min = 0;
-    if (max > m_model->getSampleRate()/2) max = m_model->getSampleRate()/2;
+    if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0;
     
-    size_t minf = lrintf(min);
-    size_t maxf = lrintf(max);
+    int minf = int(lrint(min));
+    int maxf = int(lrint(max));
 
     if (m_minFrequency == minf && m_maxFrequency == maxf) return true;
 
@@ -2972,8 +2773,8 @@
 }
 
 bool
-SpectrogramLayer::getYScaleValue(const View *v, int y,
-                                 float &value, QString &unit) const
+SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
+                                 double &value, QString &unit) const
 {
     value = getFrequencyForY(v, y);
     unit = "Hz";
@@ -2981,13 +2782,14 @@
 }
 
 bool
-SpectrogramLayer::snapToFeatureFrame(View *, int &frame,
-				     size_t &resolution,
+SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *,
+                                     sv_frame_t &frame,
+				     int &resolution,
 				     SnapType snap) const
 {
     resolution = getWindowIncrement();
-    int left = (frame / resolution) * resolution;
-    int right = left + resolution;
+    sv_frame_t left = (frame / resolution) * resolution;
+    sv_frame_t right = left + resolution;
 
     switch (snap) {
     case SnapLeft:  frame = left;  break;
@@ -3003,14 +2805,15 @@
 } 
 
 void
-SpectrogramLayer::measureDoubleClick(View *v, QMouseEvent *e)
+SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    ImageCache &cache = m_imageCaches[v];
-
-    cerr << "cache width: " << cache.image.width() << ", height: "
-              << cache.image.height() << endl;
-
-    QImage image = cache.image;
+    const View *view = v->getView();
+    ScrollableImageCache &cache = getImageCacheReference(view);
+
+    cerr << "cache width: " << cache.getSize().width() << ", height: "
+         << cache.getSize().height() << endl;
+
+    QImage image = cache.getImage();
 
     ImageRegionFinder finder;
     QRect rect = finder.findRegionExtents(&image, e->pos());
@@ -3023,11 +2826,11 @@
 }
 
 bool
-SpectrogramLayer::getCrosshairExtents(View *v, QPainter &paint,
+SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
                                       QPoint cursorPos,
-                                      std::vector<QRect> &extents) const
+                                      vector<QRect> &extents) const
 {
-    QRect vertical(cursorPos.x() - 12, 0, 12, v->height());
+    QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
     extents.push_back(vertical);
 
     QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1);
@@ -3046,14 +2849,14 @@
     extents.push_back(pitch);
 
     QRect rt(cursorPos.x(),
-             v->height() - paint.fontMetrics().height() - 2,
+             v->getPaintHeight() - paint.fontMetrics().height() - 2,
              paint.fontMetrics().width("1234.567 s"),
              paint.fontMetrics().height());
     extents.push_back(rt);
 
     int w(paint.fontMetrics().width("1234567890") + 2);
     QRect frame(cursorPos.x() - w - 2,
-                v->height() - paint.fontMetrics().height() - 2,
+                v->getPaintHeight() - paint.fontMetrics().height() - 2,
                 w,
                 paint.fontMetrics().height());
     extents.push_back(frame);
@@ -3062,7 +2865,7 @@
 }
 
 void
-SpectrogramLayer::paintCrosshairs(View *v, QPainter &paint,
+SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                   QPoint cursorPos) const
 {
     paint.save();
@@ -3077,9 +2880,9 @@
     paint.setPen(m_crosshairColour);
 
     paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y());
-    paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->height());
+    paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight());
     
-    float fundamental = getFrequencyForY(v, cursorPos.y());
+    double fundamental = getFrequencyForY(v, cursorPos.y());
 
     v->drawVisibleText(paint,
                        sw + 2,
@@ -3096,18 +2899,18 @@
                            View::OutlinedText);
     }
 
-    long frame = v->getFrameForX(cursorPos.x());
+    sv_frame_t frame = v->getFrameForX(cursorPos.x());
     RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate());
     QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str());
     QString frameLabel = QString("%1").arg(frame);
     v->drawVisibleText(paint,
                        cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
-                       v->height() - 2,
+                       v->getPaintHeight() - 2,
                        frameLabel,
                        View::OutlinedText);
     v->drawVisibleText(paint,
                        cursorPos.x() + 2,
-                       v->height() - 2,
+                       v->getPaintHeight() - 2,
                        rtLabel,
                        View::OutlinedText);
 
@@ -3115,8 +2918,8 @@
 
     while (harmonic < 100) {
 
-        float hy = lrintf(getYForFrequency(v, fundamental * harmonic));
-        if (hy < 0 || hy > v->height()) break;
+        int hy = int(lrint(getYForFrequency(v, fundamental * harmonic)));
+        if (hy < 0 || hy > v->getPaintHeight()) break;
         
         int len = 7;
 
@@ -3129,9 +2932,9 @@
         }
 
         paint.drawLine(cursorPos.x() - len,
-                       int(hy),
+                       hy,
                        cursorPos.x(),
-                       int(hy));
+                       hy);
 
         ++harmonic;
     }
@@ -3140,17 +2943,17 @@
 }
 
 QString
-SpectrogramLayer::getFeatureDescription(View *v, QPoint &pos) const
+SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
     int y = pos.y();
 
     if (!m_model || !m_model->isOK()) return "";
 
-    float magMin = 0, magMax = 0;
-    float phaseMin = 0, phaseMax = 0;
-    float freqMin = 0, freqMax = 0;
-    float adjFreqMin = 0, adjFreqMax = 0;
+    double magMin = 0, magMax = 0;
+    double phaseMin = 0, phaseMax = 0;
+    double freqMin = 0, freqMax = 0;
+    double adjFreqMin = 0, adjFreqMax = 0;
     QString pitchMin, pitchMax;
     RealTime rtMin, rtMax;
 
@@ -3222,21 +3025,21 @@
     }	
 
     if (haveValues) {
-	float dbMin = AudioLevel::multiplier_to_dB(magMin);
-	float dbMax = AudioLevel::multiplier_to_dB(magMax);
+	double dbMin = AudioLevel::multiplier_to_dB(magMin);
+	double dbMax = AudioLevel::multiplier_to_dB(magMax);
 	QString dbMinString;
 	QString dbMaxString;
 	if (dbMin == AudioLevel::DB_FLOOR) {
 	    dbMinString = tr("-Inf");
 	} else {
-	    dbMinString = QString("%1").arg(lrintf(dbMin));
+	    dbMinString = QString("%1").arg(lrint(dbMin));
 	}
 	if (dbMax == AudioLevel::DB_FLOOR) {
 	    dbMaxString = tr("-Inf");
 	} else {
-	    dbMaxString = QString("%1").arg(lrintf(dbMax));
+	    dbMaxString = QString("%1").arg(lrint(dbMax));
 	}
-	if (lrintf(dbMin) != lrintf(dbMax)) {
+	if (lrint(dbMin) != lrint(dbMax)) {
 	    text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString);
 	} else {
 	    text += tr("dB:\t%1").arg(dbMinString);
@@ -3262,7 +3065,7 @@
 }
 
 int
-SpectrogramLayer::getVerticalScaleWidth(View *, bool detailed, QPainter &paint) const
+SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const
 {
     if (!m_model || !m_model->isOK()) return 0;
 
@@ -3283,7 +3086,7 @@
 }
 
 void
-SpectrogramLayer::paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const
+SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) {
 	return;
@@ -3298,8 +3101,8 @@
     int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4);
     int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0);
 
-    size_t bins = m_fftSize / 2;
-    int sr = m_model->getSampleRate();
+    int bins = m_fftSize / 2;
+    sv_samplerate_t sr = m_model->getSampleRate();
 
     if (m_maxFrequency > 0) {
 	bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1);
@@ -3325,17 +3128,23 @@
 	paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
 
 	QString top, bottom;
-        float min = m_viewMags[v].getMin();
-        float max = m_viewMags[v].getMax();
-
-        float dBmin = AudioLevel::multiplier_to_dB(min);
-        float dBmax = AudioLevel::multiplier_to_dB(max);
-
+        double min = m_viewMags[v->getId()].getMin();
+        double max = m_viewMags[v->getId()].getMax();
+
+        double dBmin = AudioLevel::multiplier_to_dB(min);
+        double dBmax = AudioLevel::multiplier_to_dB(max);
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        cerr << "paintVerticalScale: for view id " << v->getId()
+             << ": min = " << min << ", max = " << max
+             << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl;
+#endif
+        
         if (dBmax < -60.f) dBmax = -60.f;
-        else top = QString("%1").arg(lrintf(dBmax));
+        else top = QString("%1").arg(lrint(dBmax));
 
         if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f;
-        bottom = QString("%1").arg(lrintf(dBmin));
+        bottom = QString("%1").arg(lrint(dBmin));
 
         //!!! & phase etc
 
@@ -3359,13 +3168,13 @@
 
 	for (int i = 0; i < ch; ++i) {
 
-            float dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1));
+            double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1));
             int idb = int(dBval);
 
-            float value = AudioLevel::dB_to_multiplier(dBval);
+            double value = AudioLevel::dB_to_multiplier(dBval);
             int colour = getDisplayValue(v, value * m_gain);
 
-	    paint.setPen(m_palette.getColour(colour));
+	    paint.setPen(m_palette.getColour((unsigned char)colour));
 
             int y = textHeight * topLines + 4 + ch - i;
 
@@ -3397,10 +3206,10 @@
 
     int bin = -1;
 
-    for (int y = 0; y < v->height(); ++y) {
-
-	float q0, q1;
-	if (!getYBinRange(v, v->height() - y, q0, q1)) continue;
+    for (int y = 0; y < v->getPaintHeight(); ++y) {
+
+	double q0, q1;
+	if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue;
 
 	int vy;
 
@@ -3411,7 +3220,7 @@
 	    continue;
 	}
 
-	int freq = (sr * bin) / m_fftSize;
+	int freq = int((sr * bin) / m_fftSize);
 
 	if (py >= 0 && (vy - py) < textHeight - 1) {
 	    if (m_frequencyScale == LinearFrequencyScale) {
@@ -3425,7 +3234,7 @@
 	paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy);
 
 	if (h - vy - textHeight >= -2) {
-	    int tx = w - 3 - paint.fontMetrics().width(text) - std::max(tickw, pkw);
+	    int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw);
 	    paint.drawText(tx, h - vy + toff, text);
 	}
 
@@ -3447,33 +3256,38 @@
 class SpectrogramRangeMapper : public RangeMapper
 {
 public:
-    SpectrogramRangeMapper(int sr, int /* fftsize */) :
-        m_dist(float(sr) / 2),
-        m_s2(sqrtf(sqrtf(2))) { }
+    SpectrogramRangeMapper(sv_samplerate_t sr, int /* fftsize */) :
+        m_dist(sr / 2),
+        m_s2(sqrt(sqrt(2))) { }
     ~SpectrogramRangeMapper() { }
     
-    virtual int getPositionForValue(float value) const {
-
-        float dist = m_dist;
+    virtual int getPositionForValue(double value) const {
+
+        double dist = m_dist;
     
         int n = 0;
 
-        while (dist > (value + 0.00001) && dist > 0.1f) {
+        while (dist > (value + 0.00001) && dist > 0.1) {
             dist /= m_s2;
             ++n;
         }
 
         return n;
     }
-
-    virtual float getValueForPosition(int position) const {
+    
+    virtual int getPositionForValueUnclamped(double value) const {
+        // We don't really support this
+        return getPositionForValue(value);
+    }
+
+    virtual double getValueForPosition(int position) const {
 
         // Vertical zoom step 0 shows the entire range from DC ->
         // Nyquist frequency.  Step 1 shows 2^(1/4) of the range of
         // step 0, and so on until the visible range is smaller than
         // the frequency step between bins at the current fft size.
 
-        float dist = m_dist;
+        double dist = m_dist;
     
         int n = 0;
         while (n < position) {
@@ -3484,11 +3298,16 @@
         return dist;
     }
     
+    virtual double getValueForPositionUnclamped(int position) const {
+        // We don't really support this
+        return getValueForPosition(position);
+    }
+
     virtual QString getUnit() const { return "Hz"; }
 
 protected:
-    float m_dist;
-    float m_s2;
+    double m_dist;
+    double m_s2;
 };
 
 int
@@ -3496,16 +3315,16 @@
 {
     if (!m_model) return 0;
 
-    int sr = m_model->getSampleRate();
+    sv_samplerate_t sr = m_model->getSampleRate();
 
     SpectrogramRangeMapper mapper(sr, m_fftSize);
 
-//    int maxStep = mapper.getPositionForValue((float(sr) / m_fftSize) + 0.001);
+//    int maxStep = mapper.getPositionForValue((double(sr) / m_fftSize) + 0.001);
     int maxStep = mapper.getPositionForValue(0);
-    int minStep = mapper.getPositionForValue(float(sr) / 2);
-
-    size_t initialMax = m_initialMaxFrequency;
-    if (initialMax == 0) initialMax = sr / 2;
+    int minStep = mapper.getPositionForValue(double(sr) / 2);
+
+    int initialMax = m_initialMaxFrequency;
+    if (initialMax == 0) initialMax = int(sr / 2);
 
     defaultStep = mapper.getPositionForValue(initialMax) - minStep;
 
@@ -3519,7 +3338,7 @@
 {
     if (!m_model) return 0;
 
-    float dmin, dmax;
+    double dmin, dmax;
     getDisplayExtents(dmin, dmax);
     
     SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize);
@@ -3533,16 +3352,16 @@
 {
     if (!m_model) return;
 
-    float dmin = m_minFrequency, dmax = m_maxFrequency;
+    double dmin = m_minFrequency, dmax = m_maxFrequency;
 //    getDisplayExtents(dmin, dmax);
 
 //    cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl;
     
-    int sr = m_model->getSampleRate();
+    sv_samplerate_t sr = m_model->getSampleRate();
     SpectrogramRangeMapper mapper(sr, m_fftSize);
-    float newdist = mapper.getValueForPosition(step);
-
-    float newmin, newmax;
+    double newdist = mapper.getValueForPosition(step);
+
+    double newmin, newmax;
 
     if (m_frequencyScale == LogFrequencyScale) {
 
@@ -3567,20 +3386,20 @@
         // = dmin.dmax
         // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2
 
-        newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2;
+        newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
         newmin = newmax - newdist;
 
 //        cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
 
     } else {
-        float dmid = (dmax + dmin) / 2;
+        double dmid = (dmax + dmin) / 2;
         newmin = dmid - newdist / 2;
         newmax = dmid + newdist / 2;
     }
 
-    float mmin, mmax;
+    double mmin, mmax;
     mmin = 0;
-    mmax = float(sr) / 2;
+    mmax = double(sr) / 2;
     
     if (newmin < mmin) {
         newmax += (mmin - newmin);
@@ -3592,8 +3411,8 @@
     
 //    SVDEBUG << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
 
-    setMinFrequency(lrintf(newmin));
-    setMaxFrequency(lrintf(newmax));
+    setMinFrequency(int(lrint(newmin)));
+    setMaxFrequency(int(lrint(newmax)));
 }
 
 RangeMapper *
@@ -3604,13 +3423,13 @@
 }
 
 void
-SpectrogramLayer::updateMeasureRectYCoords(View *v, const MeasureRect &r) const
+SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const
 {
     int y0 = 0;
-    if (r.startY > 0.0) y0 = getYForFrequency(v, r.startY);
+    if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY));
     
     int y1 = y0;
-    if (r.endY > 0.0) y1 = getYForFrequency(v, r.endY);
+    if (r.endY > 0.0) y1 = int(getYForFrequency(v, r.endY));
 
 //    SVDEBUG << "SpectrogramLayer::updateMeasureRectYCoords: start " << r.startY << " -> " << y0 << ", end " << r.endY << " -> " << y1 << endl;
 
@@ -3618,7 +3437,7 @@
 }
 
 void
-SpectrogramLayer::setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const
+SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const
 {
     if (start) {
         r.startY = getFrequencyForY(v, y);
@@ -3653,19 +3472,38 @@
 		 "colourScheme=\"%4\" "
 		 "colourRotation=\"%5\" "
 		 "frequencyScale=\"%6\" "
-		 "binDisplay=\"%7\" "
-		 "normalizeColumns=\"%8\" "
-                 "normalizeVisibleArea=\"%9\"")
+		 "binDisplay=\"%7\" ")
 	.arg(m_minFrequency)
 	.arg(m_maxFrequency)
 	.arg(m_colourScale)
 	.arg(m_colourMap)
 	.arg(m_colourRotation)
 	.arg(m_frequencyScale)
-	.arg(m_binDisplay)
-	.arg(m_normalizeColumns ? "true" : "false")
-        .arg(m_normalizeVisibleArea ? "true" : "false");
-
+	.arg(m_binDisplay);
+
+    // New-style normalization attributes, allowing for more types of
+    // normalization in future: write out the column normalization
+    // type separately, and then whether we are normalizing visible
+    // area as well afterwards
+    
+    s += QString("columnNormalization=\"%1\" ")
+        .arg(m_normalization == NormalizeColumns ? "peak" :
+             m_normalization == NormalizeHybrid ? "hybrid" : "none");
+
+    // Old-style normalization attribute. We *don't* write out
+    // normalizeHybrid here because the only release that would accept
+    // it (Tony v1.0) has a totally different scale factor for
+    // it. We'll just have to accept that session files from Tony
+    // v2.0+ will look odd in Tony v1.0
+    
+    s += QString("normalizeColumns=\"%1\" ")
+	.arg(m_normalization == NormalizeColumns ? "true" : "false");
+
+    // And this applies to both old- and new-style attributes
+    
+    s += QString("normalizeVisibleArea=\"%1\" ")
+        .arg(m_normalization == NormalizeVisibleArea ? "true" : "false");
+    
     Layer::toXml(stream, indent, extraAttributes + " " + s);
 }
 
@@ -3677,13 +3515,13 @@
     int channel = attributes.value("channel").toInt(&ok);
     if (ok) setChannel(channel);
 
-    size_t windowSize = attributes.value("windowSize").toUInt(&ok);
+    int windowSize = attributes.value("windowSize").toUInt(&ok);
     if (ok) setWindowSize(windowSize);
 
-    size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
+    int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
     if (ok) setWindowHopLevel(windowHopLevel);
     else {
-        size_t windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
+        int windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
         // a percentage value
         if (ok) {
             if (windowOverlap == 0) setWindowHopLevel(0);
@@ -3700,13 +3538,13 @@
     float threshold = attributes.value("threshold").toFloat(&ok);
     if (ok) setThreshold(threshold);
 
-    size_t minFrequency = attributes.value("minFrequency").toUInt(&ok);
+    int minFrequency = attributes.value("minFrequency").toUInt(&ok);
     if (ok) {
         SVDEBUG << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << endl;
         setMinFrequency(minFrequency);
     }
 
-    size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
+    int maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
     if (ok) {
         SVDEBUG << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << endl;
         setMaxFrequency(maxFrequency);
@@ -3730,12 +3568,54 @@
 	attributes.value("binDisplay").toInt(&ok);
     if (ok) setBinDisplay(binDisplay);
 
-    bool normalizeColumns =
-	(attributes.value("normalizeColumns").trimmed() == "true");
-    setNormalizeColumns(normalizeColumns);
+    bool haveNewStyleNormalization = false;
+    
+    QString columnNormalization = attributes.value("columnNormalization");
+
+    if (columnNormalization != "") {
+
+        haveNewStyleNormalization = true;
+
+        if (columnNormalization == "peak") {
+            setNormalization(NormalizeColumns);
+        } else if (columnNormalization == "hybrid") {
+            setNormalization(NormalizeHybrid);
+        } else if (columnNormalization == "none") {
+            // do nothing
+        } else {
+            cerr << "NOTE: Unknown or unsupported columnNormalization attribute \""
+                 << columnNormalization << "\"" << endl;
+        }
+    }
+
+    if (!haveNewStyleNormalization) {
+
+        bool normalizeColumns =
+            (attributes.value("normalizeColumns").trimmed() == "true");
+        if (normalizeColumns) {
+            setNormalization(NormalizeColumns);
+        }
+
+        bool normalizeHybrid =
+            (attributes.value("normalizeHybrid").trimmed() == "true");
+        if (normalizeHybrid) {
+            setNormalization(NormalizeHybrid);
+        }
+    }
 
     bool normalizeVisibleArea =
 	(attributes.value("normalizeVisibleArea").trimmed() == "true");
-    setNormalizeVisibleArea(normalizeVisibleArea);
+    if (normalizeVisibleArea) {
+        setNormalization(NormalizeVisibleArea);
+    }
+
+    if (!haveNewStyleNormalization && m_normalization == NormalizeHybrid) {
+        // Tony v1.0 is (and hopefully will remain!) the only released
+        // SV-a-like to use old-style attributes when saving sessions
+        // that ask for hybrid normalization. It saves them with the
+        // wrong gain factor, so hack in a fix for that here -- this
+        // gives us backward but not forward compatibility.
+        setGain(m_gain / float(m_fftSize / 2));
+    }
 }
     
--- a/layer/SpectrogramLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SpectrogramLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _SPECTROGRAM_LAYER_H_
-#define _SPECTROGRAM_LAYER_H_
+#ifndef SPECTROGRAM_LAYER_H
+#define SPECTROGRAM_LAYER_H
 
 #include "SliceableLayer.h"
 #include "base/Window.h"
@@ -25,6 +25,8 @@
 #include "data/model/DenseTimeValueModel.h"
 #include "data/model/FFTModel.h"
 
+#include "ScrollableImageCache.h"
+
 #include <QMutex>
 #include <QWaitCondition>
 #include <QImage>
@@ -57,23 +59,23 @@
 
     virtual const ZoomConstraint *getZoomConstraint() const { return this; }
     virtual const Model *getModel() const { return m_model; }
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
     virtual void setSynchronousPainting(bool synchronous);
 
-    virtual int getVerticalScaleWidth(View *v, bool detailed, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const;
 
-    virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos,
+    virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos,
                                      std::vector<QRect> &extents) const;
-    virtual void paintCrosshairs(View *, QPainter &, QPoint) const;
+    virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void measureDoubleClick(View *, QMouseEvent *);
+    virtual void measureDoubleClick(LayerGeometryProvider *, QMouseEvent *);
 
     virtual bool hasLightBackground() const;
 
@@ -88,6 +90,8 @@
                                          int *min, int *max, int *deflt) const;
     virtual QString getPropertyValueLabel(const PropertyName &,
 					  int value) const;
+    virtual QString getPropertyValueIconName(const PropertyName &,
+                                             int value) const;
     virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const;
     virtual void setProperty(const PropertyName &, int value);
 
@@ -99,17 +103,17 @@
     void setChannel(int);
     int getChannel() const;
 
-    void setWindowSize(size_t);
-    size_t getWindowSize() const;
+    void setWindowSize(int);
+    int getWindowSize() const;
     
-    void setWindowHopLevel(size_t level);
-    size_t getWindowHopLevel() const;
+    void setWindowHopLevel(int level);
+    int getWindowHopLevel() const;
 
     void setWindowType(WindowType type);
     WindowType getWindowType() const;
 
-    void setZeroPadLevel(size_t level);
-    size_t getZeroPadLevel() const;
+    void setZeroPadLevel(int level);
+    int getZeroPadLevel() const;
 
     /**
      * Set the gain multiplier for sample values in this view.
@@ -127,11 +131,11 @@
     void setThreshold(float threshold);
     float getThreshold() const;
 
-    void setMinFrequency(size_t);
-    size_t getMinFrequency() const;
+    void setMinFrequency(int);
+    int getMinFrequency() const;
 
-    void setMaxFrequency(size_t); // 0 -> no maximum
-    size_t getMaxFrequency() const;
+    void setMaxFrequency(int); // 0 -> no maximum
+    int getMaxFrequency() const;
 
     enum ColourScale {
 	LinearColourScale,
@@ -171,12 +175,23 @@
     void setBinDisplay(BinDisplay);
     BinDisplay getBinDisplay() const;
 
-    void setNormalizeColumns(bool n);
-    bool getNormalizeColumns() const;
+    enum Normalization {
+        NoNormalization,
+        NormalizeColumns,
+        NormalizeVisibleArea,
+        NormalizeHybrid
+    };
 
-    void setNormalizeVisibleArea(bool n);
-    bool getNormalizeVisibleArea() const;
+    /**
+     * Specify the normalization mode for bin values.
+     */
+    void setNormalization(Normalization);
+    Normalization getNormalization() const;
 
+    /**
+     * Specify the colour map. See ColourMapper for the colour map
+     * values.
+     */
     void setColourMap(int map);
     int getColourMap() const;
 
@@ -196,29 +211,29 @@
         return ColourHasMeaningfulValue;
     }
 
-    float getYForFrequency(const View *v, float frequency) const;
-    float getFrequencyForY(const View *v, int y) const;
+    double getYForFrequency(const LayerGeometryProvider *v, double frequency) const;
+    double getFrequencyForY(const LayerGeometryProvider *v, int y) const;
 
-    virtual int getCompletion(View *v) const;
-    virtual QString getError(View *v) const;
+    virtual int getCompletion(LayerGeometryProvider *v) const;
+    virtual QString getError(LayerGeometryProvider *v) const;
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
-    virtual bool getDisplayExtents(float &min, float &max) const;
+    virtual bool getDisplayExtents(double &min, double &max) const;
 
-    virtual bool setDisplayExtents(float min, float max);
+    virtual bool setDisplayExtents(double min, double max);
 
-    virtual bool getYScaleValue(const View *, int, float &, QString &) const;
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int, double &, QString &) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
 
     void setProperties(const QXmlAttributes &attributes);
 
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
-    virtual bool isLayerScrollable(const View *) const { return false; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; }
 
     virtual int getVerticalZoomSteps(int &defaultStep) const;
     virtual int getCurrentVerticalZoomStep() const;
@@ -229,43 +244,38 @@
 
 protected slots:
     void cacheInvalid();
-    void cacheInvalid(size_t startFrame, size_t endFrame);
+    void cacheInvalid(sv_frame_t startFrame, sv_frame_t endFrame);
     
     void preferenceChanged(PropertyContainer::PropertyName name);
 
-    void fillTimerTimedOut();
-
 protected:
     const DenseTimeValueModel *m_model; // I do not own this
 
     int                 m_channel;
-    size_t              m_windowSize;
+    int                 m_windowSize;
     WindowType          m_windowType;
-    size_t              m_windowHopLevel;
-    size_t              m_zeroPadLevel;
-    size_t              m_fftSize;
+    int                 m_windowHopLevel;
+    int                 m_zeroPadLevel;
+    int                 m_fftSize;
     float               m_gain;
     float               m_initialGain;
     float               m_threshold;
     float               m_initialThreshold;
     int                 m_colourRotation;
     int                 m_initialRotation;
-    size_t              m_minFrequency;
-    size_t              m_maxFrequency;
-    size_t              m_initialMaxFrequency;
+    int                 m_minFrequency;
+    int                 m_maxFrequency;
+    int                 m_initialMaxFrequency;
     ColourScale         m_colourScale;
     int                 m_colourMap;
     QColor              m_crosshairColour;
     FrequencyScale      m_frequencyScale;
     BinDisplay          m_binDisplay;
-    bool                m_normalizeColumns;
-    bool                m_normalizeVisibleArea;
+    Normalization       m_normalization;
     int                 m_lastEmittedZoomStep;
     bool                m_synchronous;
 
     mutable bool        m_haveDetailedScale;
-    mutable int         m_lastPaintBlockWidth;
-    mutable RealTime    m_lastPaintTime;
 
     enum { NO_VALUE = 0 }; // colour index for unused pixels
 
@@ -286,22 +296,10 @@
 
     Palette m_palette;
 
-    /**
-     * ImageCache covers the area of the view, at view resolution.
-     * Not all of it is necessarily valid at once (it is refreshed
-     * in parts when scrolling, for example).
-     */
-    struct ImageCache
-    {
-        QImage image;
-        QRect validArea;
-        long startFrame;
-        size_t zoomLevel;
-    };
-    typedef std::map<const View *, ImageCache> ViewImageCache;
+    typedef std::map<int, ScrollableImageCache> ViewImageCache; // key is view id
     void invalidateImageCaches();
-    void invalidateImageCaches(size_t startFrame, size_t endFrame);
     mutable ViewImageCache m_imageCaches;
+    ScrollableImageCache &getImageCacheReference(const LayerGeometryProvider *) const;
 
     /**
      * When painting, we draw directly onto the draw buffer and then
@@ -311,30 +309,19 @@
      */
     mutable QImage m_drawBuffer;
 
-    mutable QTimer *m_updateTimer;
-
-    mutable size_t m_candidateFillStartFrame;
     bool m_exiting;
 
     void initialisePalette();
     void rotatePalette(int distance);
 
-    unsigned char getDisplayValue(View *v, float input) const;
-    float getInputForDisplayValue(unsigned char uc) const;
+    unsigned char getDisplayValue(LayerGeometryProvider *v, double input) const;
 
     int getColourScaleWidth(QPainter &) const;
 
-    void illuminateLocalFeatures(View *v, QPainter &painter) const;
+    void illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &painter) const;
 
-    float getEffectiveMinFrequency() const;
-    float getEffectiveMaxFrequency() const;
-
-    struct LayerRange {
-	long   startFrame;
-	int    zoomLevel;
-	size_t modelStart;
-	size_t modelEnd;
-    };
+    double getEffectiveMinFrequency() const;
+    double getEffectiveMaxFrequency() const;
 
     // Note that the getYBin... methods return the nominal bin in the
     // un-smoothed spectrogram.  This is not necessarily the same bin
@@ -342,33 +329,32 @@
     // position, if the spectrogram has oversampling smoothing.  Use
     // getSmoothedYBinRange to obtain that.
 
-    bool getXBinRange(View *v, int x, float &windowMin, float &windowMax) const;
-    bool getYBinRange(View *v, int y, float &freqBinMin, float &freqBinMax) const;
-    bool getSmoothedYBinRange(View *v, int y, float &freqBinMin, float &freqBinMax) const;
+    bool getXBinRange(LayerGeometryProvider *v, int x, double &windowMin, double &windowMax) const;
+    bool getYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const;
+    bool getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const;
 
-    bool getYBinSourceRange(View *v, int y, float &freqMin, float &freqMax) const;
-    bool getAdjustedYBinSourceRange(View *v, int x, int y,
-				    float &freqMin, float &freqMax,
-				    float &adjFreqMin, float &adjFreqMax) const;
-    bool getXBinSourceRange(View *v, int x, RealTime &timeMin, RealTime &timeMax) const;
-    bool getXYBinSourceRange(View *v, int x, int y, float &min, float &max,
-			     float &phaseMin, float &phaseMax) const;
+    bool getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) const;
+    bool getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y,
+				    double &freqMin, double &freqMax,
+				    double &adjFreqMin, double &adjFreqMax) const;
+    bool getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &timeMin, RealTime &timeMax) const;
+    bool getXYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &min, double &max,
+			     double &phaseMin, double &phaseMax) const;
 
-    size_t getWindowIncrement() const {
+    int getWindowIncrement() const {
         if (m_windowHopLevel == 0) return m_windowSize;
         else if (m_windowHopLevel == 1) return (m_windowSize * 3) / 4;
         else return m_windowSize / (1 << (m_windowHopLevel - 1));
     }
 
-    size_t getZeroPadLevel(const View *v) const;
-    size_t getFFTSize(const View *v) const;
-    FFTModel *getFFTModel(const View *v) const;
-    Dense3DModelPeakCache *getPeakCache(const View *v) const;
+    int getZeroPadLevel(const LayerGeometryProvider *v) const;
+    int getFFTSize(const LayerGeometryProvider *v) const;
+    FFTModel *getFFTModel(const LayerGeometryProvider *v) const;
+    Dense3DModelPeakCache *getPeakCache(const LayerGeometryProvider *v) const;
     void invalidateFFTModels();
 
-    typedef std::pair<FFTModel *, int> FFTFillPair; // model, last fill
-    typedef std::map<const View *, FFTFillPair> ViewFFTMap;
-    typedef std::map<const View *, Dense3DModelPeakCache *> PeakCacheMap;
+    typedef std::map<int, FFTModel *> ViewFFTMap; // key is view id
+    typedef std::map<int, Dense3DModelPeakCache *> PeakCacheMap; // key is view id
     mutable ViewFFTMap m_fftModels;
     mutable PeakCacheMap m_peakCaches;
     mutable Model *m_sliceableModel;
@@ -379,20 +365,19 @@
         bool operator==(const MagnitudeRange &r) {
             return r.m_min == m_min && r.m_max == m_max;
         }
-        bool isSet() const { return (m_min != 0 || m_max != 0); }
+        bool isSet() const { return (m_min != 0.f || m_max != 0.f); }
         void set(float min, float max) {
-            m_min = convert(min);
-            m_max = convert(max);
+            m_min = min;
+            m_max = max;
             if (m_max < m_min) m_max = m_min;
         }
         bool sample(float f) {
-            unsigned int ui = convert(f);
             bool changed = false;
             if (isSet()) {
-                if (ui < m_min) { m_min = ui; changed = true; }
-                if (ui > m_max) { m_max = ui; changed = true; }
+                if (f < m_min) { m_min = f; changed = true; }
+                if (f > m_max) { m_max = f; changed = true; }
             } else {
-                m_max = m_min = ui;
+                m_max = m_min = f;
                 changed = true;
             }
             return changed;
@@ -409,40 +394,40 @@
             }
             return changed;
         }            
-        float getMin() const { return float(m_min) / UINT_MAX; }
-        float getMax() const { return float(m_max) / UINT_MAX; }
+        float getMin() const { return m_min; }
+        float getMax() const { return m_max; }
     private:
-        unsigned int m_min;
-        unsigned int m_max;
-        unsigned int convert(float f) {
-            if (f < 0.f) f = 0.f;
-            if (f > 1.f) f = 1.f;
-            return (unsigned int)(f * UINT_MAX);
-        }
+        float m_min;
+        float m_max;
     };
 
-    typedef std::map<const View *, MagnitudeRange> ViewMagMap;
+    typedef std::map<int, MagnitudeRange> ViewMagMap; // key is view id
     mutable ViewMagMap m_viewMags;
     mutable std::vector<MagnitudeRange> m_columnMags;
     void invalidateMagnitudes();
-    bool updateViewMagnitudes(View *v) const;
-    bool paintDrawBuffer(View *v, int w, int h,
-                         int *binforx, float *binfory,
-                         bool usePeaksCache,
-                         MagnitudeRange &overallMag,
-                         bool &overallMagChanged) const;
-    bool paintDrawBufferPeakFrequencies(View *v, int w, int h,
-                                        int *binforx,
-                                        int minbin,
-                                        int maxbin,
-                                        float displayMinFreq,
-                                        float displayMaxFreq,
-                                        bool logarithmic,
-                                        MagnitudeRange &overallMag,
-                                        bool &overallMagChanged) const;
+    bool updateViewMagnitudes(LayerGeometryProvider *v) const;
+    int paintDrawBuffer(LayerGeometryProvider *v, int w, int h,
+                        const std::vector<int> &binforx,
+                        const std::vector<double> &binfory,
+                        bool usePeaksCache,
+                        MagnitudeRange &overallMag,
+                        bool &overallMagChanged,
+                        bool rightToLeft,
+                        double softTimeLimit) const;
+    int paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, int w, int h,
+                                       const std::vector<int> &binforx,
+                                       int minbin,
+                                       int maxbin,
+                                       double displayMinFreq,
+                                       double displayMaxFreq,
+                                       bool logarithmic,
+                                       MagnitudeRange &overallMag,
+                                       bool &overallMagChanged,
+                                       bool rightToLeft,
+                                       double softTimeLimit) const;
 
-    virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const;
-    virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const;
+    virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const;
+    virtual void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const;
 };
 
 #endif
--- a/layer/SpectrumLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SpectrumLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -112,21 +112,15 @@
                                     m_windowType,
                                     m_windowSize,
                                     getWindowIncrement(),
-                                    m_windowSize,
-                                    false,
-                                    StorageAdviser::Criteria
-                                    (StorageAdviser::SpeedCritical |
-                                     StorageAdviser::FrequentLookupLikely));
+                                    m_windowSize);
 
     setSliceableModel(newFFT);
 
     m_biasCurve.clear();
-    for (size_t i = 0; i < m_windowSize; ++i) {
+    for (int i = 0; i < m_windowSize; ++i) {
         m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
     }
 
-    newFFT->resume();
-
     m_newFFTNeeded = false;
 }
 
@@ -257,7 +251,7 @@
 }
 
 void
-SpectrumLayer::setWindowSize(size_t ws)
+SpectrumLayer::setWindowSize(int ws)
 {
     if (m_windowSize == ws) return;
     m_windowSize = ws;
@@ -266,7 +260,7 @@
 }
 
 void
-SpectrumLayer::setWindowHopLevel(size_t v)
+SpectrumLayer::setWindowHopLevel(int v)
 {
     if (m_windowHopLevel == v) return;
     m_windowHopLevel = v;
@@ -301,42 +295,42 @@
 }
 
 bool
-SpectrumLayer::getValueExtents(float &, float &, bool &, QString &) const
+SpectrumLayer::getValueExtents(double &, double &, bool &, QString &) const
 {
     return false;
 }
 
-float
-SpectrumLayer::getXForBin(int bin, int totalBins, float w) const
+double
+SpectrumLayer::getXForBin(int bin, int totalBins, double w) const
 {
     if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w);
 
-    float sampleRate = m_sliceableModel->getSampleRate();
-    float binfreq = (sampleRate * bin) / (totalBins * 2);
+    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
+    double binfreq = (sampleRate * bin) / (totalBins * 2);
     
     return getXForFrequency(binfreq, w);
 }
 
 int
-SpectrumLayer::getBinForX(float x, int totalBins, float w) const
+SpectrumLayer::getBinForX(double x, int totalBins, double w) const
 {
     if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w);
 
-    float sampleRate = m_sliceableModel->getSampleRate();
-    float binfreq = getFrequencyForX(x, w);
+    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
+    double binfreq = getFrequencyForX(x, w);
 
     return int((binfreq * totalBins * 2) / sampleRate);
 }
 
-float
-SpectrumLayer::getFrequencyForX(float x, float w) const
+double
+SpectrumLayer::getFrequencyForX(double x, double w) const
 {
-    float freq = 0;
+    double freq = 0;
     if (!m_sliceableModel) return 0;
 
-    int sampleRate = m_sliceableModel->getSampleRate();
+    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
 
-    float maxfreq = float(sampleRate) / 2;
+    double maxfreq = double(sampleRate) / 2;
 
     switch (m_binScale) {
 
@@ -345,26 +339,26 @@
         break;
         
     case LogBins:
-        freq = powf(10.f, (x * log10f(maxfreq)) / w);
+        freq = pow(10.0, (x * log10(maxfreq)) / w);
         break;
 
     case InvertedLogBins:
-        freq = maxfreq - powf(10.f, ((w - x) * log10f(maxfreq)) / w);
+        freq = maxfreq - pow(10.0, ((w - x) * log10(maxfreq)) / w);
         break;
     }
 
     return freq;
 }
 
-float
-SpectrumLayer::getXForFrequency(float freq, float w) const
+double
+SpectrumLayer::getXForFrequency(double freq, double w) const
 {
-    float x = 0;
+    double x = 0;
     if (!m_sliceableModel) return x;
 
-    int sampleRate = m_sliceableModel->getSampleRate();
+    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
 
-    float maxfreq = float(sampleRate) / 2;
+    double maxfreq = double(sampleRate) / 2;
 
     switch (m_binScale) {
 
@@ -373,12 +367,12 @@
         break;
         
     case LogBins:
-        x = (log10f(freq) * w) / log10f(maxfreq);
+        x = (log10(freq) * w) / log10(maxfreq);
         break;
 
     case InvertedLogBins:
         if (maxfreq == freq) x = w;
-        else x = w - (log10f(maxfreq - freq) * w) / log10f(maxfreq);
+        else x = w - (log10(maxfreq - freq) * w) / log10(maxfreq);
         break;
     }
 
@@ -386,26 +380,26 @@
 }
 
 bool
-SpectrumLayer::getXScaleValue(const View *v, int x, 
-                              float &value, QString &unit) const
+SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, 
+                              double &value, QString &unit) const
 {
     if (m_xorigins.find(v) == m_xorigins.end()) return false;
     int xorigin = m_xorigins.find(v)->second;
-    value = getFrequencyForX(x - xorigin, v->width() - xorigin - 1);
+    value = getFrequencyForX(x - xorigin, v->getPaintWidth() - xorigin - 1);
     unit = "Hz";
     return true;
 }
 
 bool
-SpectrumLayer::getYScaleValue(const View *v, int y,
-                              float &value, QString &unit) const
+SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
+                              double &value, QString &unit) const
 {
     value = getValueForY(y, v);
 
     if (m_energyScale == dBScale || m_energyScale == MeterScale) {
 
-        if (value > 0.f) {
-            value = 10.f * log10f(value);
+        if (value > 0.0) {
+            value = 10.0 * log10(value);
             if (value < m_threshold) value = m_threshold;
         } else value = m_threshold;
 
@@ -419,8 +413,8 @@
 }
 
 bool
-SpectrumLayer::getYScaleDifference(const View *v, int y0, int y1,
-                                   float &diff, QString &unit) const
+SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
+                                   double &diff, QString &unit) const
 {
     bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
     if (rv && (unit == "dBV")) unit = "dB";
@@ -429,14 +423,14 @@
 
 
 bool
-SpectrumLayer::getCrosshairExtents(View *v, QPainter &paint,
+SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
                                    QPoint cursorPos,
                                    std::vector<QRect> &extents) const
 {
-    QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y());
+    QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
     extents.push_back(vertical);
 
-    QRect horizontal(0, cursorPos.y(), v->width(), 12);
+    QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
     extents.push_back(horizontal);
 
     int hoffset = 2;
@@ -455,14 +449,14 @@
     extents.push_back(log);
 
     QRect freq(cursorPos.x(),
-               v->height() - paint.fontMetrics().height() - hoffset,
+               v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
                paint.fontMetrics().width("123456 Hz") + 2,
                paint.fontMetrics().height());
     extents.push_back(freq);
 
     int w(paint.fontMetrics().width("C#10+50c") + 2);
     QRect pitch(cursorPos.x() - w,
-                v->height() - paint.fontMetrics().height() - hoffset,
+                v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
                 w,
                 paint.fontMetrics().height());
     extents.push_back(pitch);
@@ -471,7 +465,7 @@
 }
 
 void
-SpectrumLayer::paintCrosshairs(View *v, QPainter &paint,
+SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                QPoint cursorPos) const
 {
     if (!m_sliceableModel) return;
@@ -487,19 +481,19 @@
     paint.setPen(mapper.getContrastingColour());
 
     int xorigin = m_xorigins[v];
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
     
-    paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y());
-    paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height());
+    paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
+    paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
     
-    float fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
+    double fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
 
     int hoffset = 2;
     if (m_binScale == LogBins) hoffset = 13;
 
     v->drawVisibleText(paint,
                        cursorPos.x() + 2,
-                       v->height() - 2 - hoffset,
+                       v->getPaintHeight() - 2 - hoffset,
                        QString("%1 Hz").arg(fundamental),
                        View::OutlinedText);
 
@@ -507,15 +501,15 @@
         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
         v->drawVisibleText(paint,
                            cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
-                           v->height() - 2 - hoffset,
+                           v->getPaintHeight() - 2 - hoffset,
                            pitchLabel,
                            View::OutlinedText);
     }
 
-    float value = getValueForY(cursorPos.y(), v);
-    float thresh = m_threshold;
-    float db = thresh;
-    if (value > 0.f) db = 10.f * log10f(value);
+    double value = getValueForY(cursorPos.y(), v);
+    double thresh = m_threshold;
+    double db = thresh;
+    if (value > 0.0) db = 10.0 * log10(value);
     if (db < thresh) db = thresh;
 
     v->drawVisibleText(paint,
@@ -534,10 +528,10 @@
 
     while (harmonic < 100) {
 
-        float hx = lrintf(getXForFrequency(fundamental * harmonic, w));
+        int hx = int(lrint(getXForFrequency(fundamental * harmonic, w)));
         hx += xorigin;
 
-        if (hx < xorigin || hx > v->width()) break;
+        if (hx < xorigin || hx > v->getPaintWidth()) break;
         
         int len = 7;
 
@@ -549,9 +543,9 @@
             }
         }
 
-        paint.drawLine(int(hx),
+        paint.drawLine(hx,
                        cursorPos.y(),
-                       int(hx),
+                       hx,
                        cursorPos.y() + len);
 
         ++harmonic;
@@ -561,31 +555,31 @@
 }
 
 QString
-SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const
+SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
 {
     if (!m_sliceableModel) return "";
 
     int minbin = 0, maxbin = 0, range = 0;
-    QString genericDesc = SliceLayer::getFeatureDescription
+    QString genericDesc = SliceLayer::getFeatureDescriptionAux
         (v, p, false, minbin, maxbin, range);
 
     if (genericDesc == "") return "";
 
-    float minvalue = 0.f;
+    double minvalue = 0.f;
     if (minbin < int(m_values.size())) minvalue = m_values[minbin];
 
-    float maxvalue = minvalue;
+    double maxvalue = minvalue;
     if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin];
         
     if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
     
     QString binstr;
     QString hzstr;
-    int minfreq = lrintf((minbin * m_sliceableModel->getSampleRate()) /
-                         m_windowSize);
-    int maxfreq = lrintf((std::max(maxbin, minbin+1)
-                           * m_sliceableModel->getSampleRate()) /
-                          m_windowSize);
+    int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
+                            m_windowSize));
+    int maxfreq = int(lrint((std::max(maxbin, minbin+1)
+                             * m_sliceableModel->getSampleRate()) /
+                            m_windowSize));
 
     if (maxbin != minbin) {
         binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
@@ -606,21 +600,21 @@
     }
     
     QString dbstr;
-    float mindb = AudioLevel::multiplier_to_dB(minvalue);
-    float maxdb = AudioLevel::multiplier_to_dB(maxvalue);
+    double mindb = AudioLevel::multiplier_to_dB(minvalue);
+    double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
     QString mindbstr;
     QString maxdbstr;
     if (mindb == AudioLevel::DB_FLOOR) {
         mindbstr = tr("-Inf");
     } else {
-        mindbstr = QString("%1").arg(lrintf(mindb));
+        mindbstr = QString("%1").arg(lrint(mindb));
     }
     if (maxdb == AudioLevel::DB_FLOOR) {
         maxdbstr = tr("-Inf");
     } else {
-        maxdbstr = QString("%1").arg(lrintf(maxdb));
+        maxdbstr = QString("%1").arg(lrint(maxdb));
     }
-    if (lrintf(mindb) != lrintf(maxdb)) {
+    if (lrint(mindb) != lrint(maxdb)) {
         dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
     } else {
         dbstr = tr("%1").arg(mindbstr);
@@ -650,7 +644,7 @@
 }
 
 void
-SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
+SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_originModel || !m_originModel->isOK() ||
         !m_originModel->isReady()) {
@@ -666,10 +660,10 @@
     FFTModel *fft = dynamic_cast<FFTModel *>
         (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
 
-    float thresh = (powf(10, -6) / m_gain) * (m_windowSize / 2.f); // -60dB adj
+    double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
 
     int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
 
     int pkh = 0;
 //!!!    if (m_binScale == LogBins) {
@@ -684,7 +678,7 @@
 
 //        SVDEBUG << "Showing peaks..." << endl;
 
-        size_t col = v->getCentreFrame() / fft->getResolution();
+        int col = int(v->getCentreFrame() / fft->getResolution());
 
         paint.save();
         paint.setRenderHint(QPainter::Antialiasing, false);
@@ -692,8 +686,8 @@
 
         int peakminbin = 0;
         int peakmaxbin = fft->getHeight() - 1;
-        float peakmaxfreq = Pitch::getFrequencyForPitch(128);
-        peakmaxbin = ((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate());
+        double peakmaxfreq = Pitch::getFrequencyForPitch(128);
+        peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate()));
         
         FFTModel::PeakSet peaks = fft->getPeakFrequencies
             (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
@@ -702,12 +696,12 @@
 
         BiasCurve curve;
         getBiasCurve(curve);
-        size_t cs = curve.size();
+        int cs = int(curve.size());
 
-        std::vector<float> values;
+        std::vector<double> values;
         
-        for (size_t bin = 0; bin < fft->getHeight(); ++bin) {
-            float value = m_sliceableModel->getValueAt(col, bin);
+        for (int bin = 0; bin < fft->getHeight(); ++bin) {
+            double value = m_sliceableModel->getValueAt(col, bin);
             if (bin < cs) value *= curve[bin];
             values.push_back(value);
         }
@@ -715,21 +709,21 @@
         for (FFTModel::PeakSet::iterator i = peaks.begin();
              i != peaks.end(); ++i) {
 
-            size_t bin = i->first;
+            int bin = i->first;
             
 //            cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
 
-            if (!fft->isOverThreshold(col, bin, thresh)) continue;
+            if (!fft->isOverThreshold(col, bin, float(thresh))) continue;
             
-            float freq = i->second;
+            double freq = i->second;
           
-            int x = lrintf(getXForFrequency(freq, w));
+            int x = int(lrint(getXForFrequency(freq, w)));
 
-            float norm = 0.f;
-            float y = getYForValue(values[bin], v, norm); // don't need y, need norm
+            double norm = 0.f;
+            (void)getYForValue(values[bin], v, norm); // don't need return value, need norm
 
             paint.setPen(mapper.map(norm));
-            paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1);
+            paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
         }
 
         paint.restore();
@@ -749,7 +743,7 @@
 //    if (m_binScale == LogBins) {
 
 //        int pkh = 10;
-        int h = v->height();
+        int h = v->getPaintHeight();
 
         // piano keyboard
         //!!! should be in a new paintHorizontalScale()?
@@ -762,8 +756,8 @@
 
 	for (int i = 0; i < 128; ++i) {
 
-	    float f = Pitch::getFrequencyForPitch(i);
-	    int x = lrintf(getXForFrequency(f, w));
+	    double f = Pitch::getFrequencyForPitch(i);
+	    int x = int(lrint(getXForFrequency(f, w)));
                            
             x += xorigin;
 
@@ -805,7 +799,7 @@
 	    if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
 		// black notes
 		paint.drawLine(x, h - pkh, x, h);
-		int rw = lrintf(float(x - px) / 4) * 2;
+		int rw = int(lrint(double(x - px) / 4) * 2);
 		if (rw < 2) rw = 2;
 		paint.drawRect(x - rw/2, h - pkh, rw, pkh/2);
 	    } else if (n == 0 || n == 5) {
@@ -850,10 +844,10 @@
 
     bool ok = false;
 
-    size_t windowSize = attributes.value("windowSize").toUInt(&ok);
+    int windowSize = attributes.value("windowSize").toUInt(&ok);
     if (ok) setWindowSize(windowSize);
 
-    size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
+    int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
     if (ok) setWindowHopLevel(windowHopLevel);
 
     bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
--- a/layer/SpectrumLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/SpectrumLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -39,13 +39,13 @@
     void setModel(DenseTimeValueModel *model);
     virtual const Model *getModel() const { return m_originModel; }
 
-    virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos,
+    virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos,
                                      std::vector<QRect> &extents) const;
-    virtual void paintCrosshairs(View *, QPainter &, QPoint) const;
+    virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
     virtual VerticalPosition getPreferredFrameCountPosition() const {
 	return PositionTop;
@@ -64,28 +64,28 @@
     virtual void setProperty(const PropertyName &, int value);
     virtual void setProperties(const QXmlAttributes &);
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
-    virtual bool getXScaleValue(const View *v, int x,
-                                float &value, QString &unit) const;
+    virtual bool getXScaleValue(const LayerGeometryProvider *v, int x,
+                                double &value, QString &unit) const;
 
-    virtual bool getYScaleValue(const View *, int y,
-                                float &value, QString &unit) const;
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int y,
+                                double &value, QString &unit) const;
 
-    virtual bool getYScaleDifference(const View *, int y0, int y1,
-                                     float &diff, QString &unit) const;
+    virtual bool getYScaleDifference(const LayerGeometryProvider *, int y0, int y1,
+                                     double &diff, QString &unit) const;
 
-    virtual bool isLayerScrollable(const View *) const { return false; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; }
 
     void setChannel(int);
     int getChannel() const { return m_channel; }
 
-    void setWindowSize(size_t);
-    size_t getWindowSize() const { return m_windowSize; }
+    void setWindowSize(int);
+    int getWindowSize() const { return m_windowSize; }
     
-    void setWindowHopLevel(size_t level);
-    size_t getWindowHopLevel() const { return m_windowHopLevel; }
+    void setWindowHopLevel(int level);
+    int getWindowHopLevel() const { return m_windowHopLevel; }
 
     void setWindowType(WindowType type);
     WindowType getWindowType() const { return m_windowType; }
@@ -93,7 +93,7 @@
     void setShowPeaks(bool);
     bool getShowPeaks() const { return m_showPeaks; }
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
@@ -110,9 +110,9 @@
     DenseTimeValueModel    *m_originModel;
     int                     m_channel;
     bool                    m_channelSet;
-    size_t                  m_windowSize;
+    int                  m_windowSize;
     WindowType              m_windowType;
-    size_t                  m_windowHopLevel;
+    int                  m_windowHopLevel;
     bool                    m_showPeaks;
     mutable bool            m_newFFTNeeded;
 
@@ -123,13 +123,13 @@
     virtual void getBiasCurve(BiasCurve &) const;
     BiasCurve m_biasCurve;
 
-    virtual float getXForBin(int bin, int totalBins, float w) const;
-    virtual int getBinForX(float x, int totalBins, float w) const;
+    virtual double getXForBin(int bin, int totalBins, double w) const;
+    virtual int getBinForX(double x, int totalBins, double w) const;
 
-    float getFrequencyForX(float x, float w) const;
-    float getXForFrequency(float freq, float w) const;
+    double getFrequencyForX(double x, double w) const;
+    double getXForFrequency(double freq, double w) const;
 
-    size_t getWindowIncrement() const {
+    int getWindowIncrement() const {
         if (m_windowHopLevel == 0) return m_windowSize;
         else if (m_windowHopLevel == 1) return (m_windowSize * 3) / 4;
         else return m_windowSize / (1 << (m_windowHopLevel - 1));
--- a/layer/TextLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TextLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -96,13 +96,13 @@
 }
 
 bool
-TextLayer::getValueExtents(float &, float &, bool &, QString &) const
+TextLayer::getValueExtents(double &, double &, bool &, QString &) const
 {
     return false;
 }
 
 bool
-TextLayer::isLayerScrollable(const View *v) const
+TextLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
@@ -110,12 +110,12 @@
 
 
 TextModel::PointList
-TextLayer::getLocalPoints(View *v, int x, int y) const
+TextLayer::getLocalPoints(LayerGeometryProvider *v, int x, int y) const
 {
     if (!m_model) return TextModel::PointList();
 
-    long frame0 = v->getFrameForX(-150);
-    long frame1 = v->getFrameForX(v->width() + 150);
+    sv_frame_t frame0 = v->getFrameForX(-150);
+    sv_frame_t frame1 = v->getFrameForX(v->getPaintWidth() + 150);
     
     TextModel::PointList points(m_model->getPoints(frame0, frame1));
 
@@ -139,9 +139,9 @@
 	    (QRect(0, 0, 150, 200),
 	     Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
 
-	if (py + rect.height() > v->height()) {
-	    if (rect.height() > v->height()) py = 0;
-	    else py = v->height() - rect.height() - 1;
+	if (py + rect.height() > v->getPaintHeight()) {
+	    if (rect.height() > v->getPaintHeight()) py = 0;
+	    else py = v->getPaintHeight() - rect.height() - 1;
 	}
 
 	if (x >= px && x < px + rect.width() &&
@@ -154,23 +154,23 @@
 }
 
 bool
-TextLayer::getPointToDrag(View *v, int x, int y, TextModel::Point &p) const
+TextLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, TextModel::Point &p) const
 {
     if (!m_model) return false;
 
-    long a = v->getFrameForX(x - 120);
-    long b = v->getFrameForX(x + 10);
+    sv_frame_t a = v->getFrameForX(x - 120);
+    sv_frame_t b = v->getFrameForX(x + 10);
     TextModel::PointList onPoints = m_model->getPoints(a, b);
     if (onPoints.empty()) return false;
 
-    float nearestDistance = -1;
+    double nearestDistance = -1;
 
     for (TextModel::PointList::const_iterator i = onPoints.begin();
          i != onPoints.end(); ++i) {
 
-        int yd = getYForHeight(v, (*i).height) - y;
-        int xd = v->getXForFrame((*i).frame) - x;
-        float distance = sqrtf(yd*yd + xd*xd);
+        double yd = getYForHeight(v, (*i).height) - y;
+        double xd = v->getXForFrame((*i).frame) - x;
+        double distance = sqrt(yd*yd + xd*xd);
 
         if (nearestDistance == -1 || distance < nearestDistance) {
             nearestDistance = distance;
@@ -182,7 +182,7 @@
 }
 
 QString
-TextLayer::getFeatureDescription(View *v, QPoint &pos) const
+TextLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -198,7 +198,7 @@
 	}
     }
 
-    long useFrame = points.begin()->frame;
+    sv_frame_t useFrame = points.begin()->frame;
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
     
@@ -220,8 +220,8 @@
 //!!! too much overlap with TimeValueLayer/TimeInstantLayer
 
 bool
-TextLayer::snapToFeatureFrame(View *v, int &frame,
-			      size_t &resolution,
+TextLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+			      int &resolution,
 			      SnapType snap) const
 {
     if (!m_model) {
@@ -240,7 +240,7 @@
     }    
 
     points = m_model->getPoints(frame, frame);
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
 
     for (TextModel::PointList::const_iterator i = points.begin();
@@ -292,32 +292,32 @@
 }
 
 int
-TextLayer::getYForHeight(View *v, float height) const
+TextLayer::getYForHeight(LayerGeometryProvider *v, double height) const
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
     return h - int(height * h);
 }
 
-float
-TextLayer::getHeightForY(View *v, int y) const
+double
+TextLayer::getHeightForY(LayerGeometryProvider *v, int y) const
 {
-    int h = v->height();
-    return float(h - y) / h;
+    int h = v->getPaintHeight();
+    return double(h - y) / h;
 }
 
 void
-TextLayer::paint(View *v, QPainter &paint, QRect rect) const
+TextLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
-    int sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("TextLayer::paint", true);
 
     int x0 = rect.left(), x1 = rect.right();
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1);
+    sv_frame_t frame0 = v->getFrameForX(x0);
+    sv_frame_t frame1 = v->getFrameForX(x1);
 
     TextModel::PointList points(m_model->getPoints(frame0, frame1));
     if (points.empty()) return;
@@ -336,7 +336,7 @@
 
     QPoint localPos;
     TextModel::Point illuminatePoint(0);
-    bool shouldIlluminate;
+    bool shouldIlluminate = false;
 
     if (v->shouldIlluminateLocalFeatures(this, localPos)) {
         shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
@@ -347,7 +347,7 @@
     int boxMaxHeight = 200;
 
     paint.save();
-    paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->height());
+    paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->getPaintHeight());
     
     for (TextModel::PointList::const_iterator i = points.begin();
 	 i != points.end(); ++i) {
@@ -380,9 +380,9 @@
 	QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height());
 	boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2);
 
-	if (y + boxRect.height() > v->height()) {
-	    if (boxRect.height() > v->height()) y = 0;
-	    else y = v->height() - boxRect.height() - 1;
+	if (y + boxRect.height() > v->getPaintHeight()) {
+	    if (boxRect.height() > v->getPaintHeight()) y = 0;
+	    else y = v->getPaintHeight() - boxRect.height() - 1;
 	}
 
 	boxRect = QRect(x, y, boxRect.width(), boxRect.height());
@@ -411,7 +411,7 @@
 }
 
 void
-TextLayer::drawStart(View *v, QMouseEvent *e)
+TextLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -420,13 +420,13 @@
 	return;
     }
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float height = getHeightForY(v, e->y());
+    double height = getHeightForY(v, e->y());
 
-    m_editingPoint = TextModel::Point(frame, height, "");
+    m_editingPoint = TextModel::Point(frame, float(height), "");
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
@@ -437,35 +437,35 @@
 }
 
 void
-TextLayer::drawDrag(View *v, QMouseEvent *e)
+TextLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float height = getHeightForY(v, e->y());
+    double height = getHeightForY(v, e->y());
 
     m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = frame;
-    m_editingPoint.height = height;
+    m_editingPoint.height = float(height);
     m_editingCommand->addPoint(m_editingPoint);
 
 
 }
 
 void
-TextLayer::drawEnd(View *v, QMouseEvent *)
+TextLayer::drawEnd(LayerGeometryProvider *v, QMouseEvent *)
 {
 //    SVDEBUG << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
 
     bool ok = false;
-    QString label = QInputDialog::getText(v, tr("Enter label"),
-                      tr("Please enter a new label:"),
+    QString label = QInputDialog::getText(v->getView(), tr("Enter label"),
+					  tr("Please enter a new label:"),
 					  QLineEdit::Normal, "", &ok);
 
     if (ok) {
@@ -482,7 +482,7 @@
 }
 
 void
-TextLayer::eraseStart(View *v, QMouseEvent *e)
+TextLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -497,12 +497,12 @@
 }
 
 void
-TextLayer::eraseDrag(View *v, QMouseEvent *e)
+TextLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-TextLayer::eraseEnd(View *v, QMouseEvent *e)
+TextLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -523,7 +523,7 @@
 }
 
 void
-TextLayer::editStart(View *v, QMouseEvent *e)
+TextLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -545,21 +545,21 @@
 }
 
 void
-TextLayer::editDrag(View *v, QMouseEvent *e)
+TextLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
-    long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
-    float heightDiff = getHeightForY(v, e->y()) - getHeightForY(v, m_editOrigin.y());
+    sv_frame_t frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
+    double heightDiff = getHeightForY(v, e->y()) - getHeightForY(v, m_editOrigin.y());
 
-    long frame = m_originalPoint.frame + frameDiff;
-    float height = m_originalPoint.height + heightDiff;
+    sv_frame_t frame = m_originalPoint.frame + frameDiff;
+    double height = m_originalPoint.height + heightDiff;
 
-//    long frame = v->getFrameForX(e->x());
+//    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = (frame / m_model->getResolution()) * m_model->getResolution();
 
-//    float height = getHeightForY(v, e->y());
+//    double height = getHeightForY(v, e->y());
 
     if (!m_editingCommand) {
 	m_editingCommand = new TextModel::EditCommand(m_model, tr("Drag Label"));
@@ -567,12 +567,12 @@
 
     m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = frame;
-    m_editingPoint.height = height;
+    m_editingPoint.height = float(height);
     m_editingCommand->addPoint(m_editingPoint);
 }
 
 void
-TextLayer::editEnd(View *, QMouseEvent *)
+TextLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -600,7 +600,7 @@
 }
 
 bool
-TextLayer::editOpen(View *v, QMouseEvent *e)
+TextLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -610,7 +610,7 @@
     QString label = text.label;
 
     bool ok = false;
-    label = QInputDialog::getText(v, tr("Enter label"),
+    label = QInputDialog::getText(v->getView(), tr("Enter label"),
 				  tr("Please enter a new label:"),
 				  QLineEdit::Normal, label, &ok);
     if (ok && label != text.label) {
@@ -623,7 +623,7 @@
 }    
 
 void
-TextLayer::moveSelection(Selection s, size_t newStartFrame)
+TextLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
     if (!m_model) return;
 
@@ -667,9 +667,9 @@
 
 	if (s.contains(i->frame)) {
 
-	    double target = i->frame;
-	    target = newSize.getStartFrame() + 
-		double(target - s.getStartFrame()) * ratio;
+	    double target = double(i->frame);
+	    target = double(newSize.getStartFrame()) + 
+		target - double(s.getStartFrame()) * ratio;
 
 	    TextModel::Point newPoint(*i);
 	    newPoint.frame = lrint(target);
@@ -701,7 +701,7 @@
 }
 
 void
-TextLayer::copy(View *v, Selection s, Clipboard &to)
+TextLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -719,7 +719,7 @@
 }
 
 bool
-TextLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
+TextLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -730,7 +730,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
                                   tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
                                   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
                                   QMessageBox::Yes);
@@ -747,7 +747,7 @@
     TextModel::EditCommand *command =
 	new TextModel::EditCommand(m_model, tr("Paste"));
 
-    float valueMin = 0.0, valueMax = 1.0;
+    double valueMin = 0.0, valueMax = 1.0;
     for (Clipboard::PointList::const_iterator i = points.begin();
          i != points.end(); ++i) {
         if (i->haveValue()) {
@@ -761,7 +761,7 @@
          i != points.end(); ++i) {
         
         if (!i->haveFrame()) continue;
-        size_t frame = 0;
+        sv_frame_t frame = 0;
         
         if (!realign) {
             
@@ -780,9 +780,9 @@
         TextModel::Point newPoint(frame);
 
         if (i->haveValue()) {
-            newPoint.height = (i->getValue() - valueMin) / (valueMax - valueMin);
+            newPoint.height = float((i->getValue() - valueMin) / (valueMax - valueMin));
         } else {
-            newPoint.height = 0.5;
+            newPoint.height = 0.5f;
         }
 
         if (i->haveLabel()) {
--- a/layer/TextLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TextLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -32,35 +32,35 @@
 public:
     TextLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void moveSelection(Selection s, size_t newStartFrame);
+    virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
-    virtual bool editOpen(View *, QMouseEvent *); // on double-click
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *); // on double-click
 
     virtual const Model *getModel() const { return m_model; }
     void setModel(TextModel *model);
@@ -74,16 +74,16 @@
 					  int value) const;
     virtual void setProperty(const PropertyName &, int value);
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
@@ -91,14 +91,14 @@
     void setProperties(const QXmlAttributes &attributes);
 
 protected:
-    int getYForHeight(View *v, float height) const;
-    float getHeightForY(View *v, int y) const;
+    int getYForHeight(LayerGeometryProvider *v, double height) const;
+    double getHeightForY(LayerGeometryProvider *v, int y) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    TextModel::PointList getLocalPoints(View *v, int x, int y) const;
+    TextModel::PointList getLocalPoints(LayerGeometryProvider *v, int x, int y) const;
 
-    bool getPointToDrag(View *v, int x, int y, TextModel::Point &) const;
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, TextModel::Point &) const;
 
     TextModel *m_model;
     bool m_editing;
--- a/layer/TimeInstantLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TimeInstantLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -147,14 +147,14 @@
 }
 
 bool
-TimeInstantLayer::isLayerScrollable(const View *v) const
+TimeInstantLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
 }
 
 SparseOneDimensionalModel::PointList
-TimeInstantLayer::getLocalPoints(View *v, int x) const
+TimeInstantLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     // Return a set of points that all have the same frame number, the
     // nearest to the given x coordinate, and that are within a
@@ -162,7 +162,7 @@
 
     if (!m_model) return SparseOneDimensionalModel::PointList();
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     SparseOneDimensionalModel::PointList onPoints =
 	m_model->getPoints(frame);
@@ -201,7 +201,7 @@
 }
 
 QString
-TimeInstantLayer::getLabelPreceding(size_t frame) const
+TimeInstantLayer::getLabelPreceding(sv_frame_t frame) const
 {
     if (!m_model) return "";
     SparseOneDimensionalModel::PointList points = m_model->getPreviousPoints(frame);
@@ -213,7 +213,7 @@
 }
 
 QString
-TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const
+TimeInstantLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -229,7 +229,7 @@
 	}
     }
 
-    long useFrame = points.begin()->frame;
+    sv_frame_t useFrame = points.begin()->frame;
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
     
@@ -249,8 +249,8 @@
 }
 
 bool
-TimeInstantLayer::snapToFeatureFrame(View *v, int &frame,
-				     size_t &resolution,
+TimeInstantLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				     int &resolution,
 				     SnapType snap) const
 {
     if (!m_model) {
@@ -269,7 +269,7 @@
     }    
 
     points = m_model->getPoints(frame, frame);
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
 
     for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
@@ -321,7 +321,7 @@
 }
 
 void
-TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const
+TimeInstantLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -329,8 +329,8 @@
 
     int x0 = rect.left(), x1 = rect.right();
 
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1);
+    sv_frame_t frame0 = v->getFrameForX(x0);
+    sv_frame_t frame1 = v->getFrameForX(x1);
 
     SparseOneDimensionalModel::PointList points(m_model->getPoints
 						(frame0, frame1));
@@ -367,7 +367,7 @@
 //	      << m_model->getResolution() << " frames" << endl;
 
     QPoint localPos;
-    long illuminateFrame = -1;
+    sv_frame_t illuminateFrame = -1;
 
     if (v->shouldIlluminateLocalFeatures(this, localPos)) {
 	SparseOneDimensionalModel::PointList localPoints =
@@ -403,16 +403,16 @@
 	}
 		
 	if (p.frame == illuminateFrame) {
-	    paint.setPen(getForegroundQColor(v));
+	    paint.setPen(getForegroundQColor(v->getView()));
 	} else {
 	    paint.setPen(brushColour);
 	}
 
 	if (m_plotStyle == PlotInstants) {
 	    if (iw > 1) {
-		paint.drawRect(x, 0, iw - 1, v->height() - 1);
+		paint.drawRect(x, 0, iw - 1, v->getPaintHeight() - 1);
 	    } else {
-		paint.drawLine(x, 0, x, v->height() - 1);
+		paint.drawLine(x, 0, x, v->getPaintHeight() - 1);
 	    }
 	} else {
 
@@ -431,11 +431,11 @@
 	    if (nx >= x) {
 		
 		if (illuminateFrame != p.frame &&
-		    (nx < x + 5 || x >= v->width() - 1)) {
+		    (nx < x + 5 || x >= v->getPaintWidth() - 1)) {
 		    paint.setPen(Qt::NoPen);
 		}
 
-                paint.drawRect(x, -1, nx - x, v->height() + 1);
+                paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1);
 	    }
 
 	    odd = !odd;
@@ -466,7 +466,7 @@
 }
 
 void
-TimeInstantLayer::drawStart(View *v, QMouseEvent *e)
+TimeInstantLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << endl;
@@ -474,7 +474,7 @@
 
     if (!m_model) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -489,7 +489,7 @@
 }
 
 void
-TimeInstantLayer::drawDrag(View *v, QMouseEvent *e)
+TimeInstantLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << endl;
@@ -497,7 +497,7 @@
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
     m_editingCommand->deletePoint(m_editingPoint);
@@ -506,7 +506,7 @@
 }
 
 void
-TimeInstantLayer::drawEnd(View *, QMouseEvent *e)
+TimeInstantLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << endl;
@@ -523,7 +523,7 @@
 }
 
 void
-TimeInstantLayer::eraseStart(View *v, QMouseEvent *e)
+TimeInstantLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -541,12 +541,12 @@
 }
 
 void
-TimeInstantLayer::eraseDrag(View *v, QMouseEvent *e)
+TimeInstantLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-TimeInstantLayer::eraseEnd(View *v, QMouseEvent *e)
+TimeInstantLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -567,7 +567,7 @@
 }
 
 void
-TimeInstantLayer::editStart(View *v, QMouseEvent *e)
+TimeInstantLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << endl;
@@ -589,7 +589,7 @@
 }
 
 void
-TimeInstantLayer::editDrag(View *v, QMouseEvent *e)
+TimeInstantLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << endl;
@@ -597,7 +597,7 @@
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -612,7 +612,7 @@
 }
 
 void
-TimeInstantLayer::editEnd(View *, QMouseEvent *e)
+TimeInstantLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << endl;
@@ -631,7 +631,7 @@
 }
 
 bool
-TimeInstantLayer::editOpen(View *v, QMouseEvent *e)
+TimeInstantLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -666,7 +666,7 @@
 }
 
 void
-TimeInstantLayer::moveSelection(Selection s, size_t newStartFrame)
+TimeInstantLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
     if (!m_model) return;
 
@@ -712,9 +712,9 @@
 
 	if (s.contains(i->frame)) {
 
-	    double target = i->frame;
-	    target = newSize.getStartFrame() + 
-		double(target - s.getStartFrame()) * ratio;
+	    double target = double(i->frame);
+	    target = double(newSize.getStartFrame()) +
+		target - double(s.getStartFrame()) * ratio;
 
 	    SparseOneDimensionalModel::Point newPoint(*i);
 	    newPoint.frame = lrint(target);
@@ -747,7 +747,7 @@
 }
 
 void
-TimeInstantLayer::copy(View *v, Selection s, Clipboard &to)
+TimeInstantLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -765,7 +765,7 @@
 }
 
 bool
-TimeInstantLayer::paste(View *v, const Clipboard &from, int frameOffset, bool)
+TimeInstantLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool)
 {
     if (!m_model) return false;
 
@@ -776,7 +776,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted instants?"),
+            QMessageBox::question(v->getView(), tr("Re-align pasted instants?"),
                                   tr("The instants you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
                                   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
                                   QMessageBox::Yes);
@@ -798,7 +798,7 @@
         
         if (!i->haveFrame()) continue;
 
-        size_t frame = 0;
+        sv_frame_t frame = 0;
 
         if (!realign) {
             
--- a/layer/TimeInstantLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TimeInstantLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -33,35 +33,35 @@
     TimeInstantLayer();
     virtual ~TimeInstantLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getLabelPreceding(size_t) const;
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getLabelPreceding(sv_frame_t) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *);
 
-    virtual void moveSelection(Selection s, size_t newStartFrame);
+    virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -84,15 +84,15 @@
     void setPlotStyle(PlotStyle style);
     PlotStyle getPlotStyle() const { return m_plotStyle; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool needsTextLabelHeight() const { return m_model->hasTextLabels(); }
 
-    virtual bool getValueExtents(float &, float &, bool &, QString &) const {
+    virtual bool getValueExtents(double &, double &, bool &, QString &) const {
         return false;
     }
 
@@ -109,14 +109,14 @@
         }
     }
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
 protected:
-    SparseOneDimensionalModel::PointList getLocalPoints(View *v, int) const;
+    SparseOneDimensionalModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    bool clipboardAlignmentDiffers(View *v, const Clipboard &) const;
+    bool clipboardAlignmentDiffers(LayerGeometryProvider *v, const Clipboard &) const;
 
     SparseOneDimensionalModel *m_model;
     bool m_editing;
--- a/layer/TimeRulerLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TimeRulerLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -26,12 +26,11 @@
 
 #include <iostream>
 #include <cmath>
+#include <stdexcept>
 
 //#define DEBUG_TIME_RULER_LAYER 1
 
 
-
-
 TimeRulerLayer::TimeRulerLayer() :
     SingleColourLayer(),
     m_model(0),
@@ -49,8 +48,8 @@
 }
 
 bool
-TimeRulerLayer::snapToFeatureFrame(View *v, int &frame,
-                                   size_t &resolution, SnapType snap) const
+TimeRulerLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+                                   int &resolution, SnapType snap) const
 {
     if (!m_model) {
         resolution = 1;
@@ -60,7 +59,7 @@
     bool q;
     int tick = getMajorTickSpacing(v, q);
     RealTime rtick = RealTime::fromMilliseconds(tick);
-    int rate = m_model->getSampleRate();
+    sv_samplerate_t rate = m_model->getSampleRate();
     
     RealTime rt = RealTime::frame2RealTime(frame, rate);
     double ratio = rt / rtick;
@@ -68,9 +67,9 @@
     int rounded = int(ratio);
     RealTime rdrt = rtick * rounded;
 
-    int left = RealTime::realTime2Frame(rdrt, rate);
-    resolution = RealTime::realTime2Frame(rtick, rate);
-    int right = left + resolution;
+    sv_frame_t left = RealTime::realTime2Frame(rdrt, rate);
+    resolution = int(RealTime::realTime2Frame(rtick, rate));
+    sv_frame_t right = left + resolution;
 
 //    SVDEBUG << "TimeRulerLayer::snapToFeatureFrame: type "
 //              << int(snap) << ", frame " << frame << " (time "
@@ -88,7 +87,7 @@
         
     case SnapNearest:
     {
-        if (abs(frame - left) > abs(right - frame)) {
+        if (llabs(frame - left) > llabs(right - frame)) {
             frame = right;
         } else {
             frame = left;
@@ -141,24 +140,24 @@
 }
 
 int
-TimeRulerLayer::getMajorTickSpacing(View *v, bool &quarterTicks) const
+TimeRulerLayer::getMajorTickSpacing(LayerGeometryProvider *v, bool &quarterTicks) const
 {
     // return value is in milliseconds
 
     if (!m_model || !v) return 1000;
 
-    int sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
     if (!sampleRate) return 1000;
 
-    long startFrame = v->getStartFrame();
-    long endFrame = v->getEndFrame();
+    sv_frame_t startFrame = v->getStartFrame();
+    sv_frame_t endFrame = v->getEndFrame();
 
     int minPixelSpacing = 50;
 
     RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
     RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
 
-    int count = v->width() / minPixelSpacing;
+    int count = v->getPaintWidth() / minPixelSpacing;
     if (count < 1) count = 1;
     RealTime rtGap = (rtEnd - rtStart) / count;
 
@@ -182,6 +181,8 @@
     } else {
 	incms = 1;
 	int ms = rtGap.msec();
+//        cerr << "rtGap.msec = " << ms << ", rtGap = " << rtGap << ", count = " << count << endl;
+//        cerr << "startFrame = " << startFrame << ", endFrame = " << endFrame << " rtStart = " << rtStart << ", rtEnd = " << rtEnd << endl;
 	if (ms > 0) { incms *= 10; ms /= 10; }
 	if (ms > 0) { incms *= 10; ms /= 10; }
 	if (ms > 0) { incms *= 5; ms /= 5; }
@@ -192,7 +193,7 @@
 }
 
 void
-TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const
+TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
 #ifdef DEBUG_TIME_RULER_LAYER
     SVDEBUG << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
@@ -201,10 +202,10 @@
     
     if (!m_model || !m_model->isOK()) return;
 
-    int sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
-    long startFrame = v->getFrameForX(rect.x() - 50);
+    sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
 
 #ifdef DEBUG_TIME_RULER_LAYER
     cerr << "start frame = " << startFrame << endl;
@@ -213,7 +214,7 @@
     bool quarter = false;
     int incms = getMajorTickSpacing(v, quarter);
 
-    int ms = lrint(1000.0 * (double(startFrame) / double(sampleRate)));
+    int ms = int(lrint(1000.0 * (double(startFrame) / double(sampleRate))));
     ms = (ms / incms) * incms - incms;
 
 #ifdef DEBUG_TIME_RULER_LAYER
@@ -226,8 +227,8 @@
     // draw the actual ticks or lines.
 
     int minPixelSpacing = 50;
-    long incFrame = (incms * sampleRate) / 1000;
-    int incX = incFrame / v->getZoomLevel();
+    sv_frame_t incFrame = lrint((incms * sampleRate) / 1000);
+    int incX = int(incFrame / v->getZoomLevel());
     int ticks = 10;
     if (incX < minPixelSpacing * 2) {
 	ticks = quarter ? 4 : 5;
@@ -237,6 +238,13 @@
 
     paint.save();
 
+    // Do not label time zero - we now overlay an opaque area over
+    // time < 0 which would cut it in half
+    int minlabel = 1; // ms
+
+    // used for a sanity check
+    sv_frame_t prevframe = 0;
+    
     while (1) {
 
         // frame is used to determine where to draw the lines, so it
@@ -245,14 +253,21 @@
         // re-drawing with a different start frame).
 
         double dms = ms;
-        long frame = lrint((dms * sampleRate) / 1000.0);
+        sv_frame_t frame = lrint((dms * sampleRate) / 1000.0);
         frame /= v->getZoomLevel();
         frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel
 
+        if (frame == prevframe && prevframe != 0) {
+            cerr << "ERROR: frame == prevframe (== " << frame
+                 << ") in TimeRulerLayer::paint" << endl;
+            throw std::logic_error("frame == prevframe in TimeRulerLayer::paint");
+        }
+        prevframe = frame;
+        
         int x = v->getXForFrame(frame);
 
 #ifdef DEBUG_TIME_RULER_LAYER
-        SVDEBUG << "Considering frame = " << frame << ", x = " << x << endl;
+        cerr << "Considering frame = " << frame << ", x = " << x << endl;
 #endif
 
         if (x >= rect.x() + rect.width() + 50) {
@@ -262,7 +277,7 @@
             break;
         }
 
-	if (x >= rect.x() - 50) {
+	if (x >= rect.x() - 50 && ms >= minlabel) {
 
             RealTime rt = RealTime::fromMilliseconds(ms);
 
@@ -283,11 +298,11 @@
             }
 
             paint.setPen(greyColour);
-            paint.drawLine(x, 0, x, v->height());
+            paint.drawLine(x, 0, x, v->getPaintHeight());
 
             paint.setPen(getBaseQColor());
             paint.drawLine(x, 0, x, 5);
-            paint.drawLine(x, v->height() - 6, x, v->height() - 1);
+            paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
 
             int y;
             switch (m_labelHeight) {
@@ -296,16 +311,16 @@
                 y = 6 + metrics.ascent();
                 break;
             case LabelMiddle:
-                y = v->height() / 2 - metrics.height() / 2 + metrics.ascent();
+                y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
                 break;
             case LabelBottom:
-                y = v->height() - metrics.height() + metrics.ascent() - 6;
+                y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
             }
 
             if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
                 ViewManager::NoOverlays) {
 
-                if (v->getLayer(0) == this) {
+                if (v->getView()->getLayer(0) == this) {
                     // backmost layer, don't worry about outlining the text
                     paint.drawText(x+2 - tw/2, y, text);
                 } else {
@@ -340,14 +355,14 @@
 	    if (ticks == 10) {
 		if ((i % 2) == 1) {
 		    if (i == 5) {
-			paint.drawLine(x, 0, x, v->height());
+			paint.drawLine(x, 0, x, v->getPaintHeight());
 		    } else sz = 3;
 		} else {
 		    sz = 7;
 		}
 	    }
 	    paint.drawLine(x, 0, x, sz);
-	    paint.drawLine(x, v->height() - sz - 1, x, v->height() - 1);
+	    paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
 	}
 
 	ms += incms;
--- a/layer/TimeRulerLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TimeRulerLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -32,7 +32,7 @@
 public:
     TimeRulerLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
     void setModel(Model *);
     virtual const Model *getModel() const { return m_model; }
@@ -41,32 +41,34 @@
     void setLabelHeight(LabelHeight h) { m_labelHeight = h; }
     LabelHeight getLabelHeight() const { return m_labelHeight; }
 
-    virtual bool snapToFeatureFrame(View *, int &, size_t &, SnapType) const;
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &, int &, SnapType) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourIrrelevant;
     }
 
-    virtual bool getValueExtents(float &, float &, bool &, QString &) const {
+    virtual bool getValueExtents(double &, double &, bool &, QString &) const {
         return false;
     }
 
     virtual QString getLayerPresentationName() const;
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
 
     void setProperties(const QXmlAttributes &attributes);
 
+    virtual bool canExistWithoutModel() const { return true; }
+
 protected:
     Model *m_model;
     LabelHeight m_labelHeight;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    int getMajorTickSpacing(View *, bool &quarterTicks) const;
+    int getMajorTickSpacing(LayerGeometryProvider *, bool &quarterTicks) const;
 };
 
 #endif
--- a/layer/TimeValueLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TimeValueLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -20,6 +20,7 @@
 #include "base/Profiler.h"
 #include "base/LogRange.h"
 #include "base/RangeMapper.h"
+#include "base/Pitch.h"
 #include "ColourDatabase.h"
 #include "view/View.h"
 
@@ -315,7 +316,7 @@
 }
 
 bool
-TimeValueLayer::isLayerScrollable(const View *v) const
+TimeValueLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     // We don't illuminate sections in the line or curve modes, so
     // they're always scrollable
@@ -329,7 +330,7 @@
 }
 
 bool
-TimeValueLayer::getValueExtents(float &min, float &max,
+TimeValueLayer::getValueExtents(double &min, double &max,
                                 bool &logarithmic, QString &unit) const
 {
     if (!m_model) return false;
@@ -342,7 +343,7 @@
     unit = getScaleUnits();
 
     if (m_derivative) {
-        max = std::max(fabsf(min), fabsf(max));
+        max = std::max(fabs(min), fabs(max));
         min = -max;
     }
 
@@ -356,7 +357,7 @@
             max = max + 0.5;
             min = min - 0.5;
         } else {
-            float margin = (max - min) / 10.0;
+            double margin = (max - min) / 10.0;
             max = max + margin;
             min = min - margin;
         }
@@ -370,7 +371,7 @@
 }
 
 bool
-TimeValueLayer::getDisplayExtents(float &min, float &max) const
+TimeValueLayer::getDisplayExtents(double &min, double &max) const
 {
     if (!m_model || shouldAutoAlign()) return false;
 
@@ -384,7 +385,7 @@
     }
 
     if (m_derivative) {
-        max = std::max(fabsf(min), fabsf(max));
+        max = std::max(fabs(min), fabs(max));
         min = -max;
     }
 
@@ -396,7 +397,7 @@
 }
 
 bool
-TimeValueLayer::setDisplayExtents(float min, float max)
+TimeValueLayer::setDisplayExtents(double min, double max)
 {
     if (!m_model) return false;
 
@@ -438,7 +439,7 @@
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return 0;
 
-    float dmin, dmax;
+    double dmin, dmax;
     getDisplayExtents(dmin, dmax);
 
     int nr = mapper->getPositionForValue(dmax - dmin);
@@ -461,23 +462,23 @@
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return;
     
-    float min, max;
+    double min, max;
     bool logarithmic;
     QString unit;
     getValueExtents(min, max, logarithmic, unit);
     
-    float dmin, dmax;
+    double dmin, dmax;
     getDisplayExtents(dmin, dmax);
 
-    float newdist = mapper->getValueForPosition(100 - step);
+    double newdist = mapper->getValueForPosition(100 - step);
 
-    float newmin, newmax;
+    double newmin, newmax;
 
     if (logarithmic) {
 
         // see SpectrogramLayer::setVerticalZoomStep
 
-        newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2;
+        newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
         newmin = newmax - newdist;
 
 #ifdef DEBUG_TIME_VALUE_LAYER
@@ -485,7 +486,7 @@
 #endif
 
     } else {
-        float dmid = (dmax + dmin) / 2;
+        double dmid = (dmax + dmin) / 2;
         newmin = dmid - newdist / 2;
         newmax = dmid + newdist / 2;
     }
@@ -512,7 +513,7 @@
     
     RangeMapper *mapper;
 
-    float min, max;
+    double min, max;
     bool logarithmic;
     QString unit;
     getValueExtents(min, max, logarithmic, unit);
@@ -529,11 +530,11 @@
 }
 
 SparseTimeValueModel::PointList
-TimeValueLayer::getLocalPoints(View *v, int x) const
+TimeValueLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     if (!m_model) return SparseTimeValueModel::PointList();
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     SparseTimeValueModel::PointList onPoints =
 	m_model->getPoints(frame);
@@ -553,7 +554,7 @@
 	usePoints = nextPoints;
     } else if (nextPoints.empty()) {
         // stick with prevPoints
-    } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
+    } else if (prevPoints.begin()->frame < v->getStartFrame() &&
 	       !(nextPoints.begin()->frame > v->getEndFrame())) {
 	usePoints = nextPoints;
     } else if (nextPoints.begin()->frame - frame <
@@ -574,7 +575,7 @@
 }
 
 QString
-TimeValueLayer::getLabelPreceding(size_t frame) const
+TimeValueLayer::getLabelPreceding(sv_frame_t frame) const
 {
     if (!m_model) return "";
     SparseTimeValueModel::PointList points = m_model->getPreviousPoints(frame);
@@ -586,7 +587,7 @@
 }
 
 QString
-TimeValueLayer::getFeatureDescription(View *v, QPoint &pos) const
+TimeValueLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -602,24 +603,35 @@
 	}
     }
 
-    long useFrame = points.begin()->frame;
+    sv_frame_t useFrame = points.begin()->frame;
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
     
+    QString valueText;
+    float value = points.begin()->value;
+    QString unit = getScaleUnits();
+
+    if (unit == "Hz") {
+        valueText = tr("%1 Hz (%2, %3)")
+            .arg(value)
+            .arg(Pitch::getPitchLabelForFrequency(value))
+            .arg(Pitch::getPitchForFrequency(value));
+    } else if (unit != "") {
+        valueText = tr("%1 %2").arg(value).arg(unit);
+    } else {
+        valueText = tr("%1").arg(value);
+    }
+    
     QString text;
-    QString unit = getScaleUnits();
-    if (unit != "") unit = " " + unit;
 
     if (points.begin()->label == "") {
-	text = QString(tr("Time:\t%1\nValue:\t%2%3\nNo label"))
+	text = QString(tr("Time:\t%1\nValue:\t%2\nNo label"))
 	    .arg(rt.toText(true).c_str())
-	    .arg(points.begin()->value)
-            .arg(unit);
+	    .arg(valueText);
     } else {
-	text = QString(tr("Time:\t%1\nValue:\t%2%3\nLabel:\t%4"))
+	text = QString(tr("Time:\t%1\nValue:\t%2\nLabel:\t%4"))
 	    .arg(rt.toText(true).c_str())
-	    .arg(points.begin()->value)
-            .arg(unit)
+	    .arg(valueText)
 	    .arg(points.begin()->label);
     }
 
@@ -629,8 +641,8 @@
 }
 
 bool
-TimeValueLayer::snapToFeatureFrame(View *v, int &frame,
-				   size_t &resolution,
+TimeValueLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				   int &resolution,
 				   SnapType snap) const
 {
     if (!m_model) {
@@ -649,7 +661,7 @@
     }    
 
     points = m_model->getPoints(frame, frame);
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
 
     for (SparseTimeValueModel::PointList::const_iterator i = points.begin();
@@ -701,8 +713,8 @@
 }
 
 bool
-TimeValueLayer::snapToSimilarFeature(View *v, int &frame,
-                                     size_t &resolution,
+TimeValueLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
+                                     int &resolution,
                                      SnapType snap) const
 {
     if (!m_model) {
@@ -716,8 +728,8 @@
 
     SparseTimeValueModel::PointList::const_iterator i;
 
-    int matchframe = frame;
-    float matchvalue = 0.f;
+    sv_frame_t matchframe = frame;
+    double matchvalue = 0.0;
 
     for (i = close.begin(); i != close.end(); ++i) {
         if (i->frame > frame) break;
@@ -725,27 +737,35 @@
         matchframe = i->frame;
     }
 
-    int snapped = frame;
+    sv_frame_t snapped = frame;
     bool found = false;
     bool distant = false;
-    float epsilon = 0.0001;
+    double epsilon = 0.0001;
 
     i = close.begin();
 
     // Scan through the close points first, then the more distant ones
-    // if no suitable close one is found
+    // if no suitable close one is found. So the while-termination
+    // condition here can only happen once i has passed through the
+    // whole of the close container and then the whole of the separate
+    // points container. The two iterators are totally distinct, but
+    // have the same type so we cheekily use the same variable and a
+    // single loop for both.
 
     while (i != points.end()) {
 
-        if (i == close.end()) {
-            i = points.begin();
-            distant = true;
+        if (!distant) {
+            if (i == close.end()) {
+                // switch from the close container to the points container
+                i = points.begin();
+                distant = true;
+            }
         }
 
 	if (snap == SnapRight) {
 
 	    if (i->frame > matchframe &&
-                fabsf(i->value - matchvalue) < epsilon) {
+                fabs(i->value - matchvalue) < epsilon) {
 		snapped = i->frame;
 		found = true;
 		break;
@@ -754,7 +774,7 @@
 	} else if (snap == SnapLeft) {
 
 	    if (i->frame < matchframe) {
-                if (fabsf(i->value - matchvalue) < epsilon) {
+                if (fabs(i->value - matchvalue) < epsilon) {
                     snapped = i->frame;
                     found = true; // don't break, as the next may be better
                 }
@@ -774,7 +794,7 @@
 }
 
 void
-TimeValueLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
+TimeValueLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
 {
     min = 0.0;
     max = 0.0;
@@ -810,11 +830,11 @@
 }
 
 int
-TimeValueLayer::getYForValue(View *v, float val) const
+TimeValueLayer::getYForValue(LayerGeometryProvider *v, double val) const
 {
-    float min = 0.0, max = 0.0;
+    double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -830,16 +850,16 @@
     return int(h - ((val - min) * h) / (max - min));
 }
 
-float
-TimeValueLayer::getValueForY(View *v, int y) const
+double
+TimeValueLayer::getValueForY(LayerGeometryProvider *v, int y) const
 {
-    float min = 0.0, max = 0.0;
+    double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
-    float val = min + (float(h - y) * float(max - min)) / h;
+    double val = min + (double(h - y) * double(max - min)) / h;
 
     if (logarithmic) {
         val = LogRange::map(val);
@@ -857,9 +877,9 @@
 }
 
 QColor
-TimeValueLayer::getColourForValue(View *v, float val) const
+TimeValueLayer::getColourForValue(LayerGeometryProvider *v, double val) const
 {
-    float min, max;
+    double min, max;
     bool log;
     getScaleExtents(v, min, max, log);
 
@@ -888,11 +908,11 @@
 }
 
 void
-TimeValueLayer::paint(View *v, QPainter &paint, QRect rect) const
+TimeValueLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
-    int sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
     paint.setRenderHint(QPainter::Antialiasing, false);
@@ -900,8 +920,8 @@
 //    Profiler profiler("TimeValueLayer::paint", true);
 
     int x0 = rect.left(), x1 = rect.right();
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1);
+    sv_frame_t frame0 = v->getFrameForX(x0);
+    sv_frame_t frame1 = v->getFrameForX(x1);
     if (m_derivative) --frame0;
 
     SparseTimeValueModel::PointList points(m_model->getPoints
@@ -919,15 +939,15 @@
 	      << m_model->getResolution() << " frames" << endl;
 #endif
 
-    float min = m_model->getValueMinimum();
-    float max = m_model->getValueMaximum();
+    double min = m_model->getValueMinimum();
+    double max = m_model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
-    int origin = int(nearbyint(v->height() -
-			       (-min * v->height()) / (max - min)));
+    int origin = int(nearbyint(v->getPaintHeight() -
+			       (-min * v->getPaintHeight()) / (max - min)));
 
     QPoint localPos;
-    long illuminateFrame = -1;
+    sv_frame_t illuminateFrame = -1;
 
     if (v->shouldIlluminateLocalFeatures(this, localPos)) {
 	SparseTimeValueModel::PointList localPoints =
@@ -958,7 +978,7 @@
         textY = v->getTextLabelHeight(this, paint);
     } else {
         int originY = getYForValue(v, 0.f);
-        if (originY > 0 && originY < v->height()) {
+        if (originY > 0 && originY < v->getPaintHeight()) {
             paint.save();
             paint.setPen(getPartialShades(v)[1]);
             paint.drawLine(x0, originY, x1, originY);
@@ -966,16 +986,16 @@
         }
     }
     
-    int prevFrame = 0;
+    sv_frame_t prevFrame = 0;
 
     for (SparseTimeValueModel::PointList::const_iterator i = points.begin();
 	 i != points.end(); ++i) {
 
         if (m_derivative && i == points.begin()) continue;
-        
+
 	const SparseTimeValueModel::Point &p(*i);
 
-        float value = p.value;
+        double value = p.value;
         if (m_derivative) {
             SparseTimeValueModel::PointList::const_iterator j = i;
             --j;
@@ -987,6 +1007,10 @@
 
         bool gap = false;
         if (m_plotStyle == PlotDiscreteCurves) { 
+            if (value == 0.0) {
+                // Treat zeros as gaps
+                continue;
+            }
             gap = (p.frame > prevFrame &&
                    (p.frame - prevFrame >= m_model->getResolution() * 2));
         }
@@ -1000,7 +1024,8 @@
         }
 
 	bool haveNext = false;
-        int nf = v->getModelsEndFrame();
+        double nvalue = 0.f;
+        sv_frame_t nf = v->getModelsEndFrame();
 	int nx = v->getXForFrame(nf);
 	int ny = y;
 
@@ -1009,7 +1034,7 @@
 
 	if (j != points.end()) {
 	    const SparseTimeValueModel::Point &q(*j);
-            float nvalue = q.value;
+            nvalue = q.value;
             if (m_derivative) nvalue -= p.value;
             nf = q.frame;
 	    nx = v->getXForFrame(nf);
@@ -1109,14 +1134,16 @@
 
 		} else {
 
-		    float x0 = x + float(w)/2;
-		    float x1 = nx + float(w)/2;
+		    double x0 = x + double(w)/2;
+		    double x1 = nx + double(w)/2;
 		    
-		    float y0 = y;
-		    float y1 = ny;
+		    double y0 = y;
+		    double y1 = ny;
 
                     if (m_plotStyle == PlotDiscreteCurves) {
-                        bool nextGap = nf - p.frame >= m_model->getResolution() * 2;
+                        bool nextGap =
+                            (nvalue == 0.0) ||
+                            (nf - p.frame >= m_model->getResolution() * 2);
                         if (nextGap) {
                             x1 = x0;
                             y1 = y0;
@@ -1136,6 +1163,7 @@
 			// path.quadTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
 
 		    } else {
+                        path.lineTo(x0, y0);
 			path.lineTo((x0 + x1) / 2, (y0 + y1) / 2);
 		    }
 		}
@@ -1155,39 +1183,45 @@
             if (!illuminate) {
                 if (!m_drawSegmentDivisions ||
                     nx < x + 5 ||
-                    x >= v->width() - 1) {
+                    x >= v->getPaintWidth() - 1) {
                     paint.setPen(Qt::NoPen);
                 }
 	    }
 
-	    paint.drawRect(x, -1, nx - x, v->height() + 1);
+	    paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1);
 	}
 
-        QString label = p.label;
-        bool italic = false;
+        if (v->shouldShowFeatureLabels()) {
 
-        if (label == "" &&
-            (m_plotStyle == PlotPoints ||
-             m_plotStyle == PlotSegmentation ||
-             m_plotStyle == PlotConnectedPoints)) {
-            char lc[20];
-            snprintf(lc, 20, "%.3g", p.value);
-            label = lc;
-            italic = true;
+            QString label = p.label;
+            bool italic = false;
+
+            if (label == "" &&
+                (m_plotStyle == PlotPoints ||
+                 m_plotStyle == PlotSegmentation ||
+                 m_plotStyle == PlotConnectedPoints)) {
+                char lc[20];
+                snprintf(lc, 20, "%.3g", p.value);
+                label = lc;
+                italic = true;
+            }
+
+            if (label != "") {
+                // Quick test for 20px before we do the slower test using metrics
+                bool haveRoom = (nx > x + 20);
+                haveRoom = (haveRoom &&
+                            (nx > x + 6 + paint.fontMetrics().width(label)));
+                if (haveRoom ||
+                    (!haveNext &&
+                     (pointCount == 0 || !italic))) {
+                    v->drawVisibleText(paint, x + 5, textY, label,
+                                       italic ?
+                                       View::OutlinedItalicText :
+                                       View::OutlinedText);
+                }
+            }
         }
 
-	if (label != "") {
-            bool haveRoom = nx > x + 6 + paint.fontMetrics().width(label);
-            if (haveRoom ||
-                (!haveNext &&
-                 (pointCount == 0 || !italic))) {
-                v->drawVisibleText(paint, x + 5, textY, label,
-                                   italic ?
-                                   View::OutlinedItalicText :
-                                   View::OutlinedText);
-            }
-	}
-
         prevFrame = p.frame;
         ++pointCount;
     }
@@ -1197,7 +1231,7 @@
 	paint.drawPath(path);
     } else if ((m_plotStyle == PlotCurve || m_plotStyle == PlotLines)
                && !path.isEmpty()) {
-	paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->width());
+	paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->getPaintWidth());
 	paint.drawPath(path);
     }
 
@@ -1208,7 +1242,7 @@
 }
 
 int
-TimeValueLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+TimeValueLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
     if (!m_model || shouldAutoAlign()) {
         return 0;
@@ -1228,16 +1262,16 @@
 }
 
 void
-TimeValueLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+TimeValueLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model) return;
+    if (!m_model || m_model->getPoints().empty()) return;
 
     QString unit;
-    float min, max;
+    double min, max;
     bool logarithmic;
 
     int w = getVerticalScaleWidth(v, false, paint);
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     if (m_plotStyle == PlotSegmentation) {
 
@@ -1280,7 +1314,7 @@
 }
 
 void
-TimeValueLayer::drawStart(View *v, QMouseEvent *e)
+TimeValueLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
@@ -1288,12 +1322,12 @@
 
     if (!m_model) return;
 
-    long frame = v->getFrameForX(e->x());
-    long resolution = m_model->getResolution();
+    sv_frame_t frame = v->getFrameForX(e->x());
+    int resolution = m_model->getResolution();
     if (frame < 0) frame = 0;
     frame = (frame / resolution) * resolution;
 
-    float value = getValueForY(v, e->y());
+    double value = getValueForY(v, e->y());
 
     bool havePoint = false;
 
@@ -1314,7 +1348,7 @@
 
     if (!havePoint) {
         m_editingPoint = SparseTimeValueModel::Point
-            (frame, value, tr("New Point"));
+            (frame, float(value), tr("New Point"));
     }
 
     m_originalPoint = m_editingPoint;
@@ -1330,7 +1364,7 @@
 }
 
 void
-TimeValueLayer::drawDrag(View *v, QMouseEvent *e)
+TimeValueLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
@@ -1338,12 +1372,12 @@
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
-    long resolution = m_model->getResolution();
+    sv_frame_t frame = v->getFrameForX(e->x());
+    int resolution = m_model->getResolution();
     if (frame < 0) frame = 0;
     frame = (frame / resolution) * resolution;
 
-    float value = getValueForY(v, e->y());
+    double value = getValueForY(v, e->y());
 
     SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
 
@@ -1387,12 +1421,12 @@
 
 //    m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = frame;
-    m_editingPoint.value = value;
+    m_editingPoint.value = float(value);
     m_editingCommand->addPoint(m_editingPoint);
 }
 
 void
-TimeValueLayer::drawEnd(View *, QMouseEvent *)
+TimeValueLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::drawEnd" << endl;
@@ -1404,7 +1438,7 @@
 }
 
 void
-TimeValueLayer::eraseStart(View *v, QMouseEvent *e)
+TimeValueLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -1422,12 +1456,12 @@
 }
 
 void
-TimeValueLayer::eraseDrag(View *v, QMouseEvent *e)
+TimeValueLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-TimeValueLayer::eraseEnd(View *v, QMouseEvent *e)
+TimeValueLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1449,7 +1483,7 @@
 }
 
 void
-TimeValueLayer::editStart(View *v, QMouseEvent *e)
+TimeValueLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
@@ -1472,7 +1506,7 @@
 }
 
 void
-TimeValueLayer::editDrag(View *v, QMouseEvent *e)
+TimeValueLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
@@ -1480,11 +1514,11 @@
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
-    float value = getValueForY(v, e->y());
+    double value = getValueForY(v, e->y());
 
     if (!m_editingCommand) {
 	m_editingCommand = new SparseTimeValueModel::EditCommand(m_model,
@@ -1493,12 +1527,12 @@
 
     m_editingCommand->deletePoint(m_editingPoint);
     m_editingPoint.frame = frame;
-    m_editingPoint.value = value;
+    m_editingPoint.value = float(value);
     m_editingCommand->addPoint(m_editingPoint);
 }
 
 void
-TimeValueLayer::editEnd(View *, QMouseEvent *)
+TimeValueLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::editEnd" << endl;
@@ -1528,7 +1562,7 @@
 }
 
 bool
-TimeValueLayer::editOpen(View *v, QMouseEvent *e)
+TimeValueLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -1567,7 +1601,7 @@
 }
 
 void
-TimeValueLayer::moveSelection(Selection s, size_t newStartFrame)
+TimeValueLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
     if (!m_model) return;
 
@@ -1613,9 +1647,9 @@
 
 	if (s.contains(i->frame)) {
 
-	    double target = i->frame;
-	    target = newSize.getStartFrame() + 
-		double(target - s.getStartFrame()) * ratio;
+	    double target = double(i->frame);
+	    target = double(newSize.getStartFrame()) +
+		target - double(s.getStartFrame()) * ratio;
 
 	    SparseTimeValueModel::Point newPoint(*i);
 	    newPoint.frame = lrint(target);
@@ -1651,7 +1685,7 @@
 }    
 
 void
-TimeValueLayer::copy(View *v, Selection s, Clipboard &to)
+TimeValueLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1669,7 +1703,7 @@
 }
 
 bool
-TimeValueLayer::paste(View *v, const Clipboard &from, int frameOffset,
+TimeValueLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */,
                       bool interactive)
 {
     if (!m_model) return false;
@@ -1681,7 +1715,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
                                   tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
                                   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
                                   QMessageBox::Yes);
@@ -1708,7 +1742,6 @@
     Labeller::ValueType generation = Labeller::ValueNone;
 
     bool haveUsableLabels = false;
-    bool haveExistingItems = !(m_model->isEmpty());
     Labeller labeller;
     labeller.setSampleRate(m_model->getSampleRate());
 
@@ -1776,7 +1809,10 @@
                 (0, tr("Choose value calculation"),
                  text, options, prevSelection, &ok);
 
-            if (!ok) return false;
+            if (!ok) {
+                delete command;
+                return false;
+            }
             int selection = 0;
             generation = Labeller::ValueNone;
 
@@ -1810,7 +1846,7 @@
         
         if (!i->haveFrame()) continue;
 
-        size_t frame = 0;
+        sv_frame_t frame = 0;
 
         if (!realign) {
             
--- a/layer/TimeValueLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/TimeValueLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -37,41 +37,41 @@
 public:
     TimeValueLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
-    virtual QString getLabelPreceding(size_t) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
+    virtual QString getLabelPreceding(sv_frame_t) const;
 
-    virtual bool snapToFeatureFrame(View *v, int &frame,
-				    size_t &resolution,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+				    int &resolution,
 				    SnapType snap) const;
-    virtual bool snapToSimilarFeature(View *v, int &frame,
-                                      size_t &resolution,
+    virtual bool snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
+                                      int &resolution,
                                       SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *v, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void moveSelection(Selection s, size_t newStartFrame);
+    virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -120,21 +120,21 @@
     void setShowDerivative(bool);
     bool getShowDerivative() const { return m_derivative; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool needsTextLabelHeight() const {
         return m_plotStyle == PlotSegmentation && m_model->hasTextLabels();
     }
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
-    virtual bool getDisplayExtents(float &min, float &max) const;
-    virtual bool setDisplayExtents(float min, float max);
+    virtual bool getDisplayExtents(double &min, double &max) const;
+    virtual bool setDisplayExtents(double min, double max);
 
     virtual int getVerticalZoomSteps(int &defaultStep) const;
     virtual int getCurrentVerticalZoomStep() const;
@@ -155,16 +155,16 @@
     }
 
     /// VerticalScaleLayer and ColourScaleLayer methods
-    virtual int getYForValue(View *, float value) const;
-    virtual float getValueForY(View *, int y) const;
+    virtual int getYForValue(LayerGeometryProvider *, double value) const;
+    virtual double getValueForY(LayerGeometryProvider *, int y) const;
     virtual QString getScaleUnits() const;
-    virtual QColor getColourForValue(View *v, float value) const;
+    virtual QColor getColourForValue(LayerGeometryProvider *v, double value) const;
 
 protected:
-    void getScaleExtents(View *, float &min, float &max, bool &log) const;
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
     bool shouldAutoAlign() const;
 
-    SparseTimeValueModel::PointList getLocalPoints(View *v, int) const;
+    SparseTimeValueModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
@@ -179,8 +179,8 @@
     bool m_drawSegmentDivisions;
     bool m_derivative;
 
-    mutable float m_scaleMinimum;
-    mutable float m_scaleMaximum;
+    mutable double m_scaleMinimum;
+    mutable double m_scaleMaximum;
 
     void finish(SparseTimeValueModel::EditCommand *command) {
         Command *c = command->finish();
--- a/layer/VerticalScaleLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/VerticalScaleLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,8 +19,8 @@
 class VerticalScaleLayer
 {
 public:
-    virtual int getYForValue(View *, float value) const = 0;
-    virtual float getValueForY(View *, int y) const = 0;
+    virtual int getYForValue(LayerGeometryProvider *, double value) const = 0;
+    virtual double getValueForY(LayerGeometryProvider *, int y) const = 0;
     virtual QString getScaleUnits() const = 0;
 };
 
--- a/layer/WaveformLayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/WaveformLayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -43,9 +43,11 @@
     m_channelMode(SeparateChannels),
     m_channel(-1),
     m_scale(LinearScale),
+    m_middleLineHeight(0.5),
     m_aggressive(false),
     m_cache(0),
-    m_cacheValid(false)
+    m_cacheValid(false),
+    m_cacheZoomLevel(0)
 {
     
 }
@@ -151,7 +153,7 @@
 	*max = 50;
         *deflt = 0;
 
-	val = lrint(log10(m_gain) * 20.0);
+	val = int(lrint(log10(m_gain) * 20.0));
 	if (val < *min) val = *min;
 	if (val > *max) val = *max;
 
@@ -220,7 +222,7 @@
 WaveformLayer::setProperty(const PropertyName &name, int value)
 {
     if (name == "Gain") {
-	setGain(pow(10, float(value)/20.0));
+	setGain(float(pow(10, float(value)/20.0)));
     } else if (name == "Normalize Visible Area") {
         setAutoNormalize(value ? true : false);
     } else if (name == "Channels") {
@@ -306,6 +308,15 @@
 }
 
 void
+WaveformLayer::setMiddleLineHeight(double height)
+{
+    if (m_middleLineHeight == height) return;
+    m_middleLineHeight = height;
+    m_cacheValid = false;
+    emit layerParametersChanged();
+}
+
+void
 WaveformLayer::setAggressiveCacheing(bool aggressive)
 {
     if (m_aggressive == aggressive) return;
@@ -315,7 +326,7 @@
 }
 
 int
-WaveformLayer::getCompletion(View *) const
+WaveformLayer::getCompletion(LayerGeometryProvider *) const
 {
     int completion = 100;
     if (!m_model || !m_model->isOK()) return completion;
@@ -324,7 +335,7 @@
 }
 
 bool
-WaveformLayer::getValueExtents(float &min, float &max,
+WaveformLayer::getValueExtents(double &min, double &max,
                                bool &, QString &unit) const
 {
     if (m_scale == LinearScale) {
@@ -342,26 +353,26 @@
 }
 
 int
-WaveformLayer::dBscale(float sample, int m) const
+WaveformLayer::dBscale(double sample, int m) const
 {
     if (sample < 0.0) return dBscale(-sample, m);
-    float dB = AudioLevel::multiplier_to_dB(sample);
+    double dB = AudioLevel::multiplier_to_dB(sample);
     if (dB < -50.0) return 0;
     if (dB > 0.0) return m;
     return int(((dB + 50.0) * m) / 50.0 + 0.1);
 }
 
-size_t
-WaveformLayer::getChannelArrangement(size_t &min, size_t &max,
+int
+WaveformLayer::getChannelArrangement(int &min, int &max,
                                      bool &merging, bool &mixing)
     const
 {
     if (!m_model || !m_model->isOK()) return 0;
 
-    size_t channels = m_model->getChannelCount();
+    int channels = m_model->getChannelCount();
     if (channels == 0) return 0;
 
-    size_t rawChannels = channels;
+    int rawChannels = channels;
 
     if (m_channel == -1) {
 	min = 0;
@@ -388,7 +399,7 @@
 }    
 
 bool
-WaveformLayer::isLayerScrollable(const View *) const
+WaveformLayer::isLayerScrollable(const LayerGeometryProvider *) const
 {
     return !m_autoNormalize;
 }
@@ -397,10 +408,10 @@
                             -5, -3, -2, -1, -0.5, 0 };
 
 bool
-WaveformLayer::getSourceFramesForX(View *v, int x, size_t modelZoomLevel,
-                                   size_t &f0, size_t &f1) const
+WaveformLayer::getSourceFramesForX(LayerGeometryProvider *v, int x, int modelZoomLevel,
+                                   sv_frame_t &f0, sv_frame_t &f1) const
 {
-    long viewFrame = v->getFrameForX(x);
+    sv_frame_t viewFrame = v->getFrameForX(x);
     if (viewFrame < 0) {
         f0 = 0;
         f1 = 0;
@@ -422,17 +433,15 @@
 }
 
 float
-WaveformLayer::getNormalizeGain(View *v, int channel) const
+WaveformLayer::getNormalizeGain(LayerGeometryProvider *v, int channel) const
 {
-    long startFrame = v->getStartFrame();
-    long endFrame = v->getEndFrame();
+    sv_frame_t startFrame = v->getStartFrame();
+    sv_frame_t endFrame = v->getEndFrame();
 
-    // Although a long for purposes of comparison against the view
-    // start and end frames, these are known to be non-negative
-    long modelStart = long(m_model->getStartFrame());
-    long modelEnd = long(m_model->getEndFrame());
+    sv_frame_t modelStart = m_model->getStartFrame();
+    sv_frame_t modelEnd = m_model->getEndFrame();
     
-    size_t rangeStart, rangeEnd;
+    sv_frame_t rangeStart, rangeEnd;
             
     if (startFrame < modelStart) rangeStart = modelStart;
     else rangeStart = startFrame;
@@ -446,11 +455,11 @@
     RangeSummarisableTimeValueModel::Range range =
         m_model->getSummary(channel, rangeStart, rangeEnd - rangeStart);
 
-    size_t minChannel = 0, maxChannel = 0;
+    int minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
 
-    getChannelArrangement(minChannel, maxChannel,
-                          mergingChannels, mixingChannels);
+    (void)getChannelArrangement(minChannel, maxChannel,
+                                mergingChannels, mixingChannels);
 
     if (mergingChannels || mixingChannels) {
         RangeSummarisableTimeValueModel::Range otherRange =
@@ -460,11 +469,11 @@
         range.setAbsmean(std::min(range.absmean(), otherRange.absmean()));
     }
 
-    return 1.0 / std::max(fabsf(range.max()), fabsf(range.min()));
+    return float(1.0 / std::max(fabs(range.max()), fabs(range.min())));
 }
 
 void
-WaveformLayer::paint(View *v, QPainter &viewPainter, QRect rect) const
+WaveformLayer::paint(LayerGeometryProvider *v, QPainter &viewPainter, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) {
 	return;
@@ -478,15 +487,15 @@
 	      << ") [" << rect.width() << "x" << rect.height() << "]: zoom " << zoomLevel << endl;
 #endif
 
-    size_t channels = 0, minChannel = 0, maxChannel = 0;
+    int channels = 0, minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
 
     channels = getChannelArrangement(minChannel, maxChannel,
                                      mergingChannels, mixingChannels);
     if (channels == 0) return;
 
-    int w = v->width();
-    int h = v->height();
+    int w = v->getPaintWidth();
+    int h = v->getPaintHeight();
 
     bool ready = m_model->isReady();
     QPainter *paint;
@@ -532,6 +541,15 @@
 
     paint->setRenderHint(QPainter::Antialiasing, false);
 
+    if (m_middleLineHeight != 0.5) {
+        paint->save();
+        double space = m_middleLineHeight * 2;
+        if (space > 1.0) space = 2.0 - space;
+        double yt = h * (m_middleLineHeight - space/2);
+        paint->translate(QPointF(0, yt));
+        paint->scale(1.0, space);
+    }
+
     int x0 = 0, x1 = w - 1;
     int y0 = 0, y1 = h - 1;
 
@@ -541,7 +559,7 @@
     y1 = rect.bottom();
 
     if (x0 > 0) --x0;
-    if (x1 < v->width()) ++x1;
+    if (x1 < w) ++x1;
 
     // Our zoom level may differ from that at which the underlying
     // model has its blocks.
@@ -552,11 +570,11 @@
     // must remain the same when we scroll one or more pixels left or
     // right.
             
-    size_t modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
+    int modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
 
-    size_t frame0;
-    size_t frame1;
-    size_t spare;
+    sv_frame_t frame0;
+    sv_frame_t frame1;
+    sv_frame_t spare;
 
     getSourceFramesForX(v, x0, modelZoomLevel, frame0, spare);
     getSourceFramesForX(v, x1, modelZoomLevel, spare, frame1);
@@ -583,11 +601,11 @@
 	midColour = midColour.light(50);
     }
 
-    while (m_effectiveGains.size() <= maxChannel) {
+    while ((int)m_effectiveGains.size() <= maxChannel) {
         m_effectiveGains.push_back(m_gain);
     }
 
-    for (size_t ch = minChannel; ch <= maxChannel; ++ch) {
+    for (int ch = minChannel; ch <= maxChannel; ++ch) {
 
 	int prevRangeBottom = -1, prevRangeTop = -1;
 	QColor prevRangeBottomColour = baseColour, prevRangeTopColour = baseColour;
@@ -598,7 +616,7 @@
             m_effectiveGains[ch] = getNormalizeGain(v, ch);
         }
 
-        float gain = m_effectiveGains[ch];
+        double gain = m_effectiveGains[ch];
 
 	int m = (h / channels) / 2;
 	int my = m + (((ch - minChannel) * h) / channels);
@@ -629,7 +647,7 @@
 
             for (int i = 1; i < n; ++i) {
                 
-                float val = 0.0, nval = 0.0;
+                double val = 0.0, nval = 0.0;
 
                 switch (m_scale) {
 
@@ -692,7 +710,7 @@
 
 	    range = RangeSummarisableTimeValueModel::Range();
 
-            size_t f0, f1;
+            sv_frame_t f0, f1;
             if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) continue;
             f1 = f1 - 1;
 
@@ -701,8 +719,8 @@
                 continue;
             }
 
-            size_t i0 = (f0 - frame0) / modelZoomLevel;
-            size_t i1 = (f1 - frame0) / modelZoomLevel;
+            sv_frame_t i0 = (f0 - frame0) / modelZoomLevel;
+            sv_frame_t i1 = (f1 - frame0) / modelZoomLevel;
 
 #ifdef DEBUG_WAVEFORM_PAINT
             cerr << "WaveformLayer::paint: pixel " << x << ": i0 " << i0 << " (f " << f0 << "), i1 " << i1 << " (f " << f1 << ")" << endl;
@@ -712,14 +730,17 @@
                 cerr << "WaveformLayer::paint: ERROR: i1 " << i1 << " > i0 " << i0 << " plus one (zoom = " << zoomLevel << ", model zoom = " << modelZoomLevel << ")" << endl;
             }
 
-	    if (ranges && i0 < ranges->size()) {
+	    if (ranges && i0 < (sv_frame_t)ranges->size()) {
 
-		range = (*ranges)[i0];
+		range = (*ranges)[size_t(i0)];
 
-		if (i1 > i0 && i1 < ranges->size()) {
-		    range.setMax(std::max(range.max(), (*ranges)[i1].max()));
-		    range.setMin(std::min(range.min(), (*ranges)[i1].min()));
-		    range.setAbsmean((range.absmean() + (*ranges)[i1].absmean()) / 2);
+		if (i1 > i0 && i1 < (int)ranges->size()) {
+		    range.setMax(std::max(range.max(),
+                                          (*ranges)[size_t(i1)].max()));
+		    range.setMin(std::min(range.min(),
+                                          (*ranges)[size_t(i1)].min()));
+		    range.setAbsmean((range.absmean()
+                                      + (*ranges)[size_t(i1)].absmean()) / 2);
 		}
 
 	    } else {
@@ -733,30 +754,33 @@
 
 	    if (mergingChannels) {
 
-		if (otherChannelRanges && i0 < otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < (sv_frame_t)otherChannelRanges->size()) {
 
 		    range.setMax(fabsf(range.max()));
-		    range.setMin(-fabsf((*otherChannelRanges)[i0].max()));
+		    range.setMin(-fabsf((*otherChannelRanges)[size_t(i0)].max()));
 		    range.setAbsmean
                         ((range.absmean() +
-                          (*otherChannelRanges)[i0].absmean()) / 2);
+                          (*otherChannelRanges)[size_t(i0)].absmean()) / 2);
 
-		    if (i1 > i0 && i1 < otherChannelRanges->size()) {
+		    if (i1 > i0 && i1 < (sv_frame_t)otherChannelRanges->size()) {
 			// let's not concern ourselves about the mean
 			range.setMin
                             (std::min
                              (range.min(),
-                              -fabsf((*otherChannelRanges)[i1].max())));
+                              -fabsf((*otherChannelRanges)[size_t(i1)].max())));
 		    }
 		}
 
 	    } else if (mixingChannels) {
 
-		if (otherChannelRanges && i0 < otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < (sv_frame_t)otherChannelRanges->size()) {
 
-                    range.setMax((range.max() + (*otherChannelRanges)[i0].max()) / 2);
-                    range.setMin((range.min() + (*otherChannelRanges)[i0].min()) / 2);
-                    range.setAbsmean((range.absmean() + (*otherChannelRanges)[i0].absmean()) / 2);
+                    range.setMax((range.max()
+                                  + (*otherChannelRanges)[size_t(i0)].max()) / 2);
+                    range.setMin((range.min()
+                                  + (*otherChannelRanges)[size_t(i0)].min()) / 2);
+                    range.setAbsmean((range.absmean()
+                                      + (*otherChannelRanges)[size_t(i0)].absmean()) / 2);
                 }
             }
 
@@ -766,10 +790,10 @@
 	    switch (m_scale) {
 
 	    case LinearScale:
-		rangeBottom = int( m * greyLevels * range.min() * gain);
-		rangeTop    = int( m * greyLevels * range.max() * gain);
-		meanBottom  = int(-m * range.absmean() * gain);
-		meanTop     = int( m * range.absmean() * gain);
+		rangeBottom = int(double(m * greyLevels) * range.min() * gain);
+		rangeTop    = int(double(m * greyLevels) * range.max() * gain);
+		meanBottom  = int(double(-m) * range.absmean() * gain);
+		meanTop     = int(double(m) * range.absmean() * gain);
 		break;
 
 	    case dBScale:
@@ -915,9 +939,13 @@
 	}
     }
 
+    if (m_middleLineHeight != 0.5) {
+        paint->restore();
+    }
+
     if (m_aggressive) {
 
-	if (ready && rect == v->rect()) {
+	if (ready && rect == v->getPaintRect()) {
 	    m_cacheValid = true;
 	    m_cacheZoomLevel = zoomLevel;
 	}
@@ -931,7 +959,7 @@
 }
 
 QString
-WaveformLayer::getFeatureDescription(View *v, QPoint &pos) const
+WaveformLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -939,9 +967,9 @@
 
     int zoomLevel = v->getZoomLevel();
 
-    size_t modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
+    int modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
 
-    size_t f0, f1;
+    sv_frame_t f0, f1;
     if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) return "";
     
     QString text;
@@ -958,16 +986,16 @@
 	    .arg(rt0.toText(true).c_str());
     }
 
-    size_t channels = 0, minChannel = 0, maxChannel = 0;
+    int channels = 0, minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
 
     channels = getChannelArrangement(minChannel, maxChannel,
                                      mergingChannels, mixingChannels);
     if (channels == 0) return "";
 
-    for (size_t ch = minChannel; ch <= maxChannel; ++ch) {
+    for (int ch = minChannel; ch <= maxChannel; ++ch) {
 
-	size_t blockSize = v->getZoomLevel();
+	int blockSize = v->getZoomLevel();
 	RangeSummarisableTimeValueModel::RangeBlock ranges;
         m_model->getSummaries(ch, f0, f1 - f0, ranges, blockSize);
 
@@ -983,18 +1011,18 @@
 	}
 
         bool singleValue = false;
-        float min, max;
+        double min, max;
 
         if (fabs(range.min()) < 0.01) {
             min = range.min();
             max = range.max();
             singleValue = (min == max);
         } else {
-            int imin = lrint(range.min() * 10000);
-            int imax = lrint(range.max() * 10000);
+            int imin = int(lrint(range.min() * 10000));
+            int imax = int(lrint(range.max() * 10000));
             singleValue = (imin == imax);
-            min = float(imin)/10000;
-            max = float(imax)/10000;
+            min = double(imin)/10000;
+            max = double(imax)/10000;
         }
 
 	int db = int(AudioLevel::multiplier_to_dB(std::max(fabsf(range.min()),
@@ -1003,10 +1031,10 @@
 
 	if (!singleValue) {
 	    text += tr("\n%1\t%2 - %3 (%4 dB peak)")
-		.arg(label).arg(min).arg(max).arg(float(db)/100);
+		.arg(label).arg(min).arg(max).arg(double(db)/100);
 	} else {
 	    text += tr("\n%1\t%2 (%3 dB peak)")
-		.arg(label).arg(min).arg(float(db)/100);
+		.arg(label).arg(min).arg(double(db)/100);
 	}
     }
 
@@ -1014,17 +1042,17 @@
 }
 
 int
-WaveformLayer::getYForValue(const View *v, float value, size_t channel) const
+WaveformLayer::getYForValue(const LayerGeometryProvider *v, double value, int channel) const
 {
-    size_t channels = 0, minChannel = 0, maxChannel = 0;
+    int channels = 0, minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
 
     channels = getChannelArrangement(minChannel, maxChannel,
                                      mergingChannels, mixingChannels);
-
+    if (channels == 0) return 0;
     if (maxChannel < minChannel || channel < minChannel) return 0;
 
-    int h = v->height();
+    int h = v->getPaintHeight();
     int m = (h / channels) / 2;
 	
     if ((m_scale == dBScale || m_scale == MeterScale) &&
@@ -1056,18 +1084,18 @@
     return my - vy;
 }
 
-float
-WaveformLayer::getValueForY(const View *v, int y, size_t &channel) const
+double
+WaveformLayer::getValueForY(const LayerGeometryProvider *v, int y, int &channel) const
 {
-    size_t channels = 0, minChannel = 0, maxChannel = 0;
+    int channels = 0, minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
 
     channels = getChannelArrangement(minChannel, maxChannel,
                                      mergingChannels, mixingChannels);
-
+    if (channels == 0) return 0;
     if (maxChannel < minChannel) return 0;
 
-    int h = v->height();
+    int h = v->getPaintHeight();
     int m = (h / channels) / 2;
 
     if ((m_scale == dBScale || m_scale == MeterScale) &&
@@ -1080,13 +1108,13 @@
     int my = m + (((channel - minChannel) * h) / channels);
 
     int vy = my - y;
-    float value = 0;
-    float thresh = -50.f;
+    double value = 0;
+    double thresh = -50.f;
 
     switch (m_scale) {
 
     case LinearScale:
-        value = float(vy) / m;
+        value = double(vy) / m;
         break;
 
     case MeterScale:
@@ -1094,7 +1122,7 @@
         break;
 
     case dBScale:
-        value = (-thresh * float(vy)) / m + thresh;
+        value = (-thresh * double(vy)) / m + thresh;
         value = AudioLevel::dB_to_multiplier(value);
         break;
     }
@@ -1103,19 +1131,19 @@
 }
 
 bool
-WaveformLayer::getYScaleValue(const View *v, int y,
-                              float &value, QString &unit) const
+WaveformLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
+                              double &value, QString &unit) const
 {
-    size_t channel;
+    int channel;
 
     value = getValueForY(v, y, channel);
 
     if (m_scale == dBScale || m_scale == MeterScale) {
 
-        float thresh = -50.f;
+        double thresh = -50.f;
         
-        if (value > 0.f) {
-            value = 10.f * log10f(value);
+        if (value > 0.0) {
+            value = 10.0 * log10(value);
             if (value < thresh) value = thresh;
         } else value = thresh;
 
@@ -1129,37 +1157,37 @@
 }
 
 bool
-WaveformLayer::getYScaleDifference(const View *v, int y0, int y1,
-                                   float &diff, QString &unit) const
+WaveformLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
+                                   double &diff, QString &unit) const
 {
-    size_t c0, c1;
-    float v0 = getValueForY(v, y0, c0);
-    float v1 = getValueForY(v, y1, c1);
+    int c0, c1;
+    double v0 = getValueForY(v, y0, c0);
+    double v1 = getValueForY(v, y1, c1);
 
     if (c0 != c1) {
         // different channels, not comparable
-        diff = 0.f;
+        diff = 0.0;
         unit = "";
         return false;
     }
 
     if (m_scale == dBScale || m_scale == MeterScale) {
 
-        float thresh = -50.f;
+        double thresh = -50.0;
 
         if (v1 == v0) diff = thresh;
         else {
             if (v1 > v0) diff = v0 / v1;
             else diff = v1 / v0;
 
-            diff = 10.f * log10f(diff);
+            diff = 10.0 * log10(diff);
             if (diff < thresh) diff = thresh;
         }
 
         unit = "dBV";
 
     } else {
-        diff = fabsf(v1 - v0);
+        diff = fabs(v1 - v0);
         unit = "V";
     }
 
@@ -1167,7 +1195,7 @@
 }
 
 int
-WaveformLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const
+WaveformLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
     if (m_scale == LinearScale) {
 	return paint.fontMetrics().width("0.0") + 13;
@@ -1178,13 +1206,13 @@
 }
 
 void
-WaveformLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const
+WaveformLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) {
 	return;
     }
 
-    size_t channels = 0, minChannel = 0, maxChannel = 0;
+    int channels = 0, minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
 
     channels = getChannelArrangement(minChannel, maxChannel,
@@ -1195,26 +1223,26 @@
     int textHeight = paint.fontMetrics().height();
     int toff = -textHeight/2 + paint.fontMetrics().ascent() + 1;
 
-    float gain = m_gain;
+    double gain = m_gain;
 
-    for (size_t ch = minChannel; ch <= maxChannel; ++ch) {
+    for (int ch = minChannel; ch <= maxChannel; ++ch) {
 
 	int lastLabelledY = -1;
 
-        if (ch < m_effectiveGains.size()) gain = m_effectiveGains[ch];
+        if (ch < (int)m_effectiveGains.size()) gain = m_effectiveGains[ch];
 
         int n = 10;
 
 	for (int i = 0; i <= n; ++i) {
 
-            float val = 0.0, nval = 0.0;
+            double val = 0.0, nval = 0.0;
 	    QString text = "";
 
             switch (m_scale) {
                 
             case LinearScale:
                 val = (i * gain) / n;
-		text = QString("%1").arg(float(i) / n);
+		text = QString("%1").arg(double(i) / n);
 		if (i == 0) text = "0.0";
                 else {
                     nval = -val;
@@ -1314,14 +1342,16 @@
 		 "channelMode=\"%4\" "
 		 "channel=\"%5\" "
                  "scale=\"%6\" "
-		 "aggressive=\"%7\" "
-                 "autoNormalize=\"%8\"")
+                 "middleLineHeight=\"%7\" "
+		 "aggressive=\"%8\" "
+                 "autoNormalize=\"%9\"")
 	.arg(m_gain)
 	.arg(m_showMeans)
 	.arg(m_greyscale)
 	.arg(m_channelMode)
 	.arg(m_channel)
 	.arg(m_scale)
+        .arg(m_middleLineHeight)
 	.arg(m_aggressive)
         .arg(m_autoNormalize);
 
@@ -1353,10 +1383,12 @@
     int channel = attributes.value("channel").toInt(&ok);
     if (ok) setChannel(channel);
 
-    Scale scale = (Scale)
-	attributes.value("scale").toInt(&ok);
+    Scale scale = (Scale)attributes.value("scale").toInt(&ok);
     if (ok) setScale(scale);
 
+    float middleLineHeight = attributes.value("middleLineHeight").toFloat(&ok);
+    if (ok) setMiddleLineHeight(middleLineHeight);
+
     bool aggressive = (attributes.value("aggressive") == "1" ||
 		       attributes.value("aggressive") == "true");
     setUseGreyscale(aggressive);
@@ -1376,7 +1408,7 @@
 int
 WaveformLayer::getCurrentVerticalZoomStep() const
 {
-    int val = lrint(log10(m_gain) * 20.0) + 50;
+    int val = int(lrint(log10(m_gain) * 20.0) + 50);
     if (val < 0) val = 0;
     if (val > 100) val = 100;
     return val;
@@ -1385,6 +1417,6 @@
 void
 WaveformLayer::setVerticalZoomStep(int step)
 {
-    setGain(pow(10, float(step - 50) / 20.0));
+    setGain(powf(10, float(step - 50) / 20.f));
 }
 
--- a/layer/WaveformLayer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/layer/WaveformLayer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -38,16 +38,16 @@
         return m_model ? m_model->getZoomConstraint() : 0;
     }
     virtual const Model *getModel() const { return m_model; }
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourAndBackgroundSignificant;
     }
 
-    virtual int getVerticalScaleWidth(View *v, bool detailed, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const;
 
     void setModel(const RangeSummarisableTimeValueModel *model);
 
@@ -147,6 +147,19 @@
     Scale getScale() const { return m_scale; }
 
     /**
+     * Specify the height of the middle of the waveform track or
+     * tracks within the layer, from 0.0 to 1.0.
+     *
+     * A value of 0.0 would indicate that the waveform occupies
+     * effectively no space at the very top of the layer; 1.0 would
+     * indicate that the waveform occupies no space at the very
+     * bottom; the default value of 0.5 indicates that it occupies the
+     * whole layer, centred at the middle.
+     */
+    void setMiddleLineHeight(double);
+    double getMiddleLineHeight() const { return m_middleLineHeight; }
+
+    /**
      * Enable or disable aggressive pixmap cacheing.  If enabled,
      * waveforms will be rendered to an off-screen pixmap and
      * refreshed from there instead of being redrawn from the peak
@@ -167,18 +180,18 @@
     void setAggressiveCacheing(bool);
     bool getAggressiveCacheing() const { return m_aggressive; }
 
-    virtual bool isLayerScrollable(const View *) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const;
 
-    virtual int getCompletion(View *) const;
+    virtual int getCompletion(LayerGeometryProvider *) const;
 
-    virtual bool getValueExtents(float &min, float &max,
+    virtual bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const;
 
-    virtual bool getYScaleValue(const View *v, int y,
-                                float &value, QString &unit) const;
+    virtual bool getYScaleValue(const LayerGeometryProvider *v, int y,
+                                double &value, QString &unit) const;
     
-    virtual bool getYScaleDifference(const View *v, int y0, int y1,
-                                     float &diff, QString &unit) const;
+    virtual bool getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
+                                     double &diff, QString &unit) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
@@ -189,23 +202,25 @@
     virtual int getCurrentVerticalZoomStep() const;
     virtual void setVerticalZoomStep(int);
 
+    virtual bool canExistWithoutModel() const { return true; }
+
 protected:
-    int dBscale(float sample, int m) const;
+    int dBscale(double sample, int m) const;
 
     const RangeSummarisableTimeValueModel *m_model; // I do not own this
 
     /// Return value is number of channels displayed
-    size_t getChannelArrangement(size_t &min, size_t &max,
+    int getChannelArrangement(int &min, int &max,
                                  bool &merging, bool &mixing) const;
 
-    int getYForValue(const View *v, float value, size_t channel) const;
+    int getYForValue(const LayerGeometryProvider *v, double value, int channel) const;
 
-    float getValueForY(const View *v, int y, size_t &channel) const;
+    double getValueForY(const LayerGeometryProvider *v, int y, int &channel) const;
 
-    bool getSourceFramesForX(View *v, int x, size_t modelZoomLevel,
-                             size_t &f0, size_t &f1) const;
+    bool getSourceFramesForX(LayerGeometryProvider *v, int x, int modelZoomLevel,
+                             sv_frame_t &f0, sv_frame_t &f1) const;
 
-    float getNormalizeGain(View *v, int channel) const;
+    float getNormalizeGain(LayerGeometryProvider *v, int channel) const;
 
     virtual void flagBaseColourChanged() { m_cacheValid = false; }
 
@@ -216,6 +231,7 @@
     ChannelMode  m_channelMode;
     int          m_channel;
     Scale        m_scale;
+    double       m_middleLineHeight;
     bool         m_aggressive;
 
     mutable std::vector<float> m_effectiveGains;
--- a/layer/layer.pro	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-TEMPLATE = lib
-
-SV_UNIT_PACKAGES = fftw3f
-load(../prf/sv.prf)
-
-CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
-QT += xml
-
-TARGET = svlayer
-
-DEPENDPATH += . ..
-INCLUDEPATH += . ..
-OBJECTS_DIR = tmp_obj
-MOC_DIR = tmp_moc
-
-# Input
-HEADERS += Colour3DPlotLayer.h \
-	   ColourDatabase.h \
-	   ColourMapper.h \
-           ImageLayer.h \
-           ImageRegionFinder.h \
-           Layer.h \
-           LayerFactory.h \
-           NoteLayer.h \
-           PaintAssistant.h \
-           RegionLayer.h \
-           SingleColourLayer.h \
-           SliceableLayer.h \
-           SliceLayer.h \
-           SpectrogramLayer.h \
-           SpectrumLayer.h \
-           TextLayer.h \
-           TimeInstantLayer.h \
-           TimeRulerLayer.h \
-           TimeValueLayer.h \
-           WaveformLayer.h
-SOURCES += Colour3DPlotLayer.cpp \
-	   ColourDatabase.cpp \
-	   ColourMapper.cpp \
-           ImageLayer.cpp \
-           ImageRegionFinder.cpp \
-           Layer.cpp \
-           LayerFactory.cpp \
-           NoteLayer.cpp \
-           PaintAssistant.cpp \
-           RegionLayer.cpp \
-           SingleColourLayer.cpp \
-           SliceLayer.cpp \
-           SpectrogramLayer.cpp \
-           SpectrumLayer.cpp \
-           TextLayer.cpp \
-           TimeInstantLayer.cpp \
-           TimeRulerLayer.cpp \
-           TimeValueLayer.cpp \
-           WaveformLayer.cpp
--- a/svgui.pro	Tue Jul 14 15:04:46 2015 +0100
+++ b/svgui.pro	Wed Apr 20 12:06:28 2016 +0100
@@ -1,17 +1,35 @@
 
 TEMPLATE = lib
 
+INCLUDEPATH += ../vamp-plugin-sdk
+DEFINES += HAVE_VAMP HAVE_VAMPHOSTSDK
+
 exists(config.pri) {
     include(config.pri)
 }
-win* {
-    !exists(config.pri) {
-        DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG
+!exists(config.pri) {
+
+    CONFIG += release
+    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
+
+    win32-g++ {
+        INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
+        LIBS += -L../sv-dependency-builds/win32-mingw/lib
     }
+    win32-msvc* {
+        INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include
+        LIBS += -L../sv-dependency-builds/win32-msvc/lib
+    }
+    macx* {
+        INCLUDEPATH += ../sv-dependency-builds/osx/include
+        LIBS += -L../sv-dependency-builds/osx/lib
+    }
+
+    DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_RUBBERBAND HAVE_LIBLO HAVE_MAD HAVE_ID3TAG
 }
 
-CONFIG += staticlib qt thread warn_on stl rtti exceptions
-QT += network xml gui widgets
+CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11
+QT += network xml gui widgets svg
 
 TARGET = svgui
 
@@ -20,17 +38,11 @@
 OBJECTS_DIR = o
 MOC_DIR = o
 
-win32-g++ {
-    INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
-}
-win32-msvc* {
-    INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include
-}
-
 HEADERS += layer/Colour3DPlotLayer.h \
 	   layer/ColourDatabase.h \
 	   layer/ColourMapper.h \
            layer/ColourScaleLayer.h \
+           layer/FlexiNoteLayer.h \
            layer/ImageLayer.h \
            layer/ImageRegionFinder.h \
            layer/Layer.h \
@@ -43,6 +55,7 @@
            layer/PaintAssistant.h \
            layer/PianoScale.h \
            layer/RegionLayer.h \
+           layer/ScrollableImageCache.h \
            layer/SingleColourLayer.h \
            layer/SliceableLayer.h \
            layer/SliceLayer.h \
@@ -57,6 +70,7 @@
 SOURCES += layer/Colour3DPlotLayer.cpp \
 	   layer/ColourDatabase.cpp \
 	   layer/ColourMapper.cpp \
+           layer/FlexiNoteLayer.cpp \
            layer/ImageLayer.cpp \
            layer/ImageRegionFinder.cpp \
            layer/Layer.cpp \
@@ -69,6 +83,7 @@
            layer/PaintAssistant.cpp \
            layer/PianoScale.cpp \
            layer/RegionLayer.cpp \
+           layer/ScrollableImageCache.cpp \
            layer/SingleColourLayer.cpp \
            layer/SliceLayer.cpp \
            layer/SpectrogramLayer.cpp \
@@ -83,7 +98,8 @@
            view/Pane.h \
            view/PaneStack.h \
            view/View.h \
-           view/ViewManager.h
+           view/ViewManager.h \
+           view/ViewProxy.h
 SOURCES += view/Overview.cpp \
            view/Pane.cpp \
            view/PaneStack.cpp \
@@ -106,6 +122,8 @@
            widgets/LayerTree.h \
            widgets/LayerTreeDialog.h \
            widgets/LEDButton.h \
+           widgets/LevelPanToolButton.h \
+           widgets/LevelPanWidget.h \
            widgets/ListInputDialog.h \
            widgets/MIDIFileImportDialog.h \
            widgets/ModelDataTableDialog.h \
@@ -126,6 +144,7 @@
            widgets/Thumbwheel.h \
            widgets/TipDialog.h \
            widgets/TransformFinder.h \
+           widgets/UnitConverter.h \
            widgets/WindowShapePreview.h \
            widgets/WindowTypeSelector.h
 SOURCES += widgets/ActivityLog.cpp \
@@ -143,6 +162,8 @@
            widgets/LayerTree.cpp \
            widgets/LayerTreeDialog.cpp \
            widgets/LEDButton.cpp \
+           widgets/LevelPanToolButton.cpp \
+           widgets/LevelPanWidget.cpp \
            widgets/ListInputDialog.cpp \
            widgets/MIDIFileImportDialog.cpp \
            widgets/ModelDataTableDialog.cpp \
@@ -163,5 +184,6 @@
            widgets/Thumbwheel.cpp \
            widgets/TipDialog.cpp \
            widgets/TransformFinder.cpp \
+           widgets/UnitConverter.cpp \
            widgets/WindowShapePreview.cpp \
            widgets/WindowTypeSelector.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/LayerGeometryProvider.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,174 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef LAYER_GEOMETRY_PROVIDER_H
+#define LAYER_GEOMETRY_PROVIDER_H
+
+#include "base/BaseTypes.h"
+
+#include <QMutex>
+#include <QMutexLocker>
+#include <QPainter>
+
+class ViewManager;
+class View;
+class Layer;
+
+class LayerGeometryProvider
+{
+protected:
+    static int getNextId() {
+        static QMutex idMutex;
+        static int nextId = 1;
+        static int maxId = INT_MAX;
+        QMutexLocker locker(&idMutex);
+        int id = nextId;
+        if (nextId == maxId) {
+            // we don't expect this to happen in the lifetime of a
+            // process, but it would be undefined behaviour if it did
+            // since we're using a signed int, so we should really
+            // guard for it...
+            nextId = 1;
+        } else {
+            nextId++;
+        }
+        return id;
+    }            
+    
+public:
+    LayerGeometryProvider() { }
+    
+    /**
+     * Retrieve the id of this object.
+     */
+    virtual int getId() const = 0;
+
+    /**
+     * Retrieve the first visible sample frame on the widget.
+     * This is a calculated value based on the centre-frame, widget
+     * width and zoom level.  The result may be negative.
+     */
+    virtual sv_frame_t getStartFrame() const = 0;
+
+    /**
+     * Return the centre frame of the visible widget.  This is an
+     * exact value that does not depend on the zoom block size.  Other
+     * frame values (start, end) are calculated from this based on the
+     * zoom and other factors.
+     */
+    virtual sv_frame_t getCentreFrame() const = 0;
+
+    /**
+     * Retrieve the last visible sample frame on the widget.
+     * This is a calculated value based on the centre-frame, widget
+     * width and zoom level.
+     */
+    virtual sv_frame_t getEndFrame() const = 0;
+
+    /**
+     * Return the pixel x-coordinate corresponding to a given sample
+     * frame (which may be negative).
+     */
+    virtual int getXForFrame(sv_frame_t frame) const = 0;
+
+    /**
+     * Return the closest frame to the given pixel x-coordinate.
+     */
+    virtual sv_frame_t getFrameForX(int x) const = 0;
+
+    virtual sv_frame_t getModelsStartFrame() const = 0;
+    virtual sv_frame_t getModelsEndFrame() const = 0;
+
+    /**
+     * Return the closest pixel x-coordinate corresponding to a given
+     * view x-coordinate.
+     */
+    virtual int getXForViewX(int viewx) const = 0;
+    
+    /**
+     * Return the closest view x-coordinate corresponding to a given
+     * pixel x-coordinate.
+     */
+    virtual int getViewXForX(int x) const = 0;
+    
+    /**
+     * Return the pixel y-coordinate corresponding to a given
+     * frequency, if the frequency range is as specified.  This does
+     * not imply any policy about layer frequency ranges, but it might
+     * be useful for layers to match theirs up if desired.
+     *
+     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
+     */
+    virtual double getYForFrequency(double frequency, double minFreq, double maxFreq, 
+                                    bool logarithmic) const = 0;
+
+    /**
+     * Return the closest frequency to the given pixel y-coordinate,
+     * if the frequency range is as specified.
+     *
+     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
+     */
+    virtual double getFrequencyForY(int y, double minFreq, double maxFreq,
+			   bool logarithmic) const = 0;
+
+    virtual int getTextLabelHeight(const Layer *layer, QPainter &) const = 0;
+
+    virtual bool getValueExtents(QString unit, double &min, double &max,
+                                 bool &log) const = 0;
+
+    /**
+     * Return the zoom level, i.e. the number of frames per pixel
+     */
+    virtual int getZoomLevel() const = 0;
+
+    /**
+     * To be called from a layer, to obtain the extent of the surface
+     * that the layer is currently painting to. This may be the extent
+     * of the view (if 1x display scaling is in effect) or of a larger
+     * cached pixmap (if greater display scaling is in effect).
+     */
+    virtual QRect getPaintRect() const = 0;
+
+    virtual QSize getPaintSize() const { return getPaintRect().size(); }
+    virtual int getPaintWidth() const { return getPaintRect().width(); }
+    virtual int getPaintHeight() const { return getPaintRect().height(); }
+
+    virtual bool hasLightBackground() const = 0;
+    virtual QColor getForeground() const = 0;
+    virtual QColor getBackground() const = 0;
+
+    virtual ViewManager *getViewManager() const = 0;
+
+    virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const = 0;
+    virtual bool shouldShowFeatureLabels() const = 0;
+
+    enum TextStyle {
+	BoxedText,
+	OutlinedText,
+        OutlinedItalicText
+    };
+
+    virtual void drawVisibleText(QPainter &p, int x, int y,
+				 QString text, TextStyle style) const = 0;
+
+    virtual void drawMeasurementRect(QPainter &p, const Layer *,
+                                     QRect rect, bool focus) const = 0;
+
+    virtual void updatePaintRect(QRect r) = 0;
+    
+    virtual View *getView() = 0;
+    virtual const View *getView() const = 0;
+};
+
+#endif
--- a/view/Overview.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/Overview.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -25,26 +25,29 @@
 //#define DEBUG_OVERVIEW 1
 
 
-
-
 Overview::Overview(QWidget *w) :
     View(w, false),
-    m_clickedInRange(false)
+    m_clickedInRange(false),
+    m_dragCentreFrame(0)
 {
     setObjectName(tr("Overview"));
     m_followPan = false;
     m_followZoom = false;
     setPlaybackFollow(PlaybackIgnore);
     m_modelTestTime.start();
+
+    bool light = hasLightBackground();
+    if (light) m_boxColour = Qt::darkGray;
+    else m_boxColour = Qt::lightGray;
 }
 
 void
-Overview::modelChanged(size_t startFrame, size_t endFrame)
+Overview::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
 {
     bool zoomChanged = false;
 
-    size_t frameCount = getModelsEndFrame() - getModelsStartFrame();
-    int zoomLevel = frameCount / width();
+    sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame();
+    int zoomLevel = int(frameCount / width());
     if (zoomLevel < 1) zoomLevel = 1;
     zoomLevel = getZoomConstraintBlockSize(zoomLevel,
 					   ZoomConstraint::RoundUp);
@@ -54,8 +57,8 @@
 
     if (!zoomChanged) {
         if (m_modelTestTime.elapsed() < 1000) {
-            for (LayerList::const_iterator i = m_layers.begin();
-                 i != m_layers.end(); ++i) {
+            for (LayerList::const_iterator i = m_layerStack.begin();
+                 i != m_layerStack.end(); ++i) {
                 if ((*i)->getModel() &&
                     (!(*i)->getModel()->isOK() ||
                      !(*i)->getModel()->isReady())) {
@@ -67,7 +70,7 @@
         }
     }
 
-    View::modelChanged(startFrame, endFrame);
+    View::modelChangedWithin(startFrame, endFrame);
 }
 
 void
@@ -92,7 +95,11 @@
 }
 
 void
-Overview::globalCentreFrameChanged(unsigned long f)
+Overview::globalCentreFrameChanged(sv_frame_t 
+#ifdef DEBUG_OVERVIEW
+                                   f
+#endif
+    )
 {
 #ifdef DEBUG_OVERVIEW
     cerr << "Overview::globalCentreFrameChanged: " << f << endl;
@@ -101,7 +108,11 @@
 }
 
 void
-Overview::viewCentreFrameChanged(View *v, unsigned long f)
+Overview::viewCentreFrameChanged(View *v, sv_frame_t
+#ifdef DEBUG_OVERVIEW
+                                 f
+#endif
+    )
 {
 #ifdef DEBUG_OVERVIEW
     cerr << "Overview[" << this << "]::viewCentreFrameChanged(" << v << "): " << f << endl;
@@ -112,7 +123,7 @@
 }    
 
 void
-Overview::viewZoomLevelChanged(View *v, unsigned long, bool)
+Overview::viewZoomLevelChanged(View *v, int, bool)
 {
     if (v == this) return;
     if (m_views.find(v) != m_views.end()) {
@@ -121,7 +132,7 @@
 }
 
 void
-Overview::viewManagerPlaybackFrameChanged(unsigned long f)
+Overview::viewManagerPlaybackFrameChanged(sv_frame_t f)
 {
 #ifdef DEBUG_OVERVIEW
     cerr << "Overview[" << this << "]::viewManagerPlaybackFrameChanged(" << f << "): " << f << endl;
@@ -137,6 +148,26 @@
     if (changed) update();
 }
 
+QColor
+Overview::getFillWithin() const
+{
+    return Qt::transparent;
+}
+
+QColor
+Overview::getFillWithout() const
+{
+    QColor c = palette().window().color();
+    c.setAlpha(100);
+    return c;
+}
+
+void
+Overview::setBoxColour(QColor c)
+{
+    m_boxColour = c;
+}
+
 void
 Overview::paintEvent(QPaintEvent *e)
 {
@@ -146,9 +177,9 @@
     cerr << "Overview::paintEvent: width is " << width() << ", centre frame " << m_centreFrame << endl;
 #endif
 
-    size_t startFrame = getModelsStartFrame();
-    size_t frameCount = getModelsEndFrame() - getModelsStartFrame();
-    int zoomLevel = frameCount / width();
+    sv_frame_t startFrame = getModelsStartFrame();
+    sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame();
+    int zoomLevel = int(frameCount / width());
     if (zoomLevel < 1) zoomLevel = 1;
     zoomLevel = getZoomConstraintBlockSize(zoomLevel,
 					   ZoomConstraint::RoundUp);
@@ -157,7 +188,7 @@
 	emit zoomLevelChanged(m_zoomLevel, m_followZoom);
     }
 
-    size_t centreFrame = startFrame + m_zoomLevel * (width() / 2);
+    sv_frame_t centreFrame = startFrame + m_zoomLevel * (width() / 2);
     if (centreFrame > (startFrame + getModelsEndFrame())/2) {
 	centreFrame = (startFrame + getModelsEndFrame())/2;
     }
@@ -177,50 +208,73 @@
 
     QPainter paint;
     paint.begin(this);
-
+    paint.setClipRegion(e->region());
+    paint.setRenderHints(QPainter::Antialiasing);
+    
     QRect r(rect());
 
-    if (e) {
-	r = e->rect();
-	paint.setClipRect(r);
-    }
+    // We paint a rounded rect for each distinct set of view extents,
+    // and we colour in the inside and outside of the rect that
+    // corresponds to the current view. (One small caveat -- we don't
+    // know which rect that is yet. We'll have to figure it out
+    // somehow...)
 
-    paint.setPen(getForeground());
+    std::set<std::pair<int, int> > extents;
+    std::vector<QRect> rects;
+    QRect primary;
 
     int y = 0;
 
-    int prevx0 = -10;
-    int prevx1 = -10;
-
     for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) {
 	if (!*i) continue;
 
 	View *w = (View *)*i;
 
-	long f0 = w->getFrameForX(0);
-	long f1 = w->getFrameForX(w->width());
+	sv_frame_t f0 = w->getFrameForX(0);
+	sv_frame_t f1 = w->getFrameForX(w->width());
 
         if (f0 >= 0) {
-            size_t rf0 = w->alignToReference(f0);
+            sv_frame_t rf0 = w->alignToReference(f0);
             f0 = alignFromReference(rf0);
         }
         if (f1 >= 0) {
-            size_t rf1 = w->alignToReference(f1);
+            sv_frame_t rf1 = w->alignToReference(f1);
             f1 = alignFromReference(rf1);
         }
 
 	int x0 = getXForFrame(f0);
 	int x1 = getXForFrame(f1);
 
-	if (x0 != prevx0 || x1 != prevx1) {
-	    y += height() / 10 + 1;
-	    prevx0 = x0;
-	    prevx1 = x1;
-	}
 
 	if (x1 <= x0) x1 = x0 + 1;
-	
-	paint.drawRect(x0, y, x1 - x0, height() - 2 * y);
+
+        std::pair<int, int> extent(x0, x1);
+
+        if (extents.find(extent) == extents.end()) {
+
+    	    y += height() / 10 + 1;
+            extents.insert(extent);
+
+            QRect vr(x0, y, x1 - x0, height() - 2 * y);
+            rects.push_back(vr);
+            primary = vr; //!!! for now
+        }
+    }
+
+    QPainterPath without;
+    without.addRoundedRect(primary, 4, 4);
+    without.addRect(rect());
+    paint.setPen(Qt::NoPen);
+    paint.setBrush(getFillWithout());
+    paint.drawPath(without);
+
+    paint.setBrush(getFillWithin());
+    paint.drawRoundedRect(primary, 4, 4);
+    
+    foreach (QRect vr, rects) {
+        paint.setBrush(Qt::NoBrush);
+        paint.setPen(QPen(m_boxColour, 2));
+        paint.drawRoundedRect(vr, 4, 4);
     }
 
     paint.end();
@@ -230,7 +284,7 @@
 Overview::mousePressEvent(QMouseEvent *e)
 {
     m_clickPos = e->pos();
-    long clickFrame = getFrameForX(m_clickPos.x());
+    sv_frame_t clickFrame = getFrameForX(m_clickPos.x());
     if (clickFrame > 0) m_dragCentreFrame = clickFrame;
     else m_dragCentreFrame = 0;
     m_clickedInRange = true;
@@ -257,13 +311,13 @@
 {
     if (!m_clickedInRange) return;
 
-    long xoff = int(e->x()) - int(m_clickPos.x());
-    long frameOff = xoff * m_zoomLevel;
+    int xoff = int(e->x()) - int(m_clickPos.x());
+    sv_frame_t frameOff = xoff * m_zoomLevel;
     
-    size_t newCentreFrame = m_dragCentreFrame;
+    sv_frame_t newCentreFrame = m_dragCentreFrame;
     if (frameOff > 0) {
 	newCentreFrame += frameOff;
-    } else if (newCentreFrame >= size_t(-frameOff)) {
+    } else if (newCentreFrame >= -frameOff) {
 	newCentreFrame += frameOff;
     } else {
 	newCentreFrame = 0;
@@ -275,20 +329,25 @@
     }
     
     if (std::max(m_centreFrame, newCentreFrame) -
-	std::min(m_centreFrame, newCentreFrame) > size_t(m_zoomLevel)) {
-        size_t rf = alignToReference(newCentreFrame);
+	std::min(m_centreFrame, newCentreFrame) > m_zoomLevel) {
+        sv_frame_t rf = alignToReference(newCentreFrame);
 #ifdef DEBUG_OVERVIEW
         cerr << "Overview::mouseMoveEvent: x " << e->x() << " and click x " << m_clickPos.x() << " -> frame " << newCentreFrame << " -> rf " << rf << endl;
 #endif
-	emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
+        if (m_followPlay == PlaybackScrollContinuous ||
+            m_followPlay == PlaybackScrollPageWithCentre) {
+            emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
+        } else {
+            emit centreFrameChanged(rf, true, PlaybackIgnore);
+        }            
     }
 }
 
 void
 Overview::mouseDoubleClickEvent(QMouseEvent *e)
 {
-    long frame = getFrameForX(e->x());
-    size_t rf = 0;
+    sv_frame_t frame = getFrameForX(e->x());
+    sv_frame_t rf = 0;
     if (frame > 0) rf = alignToReference(frame);
 #ifdef DEBUG_OVERVIEW
     cerr << "Overview::mouseDoubleClickEvent: frame " << frame << " -> rf " << rf << endl;
--- a/view/Overview.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/Overview.h	Wed Apr 20 12:06:28 2016 +0100
@@ -41,14 +41,16 @@
     virtual QString getPropertyContainerIconName() const { return "panner"; }
 
 public slots:
-    virtual void modelChanged(size_t startFrame, size_t endFrame);
+    virtual void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     virtual void modelReplaced();
 
-    virtual void globalCentreFrameChanged(unsigned long);
-    virtual void viewCentreFrameChanged(View *, unsigned long);
-    virtual void viewZoomLevelChanged(View *, unsigned long, bool);
-    virtual void viewManagerPlaybackFrameChanged(unsigned long);
+    virtual void globalCentreFrameChanged(sv_frame_t);
+    virtual void viewCentreFrameChanged(View *, sv_frame_t);
+    virtual void viewZoomLevelChanged(View *, int, bool);
+    virtual void viewManagerPlaybackFrameChanged(sv_frame_t);
 
+    virtual void setBoxColour(QColor);
+    
 protected:
     virtual void paintEvent(QPaintEvent *e);
     virtual void mousePressEvent(QMouseEvent *e);
@@ -59,11 +61,15 @@
     virtual void leaveEvent(QEvent *);
     virtual bool shouldLabelSelections() const { return false; }
 
+    QColor getFillWithin() const;
+    QColor getFillWithout() const;
+    
     QPoint m_clickPos;
     QPoint m_mousePos;
     bool m_clickedInRange;
-    size_t m_dragCentreFrame;
+    sv_frame_t m_dragCentreFrame;
     QTime m_modelTestTime;
+    QColor m_boxColour;
     
     typedef std::set<View *> ViewSet;
     ViewSet m_views;
--- a/view/Pane.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/Pane.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -24,6 +24,12 @@
 #include "widgets/TextAbbrev.h"
 #include "base/Preferences.h"
 #include "layer/WaveformLayer.h"
+#include "layer/TimeRulerLayer.h"
+
+// GF: added so we can propagate the mouse move event to the note layer for context handling.
+#include "layer/LayerFactory.h"
+#include "layer/FlexiNoteLayer.h"
+
 
 //!!! ugh
 #include "data/model/WaveFileModel.h"
@@ -36,6 +42,7 @@
 #include <QCursor>
 #include <QTextStream>
 #include <QMimeData>
+#include <QApplication>
 
 #include <iostream>
 #include <cmath>
@@ -72,12 +79,15 @@
     m_releasing(false),
     m_centreLineVisible(true),
     m_scaleWidth(0),
+    m_pendingWheelAngle(0),
     m_headsUpDisplay(0),
     m_vpan(0),
     m_hthumb(0),
     m_vthumb(0),
     m_reset(0),
-    m_mouseInWidget(false)
+    m_mouseInWidget(false),
+    m_playbackFrameMoveScheduled(false),
+    m_playbackFrameMoveTo(0)
 {
     setObjectName("Pane");
     setMouseTracking(true);
@@ -85,8 +95,10 @@
     
     updateHeadsUpDisplay();
 
-
-//    SVDEBUG << "Pane::Pane(" << this << ") returning" << endl;
+    connect(this, SIGNAL(regionOutlined(QRect)), 
+            this, SLOT(zoomToRegion(QRect)));
+
+    cerr << "Pane::Pane(" << this << ") returning" << endl;
 }
 
 void
@@ -132,7 +144,7 @@
         m_hthumb->setFixedWidth(70);
         m_hthumb->setFixedHeight(16);
         m_hthumb->setDefaultValue(0);
-        m_hthumb->setSpeed(0.6);
+        m_hthumb->setSpeed(0.6f);
         connect(m_hthumb, SIGNAL(valueChanged(int)), this, 
                 SLOT(horizontalThumbwheelMoved(int)));
         connect(m_hthumb, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
@@ -168,7 +180,7 @@
         }
 
         m_reset = new NotifyingPushButton;
-	m_reset->setFlat(true);
+        m_reset->setFlat(true);
         m_reset->setCursor(Qt::ArrowCursor);
         m_reset->setFixedHeight(16);
         m_reset->setFixedWidth(16);
@@ -191,7 +203,7 @@
 
     //!!! pull out into function (presumably in View)
     bool haveConstraint = false;
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end();
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end();
          ++i) {
         if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) {
             haveConstraint = true;
@@ -309,12 +321,12 @@
         return;
     }
 
-    float vmin, vmax, dmin, dmax;
+    double vmin, vmax, dmin, dmax;
     if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax) && vmax != vmin) {
-        float y0 = (dmin - vmin) / (vmax - vmin);
-        float y1 = (dmax - vmin) / (vmax - vmin);
+        double y0 = (dmin - vmin) / (vmax - vmin);
+        double y1 = (dmax - vmin) / (vmax - vmin);
         m_vpan->blockSignals(true);
-        m_vpan->setRectExtents(0, 1.0 - y1, 1, y1 - y0);
+        m_vpan->setRectExtents(0, float(1.0 - y1), 1, float(y1 - y0));
         m_vpan->blockSignals(false);
         m_vpan->show();
     } else {
@@ -328,19 +340,19 @@
     QPoint discard;
     bool b0, b1;
 
-    if (m_manager && m_manager->getToolMode() == ViewManager::MeasureMode) {
+    if (m_manager && m_manager->getToolModeFor(this) == ViewManager::MeasureMode) {
         return false;
     }
-
+    
     if (m_manager && !m_manager->shouldIlluminateLocalFeatures()) {
         return false;
     }
 
-    if (layer == getSelectedLayer() &&
-	!shouldIlluminateLocalSelection(discard, b0, b1)) {
-
-	pos = m_identifyPoint;
-	return m_identifyFeatures;
+    if (layer == getInteractionLayer() &&
+        !shouldIlluminateLocalSelection(discard, b0, b1)) {
+
+        pos = m_identifyPoint;
+        return m_identifyFeatures;
     }
 
     return false;
@@ -348,25 +360,25 @@
 
 bool
 Pane::shouldIlluminateLocalSelection(QPoint &pos,
-				     bool &closeToLeft,
-				     bool &closeToRight) const
+                     bool &closeToLeft,
+                     bool &closeToRight) const
 {
     if (m_identifyFeatures &&
-	m_manager &&
-	m_manager->getToolMode() == ViewManager::EditMode &&
-	!m_manager->getSelections().empty() &&
-	!selectionIsBeingEdited()) {
-
-	Selection s(getSelectionAt(m_identifyPoint.x(),
-				   closeToLeft, closeToRight));
-
-	if (!s.isEmpty()) {
-	    if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) {
-		
-		pos = m_identifyPoint;
-		return true;
-	    }
-	}
+        m_manager &&
+        m_manager->getToolModeFor(this) == ViewManager::EditMode &&
+        !m_manager->getSelections().empty() &&
+        !selectionIsBeingEdited()) {
+
+        Selection s(getSelectionAt(m_identifyPoint.x(),
+                                   closeToLeft, closeToRight));
+
+        if (!s.isEmpty()) {
+            if (getInteractionLayer() && getInteractionLayer()->isLayerEditable()) {
+            
+                pos = m_identifyPoint;
+                return true;
+            }
+        }
     }
 
     return false;
@@ -376,10 +388,10 @@
 Pane::selectionIsBeingEdited() const
 {
     if (!m_editingSelection.isEmpty()) {
-	if (m_mousePos != m_clickPos &&
-	    getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
-	    return true;
-	}
+        if (m_mousePos != m_clickPos &&
+            getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
+            return true;
+        }
     }
     return false;
 }
@@ -408,14 +420,14 @@
 
     if (e) paint.setClipRect(r);
 
-    ViewManager::ToolMode toolMode = m_manager->getToolMode();
+    ViewManager::ToolMode toolMode = ViewManager::NavigateMode;
+    if (m_manager) toolMode = m_manager->getToolModeFor(this);
 
     if (m_manager &&
-//        !m_manager->isPlaying() &&
         m_mouseInWidget &&
         toolMode == ViewManager::MeasureMode) {
 
-        for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
+        for (LayerList::iterator vi = m_layerStack.end(); vi != m_layerStack.begin(); ) {
             --vi;
 
             std::vector<QRect> crosshairExtents;
@@ -436,7 +448,7 @@
     const Model *waveformModel = 0; // just for reporting purposes
     const Model *workModel = 0;
 
-    for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
+    for (LayerList::iterator vi = m_layerStack.end(); vi != m_layerStack.begin(); ) {
         --vi;
         if (!haveSomeTimeXAxis && (*vi)->hasTimeXAxis()) {
             haveSomeTimeXAxis = true;
@@ -458,6 +470,10 @@
 
     m_scaleWidth = 0;
 
+    if (workModel && hasTopLayerTimeXAxis()) {
+        drawModelTimeExtents(r, paint, workModel);
+    }
+
     if (m_manager && m_manager->shouldShowVerticalScale() && topLayer) {
         drawVerticalScale(r, topLayer, paint);
     }
@@ -468,7 +484,7 @@
         drawFeatureDescription(topLayer, paint);
     }
     
-    int sampleRate = getModelsSampleRate();
+    sv_samplerate_t sampleRate = getModelsSampleRate();
     paint.setBrush(Qt::NoBrush);
 
     if (m_centreLineVisible &&
@@ -480,6 +496,7 @@
     paint.setPen(QColor(50, 50, 50));
 
     if (waveformModel &&
+        sampleRate &&
         m_manager &&
         m_manager->shouldShowDuration()) {
         drawDurationAndRate(r, waveformModel, sampleRate, paint);
@@ -532,7 +549,7 @@
     paint.end();
 }
 
-size_t
+int
 Pane::getVerticalScaleWidth() const
 {
     if (m_scaleWidth > 0) return m_scaleWidth;
@@ -544,7 +561,7 @@
 {
     Layer *scaleLayer = 0;
 
-    float min, max;
+    double min, max;
     bool log;
     QString unit;
 
@@ -571,8 +588,8 @@
 
             if (!hasValueExtents) {
 
-                for (LayerList::iterator vi = m_layers.end();
-                     vi != m_layers.begin(); ) {
+                for (LayerList::iterator vi = m_layerStack.end();
+                     vi != m_layerStack.begin(); ) {
                         
                     --vi;
                         
@@ -591,8 +608,8 @@
 
                 QString requireUnit = unit;
 
-                for (LayerList::iterator vi = m_layers.end();
-                     vi != m_layers.begin(); ) {
+                for (LayerList::iterator vi = m_layerStack.end();
+                     vi != m_layerStack.begin(); ) {
                         
                     --vi;
                         
@@ -626,9 +643,9 @@
         
     if (m_scaleWidth > 0 && r.left() < m_scaleWidth) {
 
-//	    Profiler profiler("Pane::paintEvent - painting vertical scale", true);
-
-//	    SVDEBUG << "Pane::paintEvent: calling paint.save() in vertical scale block" << endl;
+//      Profiler profiler("Pane::paintEvent - painting vertical scale", true);
+
+//      SVDEBUG << "Pane::paintEvent: calling paint.save() in vertical scale block" << endl;
         paint.save();
             
         paint.setPen(getForeground());
@@ -649,7 +666,7 @@
 {
     QPoint pos = m_identifyPoint;
     QString desc = topLayer->getFeatureDescription(this, pos);
-	    
+        
     if (desc != "") {
         
         paint.save();
@@ -698,7 +715,7 @@
 }
 
 void
-Pane::drawCentreLine(int sampleRate, QPainter &paint, bool omitLine)
+Pane::drawCentreLine(sv_samplerate_t sampleRate, QPainter &paint, bool omitLine)
 {
     int fontHeight = paint.fontMetrics().height();
     int fontAscent = paint.fontMetrics().ascent();
@@ -723,10 +740,10 @@
     
     int y = height() - fontHeight + fontAscent - 6;
     
-    LayerList::iterator vi = m_layers.end();
+    LayerList::iterator vi = m_layerStack.end();
     
-    if (vi != m_layers.begin()) {
-	    
+    if (vi != m_layerStack.begin()) {
+        
         switch ((*--vi)->getPreferredFrameCountPosition()) {
             
         case Layer::PositionTop:
@@ -768,6 +785,37 @@
 }
 
 void
+Pane::drawModelTimeExtents(QRect r, QPainter &paint, const Model *model)
+{
+    int x0 = getXForFrame(model->getStartFrame());
+    int x1 = getXForFrame(model->getEndFrame());
+
+    paint.save();
+
+    QBrush brush;
+
+    if (hasLightBackground()) {
+        brush = QBrush(QColor("#f8f8f8"));
+        paint.setPen(Qt::black);
+    } else {
+        brush = QBrush(QColor("#101010"));
+        paint.setPen(Qt::white);
+    }
+
+    if (x0 > r.x()) {
+        paint.fillRect(0, 0, x0, height(), brush);
+        paint.drawLine(x0, 0, x0, height());
+    }
+
+    if (x1 < r.x() + r.width()) {
+        paint.fillRect(x1, 0, width() - x1, height(), brush);
+        paint.drawLine(x1, 0, x1, height());
+    }
+
+    paint.restore();
+}
+
+void
 Pane::drawAlignmentStatus(QRect r, QPainter &paint, const Model *model,
                           bool down)
 {
@@ -870,13 +918,13 @@
         lly -= 20;
     }
 
-    if (r.y() + r.height() < lly - int(m_layers.size()) * fontHeight) {
+    if (r.y() + r.height() < lly - int(m_layerStack.size()) * fontHeight) {
         return;
     }
 
     QStringList texts;
     std::vector<QPixmap> pixmaps;
-    for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
         texts.push_back((*i)->getLayerPresentationName());
 //        cerr << "Pane " << this << ": Layer presentation name for " << *i << ": "
 //                  << texts[texts.size()-1] << endl;
@@ -893,8 +941,8 @@
     }
     
     if (r.x() + r.width() >= llx - fontAscent - 3) {
-	
-        for (size_t i = 0; i < texts.size(); ++i) {
+    
+        for (int i = 0; i < texts.size(); ++i) {
 
 //            cerr << "Pane "<< this << ": text " << i << ": " << texts[i] << endl;
             
@@ -922,7 +970,7 @@
 {
     int offset = m_mousePos.x() - m_clickPos.x();
 
-    long origStart = m_editingSelection.getStartFrame();
+    sv_frame_t origStart = m_editingSelection.getStartFrame();
 
     int p0 = getXForFrame(origStart) + offset;
     int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset;
@@ -933,15 +981,15 @@
         p0 = getXForFrame(m_editingSelection.getStartFrame());
     }
     
-    long newStart = getFrameForX(p0);
-    long newEnd = getFrameForX(p1);
+    sv_frame_t newStart = getFrameForX(p0);
+    sv_frame_t newEnd = getFrameForX(p1);
     
     paint.save();
     paint.setPen(QPen(getForeground(), 2));
 
     int fontHeight = paint.fontMetrics().height();
     int fontAscent = paint.fontMetrics().ascent();
-    int sampleRate = getModelsSampleRate();
+    sv_samplerate_t sampleRate = getModelsSampleRate();
     QString startText, endText, offsetText;
     startText = QString("%1").arg(newStart);
     endText = QString("%1").arg(newEnd);
@@ -990,17 +1038,17 @@
 
 void
 Pane::drawDurationAndRate(QRect r, const Model *waveformModel,
-                          int sampleRate, QPainter &paint)
+                          sv_samplerate_t sampleRate, QPainter &paint)
 {
     int fontHeight = paint.fontMetrics().height();
     int fontAscent = paint.fontMetrics().ascent();
 
     if (r.y() + r.height() < height() - fontHeight - 6) return;
 
-    size_t modelRate = waveformModel->getSampleRate();
-    size_t nativeRate = waveformModel->getNativeRate();
-    size_t playbackRate = m_manager->getPlaybackSampleRate();
-    size_t outputRate = m_manager->getOutputSampleRate();
+    sv_samplerate_t modelRate = waveformModel->getSampleRate();
+    sv_samplerate_t nativeRate = waveformModel->getNativeRate();
+    sv_samplerate_t playbackRate = m_manager->getPlaybackSampleRate();
+    sv_samplerate_t outputRate = m_manager->getOutputSampleRate();
         
     QString srNote = "";
 
@@ -1039,7 +1087,7 @@
 }
 
 bool
-Pane::render(QPainter &paint, int xorigin, size_t f0, size_t f1)
+Pane::render(QPainter &paint, int xorigin, sv_frame_t f0, sv_frame_t f1)
 {
     if (!View::render(paint, xorigin + m_scaleWidth, f0, f1)) {
         return false;
@@ -1047,8 +1095,9 @@
 
     if (m_scaleWidth > 0) {
 
-        for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
-            --vi;
+        Layer *layer = getTopLayer();
+
+        if (layer) {
             
             paint.save();
             
@@ -1057,12 +1106,11 @@
             paint.drawRect(xorigin, -1, m_scaleWidth, height()+1);
             
             paint.setBrush(Qt::NoBrush);
-            (*vi)->paintVerticalScale
+            layer->paintVerticalScale
                 (this, m_manager->shouldShowVerticalColourScale(),
                  paint, QRect(xorigin, 0, m_scaleWidth, height()));
             
             paint.restore();
-            break;
         }
     }
 
@@ -1070,10 +1118,10 @@
 }
 
 QImage *
-Pane::toNewImage(size_t f0, size_t f1)
+Pane::toNewImage(sv_frame_t f0, sv_frame_t f1)
 {
-    size_t x0 = f0 / getZoomLevel();
-    size_t x1 = f1 / getZoomLevel();
+    int x0 = int(f0 / getZoomLevel());
+    int x1 = int(f1 / getZoomLevel());
 
     QImage *image = new QImage(x1 - x0 + m_scaleWidth,
                                height(), QImage::Format_RGB32);
@@ -1081,12 +1129,11 @@
     int formerScaleWidth = m_scaleWidth;
             
     if (m_manager && m_manager->shouldShowVerticalScale()) {
-        for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
-            --vi;
+        Layer *layer = getTopLayer();
+        if (layer) {
             QPainter paint(image);
-            m_scaleWidth = (*vi)->getVerticalScaleWidth
+            m_scaleWidth = layer->getVerticalScaleWidth
                 (this, m_manager->shouldShowVerticalColourScale(), paint);
-            break;
         }
     } else {
         m_scaleWidth = 0;
@@ -1110,7 +1157,7 @@
 }
 
 QSize
-Pane::getImageSize(size_t f0, size_t f1)
+Pane::getImageSize(sv_frame_t f0, sv_frame_t f1)
 {
     QSize s = View::getImageSize(f0, f1);
     QImage *image = new QImage(100, 100, QImage::Format_RGB32);
@@ -1118,23 +1165,22 @@
 
     int sw = 0;
     if (m_manager && m_manager->shouldShowVerticalScale()) {
-        for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
-            --vi;
-            sw = (*vi)->getVerticalScaleWidth
+        Layer *layer = getTopLayer();
+        if (layer) {
+            sw = layer->getVerticalScaleWidth
                 (this, m_manager->shouldShowVerticalColourScale(), paint);
-            break;
         }
     }
     
     return QSize(sw + s.width(), s.height());
 }
 
-size_t
+sv_frame_t
 Pane::getFirstVisibleFrame() const
 {
-    long f0 = getFrameForX(m_scaleWidth);
-    size_t f = View::getFirstVisibleFrame();
-    if (f0 < 0 || f0 < long(f)) return f;
+    sv_frame_t f0 = getFrameForX(m_scaleWidth);
+    sv_frame_t f = View::getFirstVisibleFrame();
+    if (f0 < 0 || f0 < f) return f;
     return f0;
 }
 
@@ -1145,10 +1191,10 @@
 
     if (!m_manager) return Selection();
 
-    long testFrame = getFrameForX(x - 5);
+    sv_frame_t testFrame = getFrameForX(x - 5);
     if (testFrame < 0) {
-	testFrame = getFrameForX(x);
-	if (testFrame < 0) return Selection();
+        testFrame = getFrameForX(x);
+        if (testFrame < 0) return Selection();
     }
 
     Selection selection = m_manager->getContainingSelection(testFrame, true);
@@ -1174,15 +1220,15 @@
 bool
 Pane::canTopLayerMoveVertical()
 {
-    float vmin, vmax, dmin, dmax;
+    double vmin, vmax, dmin, dmax;
     if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) return false;
     if (dmin <= vmin && dmax >= vmax) return false;
     return true;
 }
 
 bool
-Pane::getTopLayerDisplayExtents(float &vmin, float &vmax,
-                                float &dmin, float &dmax,
+Pane::getTopLayerDisplayExtents(double &vmin, double &vmax,
+                                double &dmin, double &dmax,
                                 QString *unit) 
 {
     Layer *layer = getTopLayer();
@@ -1196,7 +1242,7 @@
 }
 
 bool
-Pane::setTopLayerDisplayExtents(float dmin, float dmax)
+Pane::setTopLayerDisplayExtents(double dmin, double dmax)
 {
     Layer *layer = getTopLayer();
     if (!layer) return false;
@@ -1226,47 +1272,18 @@
                         tr("Double-click middle button to relocate with any tool"));
     kr.registerShortcut(tr("Menu"), tr("Right"),
                         tr("Show pane context menu"));
-    
-    kr.setCategory(tr("Navigate Tool Mouse Actions"));
-    
-    kr.registerShortcut(tr("Navigate"), tr("Left"), 
-                        tr("Click left button and drag to move around"));
-    kr.registerShortcut(tr("Zoom to Area"), tr("Shift+Left"), 
-                        tr("Shift-click left button and drag to zoom to a rectangular area"));
-    kr.registerShortcut(tr("Relocate"), tr("Double-Click Left"), 
-                        tr("Double-click left button to jump to clicked location"));
-    kr.registerShortcut(tr("Edit"), tr("Double-Click Left"), 
-                        tr("Double-click left button on an item to edit it"));
-        
-    kr.setCategory(tr("Select Tool Mouse Actions"));
-    kr.registerShortcut(tr("Select"), tr("Left"), 
-                        tr("Click left button and drag to select region; drag region edge to resize"));
-    kr.registerShortcut(tr("Multi Select"), tr("Ctrl+Left"), 
-#ifdef Q_OS_MAC
-                        tr("Cmd-click left button and drag to select an additional region"));
-#else
-                        tr("Ctrl-click left button and drag to select an additional region"));
-#endif
-    kr.registerShortcut(tr("Fine Select"), tr("Shift+Left"), 
-                        tr("Shift-click left button and drag to select without snapping to items or grid"));
-    
-    kr.setCategory(tr("Edit Tool Mouse Actions"));
-    kr.registerShortcut(tr("Move"), tr("Left"), 
-                        tr("Click left button on an item or selected region and drag to move"));
-    kr.registerShortcut(tr("Edit"), tr("Double-Click Left"), 
-                        tr("Double-click left button on an item to edit it"));
-    
-    kr.setCategory(tr("Draw Tool Mouse Actions"));
-    kr.registerShortcut(tr("Draw"), tr("Left"), 
-                        tr("Click left button and drag to create new item"));
-
-    kr.setCategory(tr("Measure Tool Mouse Actions"));
-    kr.registerShortcut(tr("Measure Area"), tr("Left"), 
-                        tr("Click left button and drag to measure a rectangular area"));
-    kr.registerShortcut(tr("Measure Item"), tr("Double-Click Left"), 
-                        tr("Click left button and drag to measure extents of an item or shape"));
-    kr.registerShortcut(tr("Zoom to Area"), tr("Shift+Left"), 
-                        tr("Shift-click left button and drag to zoom to a rectangular area"));
+}
+
+Layer *
+Pane::getTopFlexiNoteLayer()
+{
+    for (int i = int(m_layerStack.size()) - 1; i >= 0; --i) {
+        if (LayerFactory::getInstance()->getLayerType(m_layerStack[i]) ==
+            LayerFactory::FlexiNotes) {
+            return m_layerStack[i];
+        }
+    }
+    return 0;
 }
 
 void
@@ -1291,7 +1308,7 @@
     m_dragMode = UnresolvedDrag;
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     m_navigating = false;
     m_resizing = false;
@@ -1303,78 +1320,104 @@
         (mode == ViewManager::MeasureMode &&
          (e->buttons() & Qt::LeftButton) && m_shiftPressed)) {
 
-	if (mode != ViewManager::NavigateMode) {
-	    setCursor(Qt::PointingHandCursor);
-	}
-
-	m_navigating = true;
-	m_dragCentreFrame = m_centreFrame;
+        if (mode != ViewManager::NavigateMode) {
+            setCursor(Qt::PointingHandCursor);
+        }
+
+        m_navigating = true;
+        m_dragCentreFrame = m_centreFrame;
         m_dragStartMinValue = 0;
         
-        float vmin, vmax, dmin, dmax;
+        double vmin, vmax, dmin, dmax;
         if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) {
             m_dragStartMinValue = dmin;
         }
 
+        if (m_followPlay == PlaybackScrollPage) {
+            // Schedule a play-head move to the mouse frame
+            // location. This will happen only if nothing else of
+            // interest happens (double-click, drag) before the
+            // timeout.
+            schedulePlaybackFrameMove(getFrameForX(e->x()));
+        }
+
     } else if (mode == ViewManager::SelectMode) {
 
         if (!hasTopLayerTimeXAxis()) return;
 
-	bool closeToLeft = false, closeToRight = false;
-	Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight);
-
-	if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
-
-	    m_manager->removeSelection(selection);
-
-	    if (closeToLeft) {
-		m_selectionStartFrame = selection.getEndFrame();
-	    } else {
-		m_selectionStartFrame = selection.getStartFrame();
-	    }
-
-	    m_manager->setInProgressSelection(selection, false);
-	    m_resizing = true;
-	
-	} else {
-
-	    int mouseFrame = getFrameForX(e->x());
-	    size_t resolution = 1;
-	    int snapFrame = mouseFrame;
-	
-	    Layer *layer = getSelectedLayer();
-	    if (layer && !m_shiftPressed) {
-		layer->snapToFeatureFrame(this, snapFrame,
-					  resolution, Layer::SnapLeft);
-	    }
-	    
-	    if (snapFrame < 0) snapFrame = 0;
-	    m_selectionStartFrame = snapFrame;
-	    if (m_manager) {
-		m_manager->setInProgressSelection
+        bool closeToLeft = false, closeToRight = false;
+        Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight);
+
+        if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
+
+            m_manager->removeSelection(selection);
+
+            if (closeToLeft) {
+                m_selectionStartFrame = selection.getEndFrame();
+            } else {
+                m_selectionStartFrame = selection.getStartFrame();
+            }
+            
+            m_manager->setInProgressSelection(selection, false);
+            m_resizing = true;
+            
+        } else {
+            
+            sv_frame_t mouseFrame = getFrameForX(e->x());
+            int resolution = 1;
+            sv_frame_t snapFrame = mouseFrame;
+    
+            Layer *layer = getInteractionLayer();
+            if (layer && !m_shiftPressed &&
+                !qobject_cast<TimeRulerLayer *>(layer)) { // don't snap to secs
+                layer->snapToFeatureFrame(this, snapFrame,
+                                          resolution, Layer::SnapLeft);
+            }
+        
+            if (snapFrame < 0) snapFrame = 0;
+            m_selectionStartFrame = snapFrame;
+            if (m_manager) {
+                m_manager->setInProgressSelection
                     (Selection(alignToReference(snapFrame),
                                alignToReference(snapFrame + resolution)),
                      !m_ctrlPressed);
-	    }
-
-	    m_resizing = false;
-	}
-
-	update();
+            }
+
+            m_resizing = false;
+
+            if (m_followPlay == PlaybackScrollPage) {
+                // Schedule a play-head move to the mouse frame
+                // location. This will happen only if nothing else of
+                // interest happens (double-click, drag) before the
+                // timeout.
+                schedulePlaybackFrameMove(mouseFrame);
+            }
+        }
+
+        update();
 
     } else if (mode == ViewManager::DrawMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->drawStart(this, e);
-	}
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->drawStart(this, e);
+        }
 
     } else if (mode == ViewManager::EraseMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->eraseStart(this, e);
-	}
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->eraseStart(this, e);
+        }
+
+        // GF: handle mouse press for NoteEditMode 
+    } else if (mode == ViewManager::NoteEditMode) {
+
+        std::cerr << "mouse pressed in note edit mode" << std::endl;
+        Layer *layer = getTopFlexiNoteLayer();
+        if (layer) {
+            layer->splitStart(this, e); 
+        }
 
     } else if (mode == ViewManager::EditMode) {
 
@@ -1392,43 +1435,63 @@
 }
 
 void
+Pane::schedulePlaybackFrameMove(sv_frame_t frame)
+{
+    m_playbackFrameMoveTo = frame;
+    m_playbackFrameMoveScheduled = true;
+    QTimer::singleShot(QApplication::doubleClickInterval() + 10, this,
+                       SLOT(playbackScheduleTimerElapsed()));
+}
+
+void
+Pane::playbackScheduleTimerElapsed()
+{
+    if (m_playbackFrameMoveScheduled) {
+        m_manager->setPlaybackFrame(m_playbackFrameMoveTo);
+        m_playbackFrameMoveScheduled = false;
+    }
+}
+
+void
 Pane::mouseReleaseEvent(QMouseEvent *e)
 {
-    if (e->buttons() & Qt::RightButton) {
+    if (e && (e->buttons() & Qt::RightButton)) {
         return;
     }
 
 //    cerr << "mouseReleaseEvent" << endl;
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     m_releasing = true;
 
     if (m_clickedInRange) {
-	mouseMoveEvent(e);
+        mouseMoveEvent(e);
     }
 
+    sv_frame_t mouseFrame = e ? getFrameForX(e->x()) : 0;
+    if (mouseFrame < 0) mouseFrame = 0;
+
     if (m_navigating || mode == ViewManager::NavigateMode) {
 
-	m_navigating = false;
-
-	if (mode != ViewManager::NavigateMode) {
-	    // restore cursor
-	    toolModeChanged();
-	}
-
-	if (m_shiftPressed) {
-
-	    int x0 = std::min(m_clickPos.x(), m_mousePos.x());
-	    int x1 = std::max(m_clickPos.x(), m_mousePos.x());
-
-	    int y0 = std::min(m_clickPos.y(), m_mousePos.y());
-	    int y1 = std::max(m_clickPos.y(), m_mousePos.y());
-
-            zoomToRegion(x0, y0, x1, y1);
-
-    }
+        m_navigating = false;
+
+        if (mode != ViewManager::NavigateMode) {
+            // restore cursor
+            toolModeChanged();
+        }
+
+        if (m_shiftPressed) {
+
+            int x0 = std::min(m_clickPos.x(), m_mousePos.x());
+            int x1 = std::max(m_clickPos.x(), m_mousePos.x());
+
+            int y0 = std::min(m_clickPos.y(), m_mousePos.y());
+            int y1 = std::max(m_clickPos.y(), m_mousePos.y());
+
+            emit regionOutlined(QRect(x0, y0, x1 - x0, y1 - y0));
+        }
 
     } else if (mode == ViewManager::SelectMode) {
 
@@ -1437,53 +1500,71 @@
             return;
         }
 
-	if (m_manager && m_manager->haveInProgressSelection()) {
-
-	    bool exclusive;
-	    Selection selection = m_manager->getInProgressSelection(exclusive);
-	    
-	    if (selection.getEndFrame() < selection.getStartFrame() + 2) {
-		selection = Selection();
-	    }
-	    
-	    m_manager->clearInProgressSelection();
-	    
-	    if (exclusive) {
-		m_manager->setSelection(selection);
-	    } else {
-		m_manager->addSelection(selection);
-	    }
-	}
-	
-	update();
+        if (m_manager && m_manager->haveInProgressSelection()) {
+
+            //cerr << "JTEST: release with selection" << endl;
+            bool exclusive;
+            Selection selection = m_manager->getInProgressSelection(exclusive);
+        
+            if (selection.getEndFrame() < selection.getStartFrame() + 2) {
+                selection = Selection();
+            }
+        
+            m_manager->clearInProgressSelection();
+        
+            if (exclusive) {
+                m_manager->setSelection(selection);
+            } else {
+                m_manager->addSelection(selection);
+            }
+        }
+    
+        update();
 
     } else if (mode == ViewManager::DrawMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-        layer->drawEnd(this, e);
-        update();
-	}
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->drawEnd(this, e);
+            update();
+        }
 
     } else if (mode == ViewManager::EraseMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->eraseEnd(this, e);
-	    update();
-	}
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->eraseEnd(this, e);
+            update();
+        }
+
+    } else if (mode == ViewManager::NoteEditMode) {
+    
+        //GF: handle mouse release for NoteEditMode (note: works but will need to re-think this a bit later)
+        Layer *layer = getTopFlexiNoteLayer();
+
+        if (layer) {
+            layer->splitEnd(this, e);
+            update();
+
+            if (m_editing) {
+                if (!editSelectionEnd(e)) {
+                    layer->editEnd(this, e);
+                    update();
+                }
+            }
+        } 
 
     } else if (mode == ViewManager::EditMode) {
-
+        
         if (m_editing) {
             if (!editSelectionEnd(e)) {
-                Layer *layer = getSelectedLayer();
+                Layer *layer = getInteractionLayer();
                 if (layer && layer->isLayerEditable()) {
                     layer->editEnd(this, e);
                     update();
                 }
             }
-        }
+        } 
 
     } else if (mode == ViewManager::MeasureMode) {
 
@@ -1502,7 +1583,7 @@
 void
 Pane::mouseMoveEvent(QMouseEvent *e)
 {
-    if (e->buttons() & Qt::RightButton) {
+    if (!e || (e->buttons() & Qt::RightButton)) {
         return;
     }
 
@@ -1526,28 +1607,39 @@
     }
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     QPoint prevPoint = m_identifyPoint;
     m_identifyPoint = e->pos();
 
     if (!m_clickedInRange) {
-	
-	if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) {
-	    bool closeToLeft = false, closeToRight = false;
-	    getSelectionAt(e->x(), closeToLeft, closeToRight);
-	    if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
-		setCursor(Qt::SizeHorCursor);
-	    } else {
-		setCursor(Qt::ArrowCursor);
-	    }
-	}
-
-        if (!m_manager->isPlaying()) {
+    
+        // GF: handle mouse move for context sensitive cursor switching in NoteEditMode.
+        // GF: Propagate the event to FlexiNoteLayer. I somehow feel it's best handeled there rather than here, but perhaps not if this will be needed elsewhere too.
+        if (mode == ViewManager::NoteEditMode) {
+            FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(getTopFlexiNoteLayer());
+            if (layer) {
+                layer->mouseMoveEvent(this, e); //!!! ew
+                update();
+                // return;
+            }
+        }   
+    
+        if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) {
+            bool closeToLeft = false, closeToRight = false;
+            getSelectionAt(e->x(), closeToLeft, closeToRight);
+            if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
+                setCursor(Qt::SizeHorCursor);
+            } else {
+                setCursor(Qt::ArrowCursor);
+            }
+        }
+
+        if (m_manager && !m_manager->isPlaying()) {
 
             bool updating = false;
 
-            if (getSelectedLayer() &&
+            if (getInteractionLayer() &&
                 m_manager->shouldIlluminateLocalFeatures()) {
 
                 bool previouslyIdentifying = m_identifyFeatures;
@@ -1560,8 +1652,7 @@
                 }
             }
 
-            if (!updating && mode == ViewManager::MeasureMode &&
-                m_manager && !m_manager->isPlaying()) {
+            if (!updating && mode == ViewManager::MeasureMode) {
 
                 Layer *layer = getTopLayer();
                 if (layer && layer->nearestMeasurementRectChanged
@@ -1571,17 +1662,17 @@
             }
         }
 
-	return;
+        return;
     }
 
     if (m_navigating || mode == ViewManager::NavigateMode) {
 
-	if (m_shiftPressed) {
-
-	    m_mousePos = e->pos();
-	    update();
-
-	} else {
+        if (m_shiftPressed) {
+
+            m_mousePos = e->pos();
+            update();
+
+        } else {
 
             dragTopLayer(e);
         }
@@ -1594,27 +1685,25 @@
 
     } else if (mode == ViewManager::DrawMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-        layer->drawDrag(this, e);
-	}
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->drawDrag(this, e);
+        }
 
     } else if (mode == ViewManager::EraseMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->eraseDrag(this, e);
-	}
-
-    } else if (mode == ViewManager::EditMode) {
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->eraseDrag(this, e);
+        }
+
+        // GF: handling NoteEditMode dragging and boundary actions for mouseMoveEvent
+    } else if (mode == ViewManager::NoteEditMode) {
 
         bool resist = true;
 
         if ((e->modifiers() & Qt::ShiftModifier)) {
             m_shiftPressed = true;
-            // ... but don't set it false if shift has been
-            // released -- we want the state when we started
-            // dragging to be used most of the time
         }
 
         if (m_shiftPressed) resist = false;
@@ -1641,7 +1730,74 @@
                                        e->modifiers());
 
                 if (!editSelectionStart(&clickEvent)) {
-                    Layer *layer = getSelectedLayer();
+                    Layer *layer = getTopFlexiNoteLayer();
+                    if (layer) {
+                        std::cerr << "calling edit start" << std::endl;
+                        layer->editStart(this, &clickEvent);
+                    }
+                }
+            }
+
+        } else {
+
+            if (!editSelectionDrag(e)) {
+
+                Layer *layer = getTopFlexiNoteLayer();
+
+                if (layer) {
+
+                    int x = e->x();
+                    int y = e->y();
+                    if (m_dragMode == VerticalDrag) x = m_clickPos.x();
+                    else if (m_dragMode == HorizontalDrag) y = m_clickPos.y();
+
+                    QMouseEvent moveEvent(QEvent::MouseMove,
+                                          QPoint(x, y),
+                                          Qt::NoButton,
+                                          e->buttons(),
+                                          e->modifiers());
+                    std::cerr << "calling editDrag" << std::endl;
+                    layer->editDrag(this, &moveEvent);
+                }
+            }
+        }
+
+    } else if (mode == ViewManager::EditMode) {
+
+        bool resist = true;
+
+        if ((e->modifiers() & Qt::ShiftModifier)) {
+            m_shiftPressed = true;
+            // ... but don't set it false if shift has been
+            // released -- we want the state when we started
+            // dragging to be used most of the time
+        }
+
+        if (m_shiftPressed) resist = false;
+
+        m_dragMode = updateDragMode
+            (m_dragMode,
+             m_clickPos,
+             e->pos(),
+             true,    // can move horiz
+             true,    // can move vert
+             resist,  // resist horiz
+             resist); // resist vert
+
+        if (!m_editing) {
+
+            if (m_dragMode != UnresolvedDrag) {
+
+                m_editing = true;
+
+                QMouseEvent clickEvent(QEvent::MouseButtonPress,
+                                       m_clickPos,
+                                       Qt::NoButton,
+                                       e->buttons(),
+                                       e->modifiers());
+
+                if (!editSelectionStart(&clickEvent)) {
+                    Layer *layer = getInteractionLayer();
                     if (layer && layer->isLayerEditable()) {
                         layer->editStart(this, &clickEvent);
                     }
@@ -1652,7 +1808,7 @@
 
             if (!editSelectionDrag(e)) {
 
-                Layer *layer = getSelectedLayer();
+                Layer *layer = getInteractionLayer();
 
                 if (layer && layer->isLayerEditable()) {
 
@@ -1684,27 +1840,36 @@
 
         update();
     }
+    
+    if (m_dragMode != UnresolvedDrag) {
+        m_playbackFrameMoveScheduled = false;
+    }
 }
 
 void
-Pane::zoomToRegion(int x0, int y0, int x1, int y1)
+Pane::zoomToRegion(QRect r)
 {
+    int x0 = r.x();
+    int y0 = r.y();
+    int x1 = r.x() + r.width();
+    int y1 = r.y() + r.height();
+
     int w = x1 - x0;
-	    
-    long newStartFrame = getFrameForX(x0);
-	    
-    long visibleFrames = getEndFrame() - getStartFrame();
+        
+    sv_frame_t newStartFrame = getFrameForX(x0);
+        
+    sv_frame_t visibleFrames = getEndFrame() - getStartFrame();
     if (newStartFrame <= -visibleFrames) {
         newStartFrame  = -visibleFrames + 1;
     }
-	    
-    if (newStartFrame >= long(getModelsEndFrame())) {
+        
+    if (newStartFrame >= getModelsEndFrame()) {
         newStartFrame  = getModelsEndFrame() - 1;
     }
-	    
-    float ratio = float(w) / float(width());
+        
+    double ratio = double(w) / double(width());
 //	cerr << "ratio: " << ratio << endl;
-    size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio);
+    int newZoomLevel = (int)nearbyint(m_zoomLevel * ratio);
     if (newZoomLevel < 1) newZoomLevel = 1;
 
 //	cerr << "start: " << m_startFrame << ", level " << m_zoomLevel << endl;
@@ -1712,11 +1877,11 @@
     setStartFrame(newStartFrame);
 
     QString unit;
-    float min, max;
+    double min, max;
     bool log;
     Layer *layer = 0;
-    for (LayerList::const_iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) { 
+    for (LayerList::const_iterator i = m_layerStack.begin();
+         i != m_layerStack.end(); ++i) { 
         if ((*i)->getValueExtents(min, max, log, unit) &&
             (*i)->getDisplayExtents(min, max)) {
             layer = *i;
@@ -1726,15 +1891,15 @@
             
     if (layer) {
         if (log) {
-            min = (min < 0.0) ? -log10f(-min) : (min == 0.0) ? 0.0 : log10f(min);
-            max = (max < 0.0) ? -log10f(-max) : (max == 0.0) ? 0.0 : log10f(max);
+            min = (min < 0.0) ? -log10(-min) : (min == 0.0) ? 0.0 : log10(min);
+            max = (max < 0.0) ? -log10(-max) : (max == 0.0) ? 0.0 : log10(max);
         }
-        float rmin = min + ((max - min) * (height() - y1)) / height();
-        float rmax = min + ((max - min) * (height() - y0)) / height();
+        double rmin = min + ((max - min) * (height() - y1)) / height();
+        double rmax = min + ((max - min) * (height() - y0)) / height();
         cerr << "min: " << min << ", max: " << max << ", y0: " << y0 << ", y1: " << y1 << ", h: " << height() << ", rmin: " << rmin << ", rmax: " << rmax << endl;
         if (log) {
-            rmin = powf(10, rmin);
-            rmax = powf(10, rmax);
+            rmin = pow(10, rmin);
+            rmax = pow(10, rmax);
         }
         cerr << "finally: rmin: " << rmin << ", rmax: " << rmax << " " << unit << endl;
 
@@ -1783,19 +1948,18 @@
     if (m_dragMode == HorizontalDrag ||
         m_dragMode == FreeDrag) {
 
-        long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x());
-
-        size_t newCentreFrame = m_dragCentreFrame;
-	    
+        sv_frame_t frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x());
+        sv_frame_t newCentreFrame = m_dragCentreFrame;
+        
         if (frameOff < 0) {
             newCentreFrame -= frameOff;
-        } else if (newCentreFrame >= size_t(frameOff)) {
+        } else if (newCentreFrame >= frameOff) {
             newCentreFrame -= frameOff;
         } else {
             newCentreFrame = 0;
         }
 
-#ifdef DEBUG_PANE	    
+#ifdef DEBUG_PANE       
         SVDEBUG << "Pane::dragTopLayer: newCentreFrame = " << newCentreFrame <<
             ", models end frame = " << getModelsEndFrame() << endl;
 #endif
@@ -1813,24 +1977,24 @@
     if (m_dragMode == VerticalDrag ||
         m_dragMode == FreeDrag) {
 
-        float vmin = 0.f, vmax = 0.f;
-        float dmin = 0.f, dmax = 0.f;
+        double vmin = 0.f, vmax = 0.f;
+        double dmin = 0.f, dmax = 0.f;
 
         if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) {
 
 //            cerr << "ydiff = " << ydiff << endl;
 
             int ydiff = e->y() - m_clickPos.y();
-            float perpix = (dmax - dmin) / height();
-            float valdiff = ydiff * perpix;
+            double perpix = (dmax - dmin) / height();
+            double valdiff = ydiff * perpix;
 //            cerr << "valdiff = " << valdiff << endl;
 
             if (m_dragMode == UnresolvedDrag && ydiff != 0) {
                 m_dragMode = VerticalDrag;
             }
 
-            float newmin = m_dragStartMinValue + valdiff;
-            float newmax = m_dragStartMinValue + (dmax - dmin) + valdiff;
+            double newmin = m_dragStartMinValue + valdiff;
+            double newmax = m_dragStartMinValue + (dmax - dmin) + valdiff;
             if (newmin < vmin) {
                 newmax += vmin - newmin;
                 newmin += vmin - newmin;
@@ -1862,6 +2026,11 @@
 
     int smallThreshold = 10, bigThreshold = 80;
 
+    if (m_manager) {
+        smallThreshold = m_manager->scalePixelSize(smallThreshold);
+        bigThreshold = m_manager->scalePixelSize(bigThreshold);
+    }
+
 //    SVDEBUG << "Pane::updateDragMode: xdiff = " << xdiff << ", ydiff = "
 //              << ydiff << ", canMoveVertical = " << canMoveVertical << ", drag mode = " << m_dragMode << endl;
 
@@ -1910,13 +2079,14 @@
 void
 Pane::dragExtendSelection(QMouseEvent *e)
 {
-    int mouseFrame = getFrameForX(e->x());
-    size_t resolution = 1;
-    int snapFrameLeft = mouseFrame;
-    int snapFrameRight = mouseFrame;
-	
-    Layer *layer = getSelectedLayer();
-    if (layer && !m_shiftPressed) {
+    sv_frame_t mouseFrame = getFrameForX(e->x());
+    int resolution = 1;
+    sv_frame_t snapFrameLeft = mouseFrame;
+    sv_frame_t snapFrameRight = mouseFrame;
+    
+    Layer *layer = getInteractionLayer();
+    if (layer && !m_shiftPressed &&
+        !qobject_cast<TimeRulerLayer *>(layer)) { // don't snap to secs
         layer->snapToFeatureFrame(this, snapFrameLeft,
                                   resolution, Layer::SnapLeft);
         layer->snapToFeatureFrame(this, snapFrameRight,
@@ -1927,13 +2097,13 @@
 
     if (snapFrameLeft < 0) snapFrameLeft = 0;
     if (snapFrameRight < 0) snapFrameRight = 0;
-	
-    size_t min, max;
-	
-    if (m_selectionStartFrame > size_t(snapFrameLeft)) {
+    
+    sv_frame_t min, max;
+    
+    if (m_selectionStartFrame > snapFrameLeft) {
         min = snapFrameLeft;
         max = m_selectionStartFrame;
-    } else if (size_t(snapFrameRight) > m_selectionStartFrame) {
+    } else if (snapFrameRight > m_selectionStartFrame) {
         min = m_selectionStartFrame;
         max = snapFrameRight;
     } else {
@@ -1941,36 +2111,53 @@
         max = snapFrameRight;
     }
 
+    sv_frame_t end = getModelsEndFrame();
+    if (min > end) min = end;
+    if (max > end) max = end;
+
     if (m_manager) {
-        m_manager->setInProgressSelection(Selection(alignToReference(min),
-                                                    alignToReference(max)),
-                                          !m_resizing && !m_ctrlPressed);
+
+        Selection sel(alignToReference(min), alignToReference(max));
+
+        bool exc;
+        bool same = (m_manager->haveInProgressSelection() &&
+                     m_manager->getInProgressSelection(exc) == sel);
+        
+        m_manager->setInProgressSelection(sel, !m_resizing && !m_ctrlPressed);
+
+        if (!same) {
+            edgeScrollMaybe(e->x());
+        }
     }
 
-    edgeScrollMaybe(e->x());
-
     update();
+
+    if (min != max) {
+        m_playbackFrameMoveScheduled = false;
+    }
 }
 
 void
 Pane::edgeScrollMaybe(int x)
 {
-    int mouseFrame = getFrameForX(x);
+    sv_frame_t mouseFrame = getFrameForX(x);
 
     bool doScroll = false;
     if (!m_manager) doScroll = true;
-    if (!m_manager->isPlaying()) doScroll = true;
+    else if (!m_manager->isPlaying()) doScroll = true;
+
     if (m_followPlay != PlaybackScrollContinuous) doScroll = true;
 
     if (doScroll) {
-        int offset = mouseFrame - getStartFrame();
-        int available = getEndFrame() - getStartFrame();
-        int move = 0;
-        if (offset >= available * 0.95) {
-            move = int(offset - available * 0.95) + 1;
-        } else if (offset <= available * 0.10) {
-             move = int(available * 0.10 - offset) + 1;
-             move = -move;
+        sv_frame_t offset = mouseFrame - getStartFrame();
+        sv_frame_t available = getEndFrame() - getStartFrame();
+        sv_frame_t move = 0;
+        sv_frame_t rightEdge = available - (available / 20);
+        sv_frame_t leftEdge = (available / 10);
+        if (offset >= rightEdge) {
+            move = offset - rightEdge + 1;
+        } else if (offset <= leftEdge) {
+            move = offset - leftEdge - 1;
         }
         if (move != 0) {
             setCentreFrame(m_centreFrame + move);
@@ -1986,7 +2173,7 @@
         return;
     }
 
-//    cerr << "mouseDoubleClickEvent" << endl;
+    cerr << "mouseDoubleClickEvent" << endl;
 
     m_clickPos = e->pos();
     m_clickedInRange = true;
@@ -1994,19 +2181,29 @@
     m_ctrlPressed = (e->modifiers() & Qt::ControlModifier);
     m_altPressed = (e->modifiers() & Qt::AltModifier);
 
+    // cancel any pending move that came from a single click
+    m_playbackFrameMoveScheduled = false;
+
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     bool relocate = (mode == ViewManager::NavigateMode ||
                      (e->buttons() & Qt::MidButton));
 
+    if (mode == ViewManager::SelectMode) {
+        m_clickedInRange = false;
+        if (m_manager) m_manager->clearInProgressSelection();
+        emit doubleClickSelectInvoked(getFrameForX(e->x()));
+        return;
+    }
+
     if (mode == ViewManager::NavigateMode ||
         mode == ViewManager::EditMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    if (layer->editOpen(this, e)) relocate = false;
-	}
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            if (layer->editOpen(this, e)) relocate = false;
+        }
 
     } else if (mode == ViewManager::MeasureMode) {
 
@@ -2017,7 +2214,7 @@
 
     if (relocate) {
 
-        long f = getFrameForX(e->x());
+        sv_frame_t f = getFrameForX(e->x());
 
         setCentreFrame(f);
 
@@ -2025,11 +2222,19 @@
         m_dragStartMinValue = 0;
         m_dragMode = UnresolvedDrag;
 
-        float vmin, vmax, dmin, dmax;
+        double vmin, vmax, dmin, dmax;
         if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) {
             m_dragStartMinValue = dmin;
         }
     }
+    
+    if (mode == ViewManager::NoteEditMode) {
+        std::cerr << "double click in note edit mode" << std::endl;
+        Layer *layer = getInteractionLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->addNote(this, e); 
+        }
+    }
 
     m_clickedInRange = false; // in case mouseReleaseEvent is not properly called
 }
@@ -2059,78 +2264,145 @@
 void
 Pane::wheelEvent(QWheelEvent *e)
 {
-    //cerr << "wheelEvent, delta " << e->delta() << endl;
-
-    int count = e->delta();
-
-    if (count > 0) {
-	if (count >= 120) count /= 120;
-	else count = 1;
-    } 
-
-    if (count < 0) {
-	if (count <= -120) count /= 120;
-	else count = -1;
+    cerr << "wheelEvent, delta " << e->delta() << ", angleDelta " << e->angleDelta().x() << "," << e->angleDelta().y() << ", pixelDelta " << e->pixelDelta().x() << "," << e->pixelDelta().y() << ", modifiers " << e->modifiers() << endl;
+
+    int dx = e->angleDelta().x();
+    int dy = e->angleDelta().y();
+
+    if (dx == 0 && dy == 0) {
+        return;
     }
 
-    if (e->modifiers() & Qt::ControlModifier) {
-
-	// Scroll left or right, rapidly
-
-	if (getStartFrame() < 0 && 
-	    getEndFrame() >= getModelsEndFrame()) return;
-
-	long delta = ((width() / 2) * count * m_zoomLevel);
-
-	if (int(m_centreFrame) < delta) {
-	    setCentreFrame(0);
-	} else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
-	    setCentreFrame(getModelsEndFrame());
-	} else {
-	    setCentreFrame(m_centreFrame - delta);
-	}
-
-    } else if (e->modifiers() & Qt::ShiftModifier) {
+    int d = dy;
+    bool horizontal = false;
+
+    if (abs(dx) > abs(dy)) {
+        d = dx;
+        horizontal = true;
+    } else if (e->modifiers() & Qt::ControlModifier) {
+        // treat a vertical wheel as horizontal
+        horizontal = true;
+    }
+
+    if (e->phase() == Qt::ScrollBegin ||
+        fabs(d) >= 120 ||
+        (d > 0 && m_pendingWheelAngle < 0) ||
+        (d < 0 && m_pendingWheelAngle > 0)) {
+        m_pendingWheelAngle = d;
+    } else {
+        m_pendingWheelAngle += d;
+    }
+
+    if (horizontal && e->pixelDelta().x() != 0) {
+
+        // Have fine pixel information: use it
+
+        wheelHorizontalFine(e->pixelDelta().x(), e->modifiers());
+    
+        m_pendingWheelAngle = 0;
+
+    } else {
+
+        // Coarse wheel information (or vertical zoom, which is
+        // necessarily coarse itself)
+
+        // Sometimes on Linux we're seeing absurdly extreme angles on
+        // the first wheel event -- discard those entirely
+        if (abs(m_pendingWheelAngle) >= 600) {
+            m_pendingWheelAngle = 0;
+            return;
+        }
+        
+        while (abs(m_pendingWheelAngle) >= 120) {
+
+            int sign = (m_pendingWheelAngle < 0 ? -1 : 1);
+
+            if (horizontal) {
+                wheelHorizontal(sign, e->modifiers());
+            } else {
+                wheelVertical(sign, e->modifiers());
+            }
+
+            m_pendingWheelAngle -= sign * 120;
+        }
+    }
+}
+
+void
+Pane::wheelVertical(int sign, Qt::KeyboardModifiers mods)
+{
+    cerr << "wheelVertical: sign = " << sign << endl;
+
+    if (mods & Qt::ShiftModifier) {
+
+        // Pan vertically
+
+        if (m_vpan) {
+            m_vpan->scroll(sign > 0);
+        }
+
+    } else if (mods & Qt::AltModifier) {
 
         // Zoom vertically
 
-        if (m_vpan) {
-            m_vpan->scroll(e->delta() > 0);
+        if (m_vthumb) {
+            m_vthumb->scroll(sign > 0);
         }
 
-    } else if (e->modifiers() & Qt::AltModifier) {
-
-        // Zoom vertically
-
-        if (m_vthumb) {
-            m_vthumb->scroll(e->delta() > 0);
+    } else {
+
+        // Zoom in or out
+
+        int newZoomLevel = m_zoomLevel;
+  
+        if (sign > 0) {
+            if (newZoomLevel <= 2) {
+                newZoomLevel = 1;
+            } else {
+                newZoomLevel = getZoomConstraintBlockSize
+                    (newZoomLevel - 1, ZoomConstraint::RoundDown);
+            }
+        } else { // sign < 0
+            newZoomLevel = getZoomConstraintBlockSize
+                (newZoomLevel + 1, ZoomConstraint::RoundUp);
         }
-
+    
+        if (newZoomLevel != m_zoomLevel) {
+            setZoomLevel(newZoomLevel);
+        }
+    }
+
+    emit paneInteractedWith();
+}
+
+void
+Pane::wheelHorizontal(int sign, Qt::KeyboardModifiers mods)
+{
+    cerr << "wheelHorizontal: sign = " << sign << endl;
+
+    // Scroll left or right, rapidly
+
+    wheelHorizontalFine((width() / 4) * sign, mods);
+}
+
+void
+Pane::wheelHorizontalFine(int pixels, Qt::KeyboardModifiers)
+{
+    cerr << "wheelHorizontalFine: pixels = " << pixels << endl;
+
+    // Scroll left or right by a fixed number of pixels
+
+    if (getStartFrame() < 0 && 
+        getEndFrame() >= getModelsEndFrame()) return;
+
+    int delta = (pixels * m_zoomLevel);
+
+    if (m_centreFrame < delta) {
+        setCentreFrame(0);
+    } else if (m_centreFrame - delta >= getModelsEndFrame()) {
+        setCentreFrame(getModelsEndFrame());
     } else {
-
-	// Zoom in or out
-
-	int newZoomLevel = m_zoomLevel;
-  
-	while (count > 0) {
-	    if (newZoomLevel <= 2) {
-		newZoomLevel = 1;
-		break;
-	    }
-	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, 
-						      ZoomConstraint::RoundDown);
-	    --count;
-	}
-	
-	while (count < 0) {
-	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
-						      ZoomConstraint::RoundUp);
-	    ++count;
-	}
-	
-	if (newZoomLevel != m_zoomLevel) {
-	    setZoomLevel(newZoomLevel);
-	}
+        setCentreFrame(m_centreFrame - delta);
     }
 
     emit paneInteractedWith();
@@ -2147,7 +2419,7 @@
 
     //!!! pull out into function (presumably in View)
     bool haveConstraint = false;
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end();
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end();
          ++i) {
         if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) {
             haveConstraint = true;
@@ -2209,13 +2481,13 @@
 }    
 
 void
-Pane::verticalPannerMoved(float x0, float y0, float w, float h)
+Pane::verticalPannerMoved(float , float y0, float , float h)
 {
-    float vmin, vmax, dmin, dmax;
+    double vmin, vmax, dmin, dmax;
     if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) return;
-    float y1 = y0 + h;
-    float newmax = vmin + ((1.0 - y0) * (vmax - vmin));
-    float newmin = vmin + ((1.0 - y1) * (vmax - vmin));
+    double y1 = y0 + h;
+    double newmax = vmin + ((1.0 - y0) * (vmax - vmin));
+    double newmin = vmin + ((1.0 - y1) * (vmax - vmin));
 //    cerr << "verticalPannerMoved: (" << x0 << "," << y0 << "," << w
 //              << "," << h << ") -> (" << newmin << "," << newmax << ")" << endl;
     setTopLayerDisplayExtents(newmin, newmax);
@@ -2226,7 +2498,7 @@
 {
     if (!m_vpan || !m_manager || !m_manager->getZoomWheelsEnabled()) return;
 
-    float vmin, vmax, dmin, dmax;
+    double vmin, vmax, dmin, dmax;
     QString unit;
     if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax, &unit)
         || vmax == vmin) {
@@ -2236,12 +2508,13 @@
     RangeInputDialog dialog(tr("Enter new range"),
                             tr("New vertical display range, from %1 to %2 %4:")
                             .arg(vmin).arg(vmax).arg(unit),
-                            unit, vmin, vmax, this);
-    dialog.setRange(dmin, dmax);
+                            unit, float(vmin), float(vmax), this);
+    dialog.setRange(float(dmin), float(dmax));
 
     if (dialog.exec() == QDialog::Accepted) {
-        dialog.getRange(dmin, dmax);
-        setTopLayerDisplayExtents(dmin, dmax);
+        float newmin, newmax;
+        dialog.getRange(newmin, newmax);
+        setTopLayerDisplayExtents(newmin, newmax);
         updateVerticalPanner();
     }
 }
@@ -2308,9 +2581,9 @@
 Pane::editSelectionStart(QMouseEvent *e)
 {
     if (!m_identifyFeatures ||
-	!m_manager ||
-	m_manager->getToolMode() != ViewManager::EditMode) {
-	return false;
+        !m_manager ||
+        m_manager->getToolModeFor(this) != ViewManager::EditMode) {
+        return false;
     }
 
     bool closeToLeft, closeToRight;
@@ -2337,41 +2610,41 @@
     if (m_editingSelection.isEmpty()) return false;
 
     int offset = m_mousePos.x() - m_clickPos.x();
-    Layer *layer = getSelectedLayer();
+    Layer *layer = getInteractionLayer();
 
     if (offset == 0 || !layer) {
-	m_editingSelection = Selection();
-	return true;
+        m_editingSelection = Selection();
+        return true;
     }
 
     int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset;
     int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset;
 
-    long f0 = getFrameForX(p0);
-    long f1 = getFrameForX(p1);
+    sv_frame_t f0 = getFrameForX(p0);
+    sv_frame_t f1 = getFrameForX(p1);
 
     Selection newSelection(f0, f1);
     
     if (m_editingSelectionEdge == 0) {
-	
+    
         CommandHistory::getInstance()->startCompoundOperation
             (tr("Drag Selection"), true);
 
-	layer->moveSelection(m_editingSelection, f0);
-	
+        layer->moveSelection(m_editingSelection, f0);
+    
     } else {
-	
+    
         CommandHistory::getInstance()->startCompoundOperation
             (tr("Resize Selection"), true);
 
-	if (m_editingSelectionEdge < 0) {
-	    f1 = m_editingSelection.getEndFrame();
-	} else {
-	    f0 = m_editingSelection.getStartFrame();
-	}
-
-	newSelection = Selection(f0, f1);
-	layer->resizeSelection(m_editingSelection, newSelection);
+        if (m_editingSelectionEdge < 0) {
+            f1 = m_editingSelection.getEndFrame();
+        } else {
+            f0 = m_editingSelection.getStartFrame();
+        }
+
+        newSelection = Selection(f0, f1);
+        layer->resizeSelection(m_editingSelection, newSelection);
     }
     
     m_manager->removeSelection(m_editingSelection);
@@ -2386,7 +2659,7 @@
 void
 Pane::toolModeChanged()
 {
-    ViewManager::ToolMode mode = m_manager->getToolMode();
+    ViewManager::ToolMode mode = m_manager->getToolModeFor(this);
 //    SVDEBUG << "Pane::toolModeChanged(" << mode << ")" << endl;
 
     if (mode == ViewManager::MeasureMode && !m_measureCursor1) {
@@ -2401,33 +2674,38 @@
     switch (mode) {
 
     case ViewManager::NavigateMode:
-	setCursor(Qt::PointingHandCursor);
-	break;
-	
+        setCursor(Qt::PointingHandCursor);
+        break;
+    
     case ViewManager::SelectMode:
-	setCursor(Qt::ArrowCursor);
-	break;
-	
+        setCursor(Qt::ArrowCursor);
+        break;
+    
     case ViewManager::EditMode:
-	setCursor(Qt::UpArrowCursor);
-	break;
-	
+        setCursor(Qt::UpArrowCursor);
+        break;
+    
     case ViewManager::DrawMode:
-	setCursor(Qt::CrossCursor);
-	break;
-	
+        setCursor(Qt::CrossCursor);
+        break;
+    
     case ViewManager::EraseMode:
-	setCursor(Qt::CrossCursor);
-	break;
+        setCursor(Qt::CrossCursor);
+        break;
 
     case ViewManager::MeasureMode:
         if (m_measureCursor1) setCursor(*m_measureCursor1);
-	break;
-
-/*	
+        break;
+
+        // GF: NoteEditMode uses the same default cursor as EditMode, but it will change in a context sensitive manner.
+    case ViewManager::NoteEditMode:
+        setCursor(Qt::UpArrowCursor);
+        break;
+
+/*  
     case ViewManager::TextMode:
-	setCursor(Qt::IBeamCursor);
-	break;
+    setCursor(Qt::IBeamCursor);
+    break;
 */
     }
 }
@@ -2440,7 +2718,7 @@
 }
 
 void
-Pane::viewZoomLevelChanged(View *v, unsigned long z, bool locked)
+Pane::viewZoomLevelChanged(View *v, int z, bool locked)
 {
 //    cerr << "Pane[" << this << "]::zoomLevelChanged (global now "
 //              << (m_manager ? m_manager->getGlobalZoom() : 0) << ")" << endl;
@@ -2511,10 +2789,10 @@
     }
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     bool editable = false;
-    Layer *layer = getSelectedLayer();
+    Layer *layer = getInteractionLayer();
     if (layer && layer->isLayerEditable()) {
         editable = true;
     }
@@ -2563,21 +2841,21 @@
     } else if (mode == ViewManager::DrawMode) {
         
         //!!! could call through to a layer function to find out exact meaning
-	if (editable) {
+        if (editable) {
             help = tr("Click to add a new item in the active layer");
         }
 
     } else if (mode == ViewManager::EraseMode) {
         
         //!!! could call through to a layer function to find out exact meaning
-	if (editable) {
+        if (editable) {
             help = tr("Click to erase an item from the active layer");
         }
         
     } else if (mode == ViewManager::EditMode) {
         
         //!!! could call through to layer
-	if (editable) {
+        if (editable) {
             help = tr("Click and drag an item in the active layer to move it; hold Shift to override initial resistance");
             if (pos) {
                 bool closeToLeft = false, closeToRight = false;
@@ -2621,8 +2899,8 @@
 {
     View::toXml
         (stream, indent,
-	 QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3")
-	 .arg(m_centreLineVisible).arg(height()).arg(extraAttributes));
+     QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3")
+     .arg(m_centreLineVisible).arg(height()).arg(extraAttributes));
 }
 
 
--- a/view/Pane.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/Pane.h	Wed Apr 20 12:06:28 2016 +0100
@@ -48,13 +48,13 @@
     void setCentreLineVisible(bool visible);
     bool getCentreLineVisible() const { return m_centreLineVisible; }
 
-    virtual size_t getFirstVisibleFrame() const;
+    virtual sv_frame_t getFirstVisibleFrame() const;
 
-    virtual size_t getVerticalScaleWidth() const;
+    virtual int getVerticalScaleWidth() const;
 
-    virtual QImage *toNewImage(size_t f0, size_t f1);
+    virtual QImage *toNewImage(sv_frame_t f0, sv_frame_t f1);
     virtual QImage *toNewImage() { return View::toNewImage(); }
-    virtual QSize getImageSize(size_t f0, size_t f1);
+    virtual QSize getImageSize(sv_frame_t f0, sv_frame_t f1);
     virtual QSize getImageSize() { return View::getImageSize(); }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
@@ -62,16 +62,24 @@
 
     static void registerShortcuts(KeyReference &kr);
 
+    enum PaneType {
+        Normal = 0,
+        TonyMain = 1,
+        TonySelection = 2
+    };
+
 signals:
     void paneInteractedWith();
     void rightButtonMenuRequested(QPoint position);
     void dropAccepted(QStringList uriList);
     void dropAccepted(QString text);
+    void doubleClickSelectInvoked(sv_frame_t frame);
+    void regionOutlined(QRect rect);
 
 public slots:
     virtual void toolModeChanged();
     virtual void zoomWheelsEnabledChanged();
-    virtual void viewZoomLevelChanged(View *v, unsigned long z, bool locked);
+    virtual void viewZoomLevelChanged(View *v, int z, bool locked);
     virtual void modelAlignmentCompletionChanged();
 
     virtual void horizontalThumbwheelMoved(int value);
@@ -84,9 +92,14 @@
 
     virtual void propertyContainerSelected(View *, PropertyContainer *pc);
 
+    void zoomToRegion(QRect r);
+
     void mouseEnteredWidget();
     void mouseLeftWidget();
 
+protected slots:
+    void playbackScheduleTimerElapsed();
+
 protected:
     virtual void paintEvent(QPaintEvent *e);
     virtual void mousePressEvent(QMouseEvent *e);
@@ -100,16 +113,21 @@
     virtual void dragEnterEvent(QDragEnterEvent *e);
     virtual void dropEvent(QDropEvent *e);
 
+    void wheelVertical(int sign, Qt::KeyboardModifiers);
+    void wheelHorizontal(int sign, Qt::KeyboardModifiers);
+    void wheelHorizontalFine(int pixels, Qt::KeyboardModifiers);
+
     void drawVerticalScale(QRect r, Layer *, QPainter &);
     void drawFeatureDescription(Layer *, QPainter &);
-    void drawCentreLine(int, QPainter &, bool omitLine);
-    void drawDurationAndRate(QRect, const Model *, int, QPainter &);
+    void drawCentreLine(sv_samplerate_t, QPainter &, bool omitLine);
+    void drawModelTimeExtents(QRect, QPainter &, const Model *);
+    void drawDurationAndRate(QRect, const Model *, sv_samplerate_t, QPainter &);
     void drawWorkTitle(QRect, QPainter &, const Model *);
     void drawLayerNames(QRect, QPainter &);
     void drawEditingSelection(QPainter &);
     void drawAlignmentStatus(QRect, QPainter &, const Model *, bool down);
 
-    virtual bool render(QPainter &paint, int x0, size_t f0, size_t f1);
+    virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1);
 
     Selection getSelectionAt(int x, bool &closeToLeft, bool &closeToRight) const;
 
@@ -122,17 +140,20 @@
     void updateVerticalPanner();
 
     bool canTopLayerMoveVertical();
-    bool getTopLayerDisplayExtents(float &valueMin, float &valueMax,
-                                   float &displayMin, float &displayMax,
+    bool getTopLayerDisplayExtents(double &valueMin, double &valueMax,
+                                   double &displayMin, double &displayMax,
                                    QString *unit = 0);
-    bool setTopLayerDisplayExtents(float displayMin, float displayMax);
+    bool setTopLayerDisplayExtents(double displayMin, double displayMax);
 
     void dragTopLayer(QMouseEvent *e);
     void dragExtendSelection(QMouseEvent *e);
-    void zoomToRegion(int x0, int y0, int x1, int y1);
     void updateContextHelp(const QPoint *pos);
     void edgeScrollMaybe(int x);
 
+    Layer *getTopFlexiNoteLayer();
+
+    void schedulePlaybackFrameMove(sv_frame_t frame);
+
     bool m_identifyFeatures;
     QPoint m_identifyPoint;
     QPoint m_clickPos;
@@ -146,14 +167,16 @@
     bool m_resizing;
     bool m_editing;
     bool m_releasing;
-    size_t m_dragCentreFrame;
-    float m_dragStartMinValue;
+    sv_frame_t m_dragCentreFrame;
+    double m_dragStartMinValue;
     bool m_centreLineVisible;
-    size_t m_selectionStartFrame;
+    sv_frame_t m_selectionStartFrame;
     Selection m_editingSelection;
     int m_editingSelectionEdge;
     mutable int m_scaleWidth;
 
+    int m_pendingWheelAngle;
+
     enum DragMode {
         UnresolvedDrag,
         VerticalDrag,
@@ -178,6 +201,9 @@
 
     bool m_mouseInWidget;
 
+    bool m_playbackFrameMoveScheduled;
+    sv_frame_t m_playbackFrameMoveTo;
+
     static QCursor *m_measureCursor1;
     static QCursor *m_measureCursor2;
 };
--- a/view/PaneStack.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/PaneStack.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -39,6 +39,7 @@
 PaneStack::PaneStack(QWidget *parent, ViewManager *viewManager) :
     QFrame(parent),
     m_currentPane(0),
+    m_showAccessories(true),
     m_splitter(new QSplitter),
     m_propertyStackStack(new QStackedWidget),
     m_viewManager(viewManager),
@@ -60,6 +61,12 @@
     setLayout(layout);
 }
 
+void
+PaneStack::setShowPaneAccessories(bool show)
+{
+    m_showAccessories = show;
+}
+
 Pane *
 PaneStack::addPane(bool suppressPropertyBox)
 {
@@ -79,6 +86,7 @@
     xButton->setIcon(IconLoader().load("cross"));
     xButton->setFixedSize(QSize(16, 16));
     xButton->setFlat(true);
+    xButton->setVisible(m_showAccessories);
     layout->addWidget(xButton, 0, 0);
     connect(xButton, SIGNAL(clicked()), this, SLOT(paneDeleteButtonClicked()));
 
@@ -88,12 +96,11 @@
     layout->setRowStretch(1, 20);
     currentIndicator->setMinimumWidth(8);
     currentIndicator->setScaledContents(true);
+    currentIndicator->setVisible(m_showAccessories);
 
-    long initialCentreFrame = -1;
-    for (int i = 0; i < m_panes.size(); ++i) {
-        long f = m_panes[i].pane->getCentreFrame();
-        initialCentreFrame = f;
-        break;
+    sv_frame_t initialCentreFrame = -1;
+    if (!m_panes.empty()) {
+        initialCentreFrame = m_panes[0].pane->getCentreFrame();
     }
 
     Pane *pane = new Pane(frame);
@@ -149,6 +156,8 @@
             this, SLOT(paneDropAccepted(QStringList)));
     connect(pane, SIGNAL(dropAccepted(QString)),
             this, SLOT(paneDropAccepted(QString)));
+    connect(pane, SIGNAL(doubleClickSelectInvoked(sv_frame_t)),
+            this, SIGNAL(doubleClickSelectInvoked(sv_frame_t)));
 
     emit paneAdded(pane);
     emit paneAdded();
@@ -209,7 +218,7 @@
 Pane *
 PaneStack::getPane(int n)
 {
-    if (n < m_panes.size()) {
+    if (n < (int)m_panes.size()) {
         return m_panes[n].pane;
     } else {
         return 0;
@@ -236,11 +245,16 @@
 void
 PaneStack::deletePane(Pane *pane)
 {
+    cerr << "PaneStack::deletePane(" << pane << ")" << endl;
+
     std::vector<PaneRec>::iterator i;
     bool found = false;
 
+    QWidget *stack = 0;
+
     for (i = m_panes.begin(); i != m_panes.end(); ++i) {
 	if (i->pane == pane) {
+            stack = i->propertyStack;
 	    m_panes.erase(i);
 	    found = true;
 	    break;
@@ -251,6 +265,7 @@
 
 	for (i = m_hiddenPanes.begin(); i != m_hiddenPanes.end(); ++i) {
 	    if (i->pane == pane) {
+                stack = i->propertyStack;
 		m_hiddenPanes.erase(i);
 		found = true;
 		break;
@@ -265,6 +280,18 @@
 
     emit paneAboutToBeDeleted(pane);
 
+    cerr << "PaneStack::deletePane: about to delete parent " << pane->parent() << " of pane " << pane << endl;
+
+    // The property stack associated with the parent was initially
+    // created with the same parent as it, so it would be deleted when
+    // we delete the pane's parent in a moment -- but it may have been
+    // reparented depending on the layout. We'd better delete it
+    // separately first. (This fixes a crash on opening a new layer
+    // with a new unit type in it, when a long-defunct property box
+    // could be signalled from the unit database to tell it that a new
+    // unit had appeared.)
+    delete stack;
+
     delete pane->parent();
 
     if (m_currentPane == pane) {
@@ -288,21 +315,21 @@
     bool multi = (getPaneCount() > 1);
     for (std::vector<PaneRec>::iterator i = m_panes.begin();
          i != m_panes.end(); ++i) {
-        i->xButton->setVisible(multi);
-        i->currentIndicator->setVisible(multi);
+        i->xButton->setVisible(multi && m_showAccessories);
+        i->currentIndicator->setVisible(multi && m_showAccessories);
     }
 }
 
 int
 PaneStack::getPaneCount() const
 {
-    return m_panes.size();
+    return int(m_panes.size());
 }
 
 int
 PaneStack::getHiddenPaneCount() const
 {
-    return m_hiddenPanes.size();
+    return int(m_hiddenPanes.size());
 }
 
 void
@@ -503,7 +530,7 @@
 
     if (m_propertyStackMinWidth > 0) maxMinWidth = m_propertyStackMinWidth;
 
-    for (size_t i = 0; i < m_panes.size(); ++i) {
+    for (int i = 0; i < (int)m_panes.size(); ++i) {
 	if (!m_panes[i].propertyStack) continue;
 #ifdef DEBUG_PANE_STACK
 	SVDEBUG << "PaneStack::sizePropertyStacks: " << i << ": min " 
@@ -525,7 +552,7 @@
 
     m_propertyStackStack->setMaximumWidth(setWidth + 10);
 
-    for (size_t i = 0; i < m_panes.size(); ++i) {
+    for (int i = 0; i < (int)m_panes.size(); ++i) {
 	if (!m_panes[i].propertyStack) continue;
 	m_panes[i].propertyStack->setMinimumWidth(setWidth);
     }
@@ -552,7 +579,7 @@
 PaneStack::paneDeleteButtonClicked()
 {
     QObject *s = sender();
-    for (size_t i = 0; i < m_panes.size(); ++i) {
+    for (int i = 0; i < (int)m_panes.size(); ++i) {
 	if (m_panes[i].xButton == s) {
             emit paneDeleteButtonClicked(m_panes[i].pane);
         }
@@ -564,7 +591,7 @@
 {
     QObject *s = sender();
 
-    for (size_t i = 0; i < m_panes.size(); ++i) {
+    for (int i = 0; i < (int)m_panes.size(); ++i) {
 	if (m_panes[i].currentIndicator == s) {
             setCurrentPane(m_panes[i].pane);
             return;
@@ -580,24 +607,44 @@
 
     int count = sizes.size();
 
-    int total = 0;
+    int fixed = 0, variable = 0, total = 0;
+    int varicount = 0;
+
     for (int i = 0; i < count; ++i) {
         total += sizes[i];
     }
 
+    variable = total;
+
+    for (int i = 0; i < count; ++i) {
+        int minh = m_panes[i].pane->minimumSize().height();
+        if (minh == m_panes[i].pane->maximumSize().height()) {
+            fixed += minh;
+            variable -= minh;
+        } else {
+            varicount++;
+        }
+    }
+
     if (total == 0) return;
 
     sizes.clear();
 
-    int each = total / count;
+    int each = (varicount > 0 ? (variable / varicount) : 0);
     int remaining = total;
 
     for (int i = 0; i < count; ++i) {
         if (i == count - 1) {
             sizes.push_back(remaining);
         } else {
-            sizes.push_back(each);
-            remaining -= each;
+            int minh = m_panes[i].pane->minimumSize().height();
+            if (minh == m_panes[i].pane->maximumSize().height()) {
+                sizes.push_back(minh);
+                remaining -= minh;
+            } else {
+                sizes.push_back(each);
+                remaining -= each;
+            }
         }
     }
 
@@ -612,4 +659,3 @@
     m_splitter->setSizes(sizes);
 }
 
-
--- a/view/PaneStack.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/PaneStack.h	Wed Apr 20 12:06:28 2016 +0100
@@ -20,6 +20,8 @@
 
 #include <map>
 
+#include "base/BaseTypes.h"
+
 class QWidget;
 class QLabel;
 class QStackedWidget;
@@ -68,6 +70,8 @@
     void setLayoutStyle(LayoutStyle style);
 
     void setPropertyStackMinWidth(int mw);
+    
+    void setShowPaneAccessories(bool show); // current indicator, close button
 
     void sizePanesEqually();
 
@@ -91,6 +95,8 @@
 
     void paneDeleteButtonClicked(Pane *pane);
 
+    void doubleClickSelectInvoked(sv_frame_t frame);
+
 public slots:
     void propertyContainerAdded(PropertyContainer *);
     void propertyContainerRemoved(PropertyContainer *);
@@ -119,6 +125,8 @@
     std::vector<PaneRec> m_panes;
     std::vector<PaneRec> m_hiddenPanes;
 
+    bool m_showAccessories;
+
     QSplitter *m_splitter;
     QStackedWidget *m_propertyStackStack;
 
--- a/view/View.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/View.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -20,12 +20,15 @@
 #include "base/Profiler.h"
 #include "base/Pitch.h"
 #include "base/Preferences.h"
+#include "ViewProxy.h"
 
 #include "layer/TimeRulerLayer.h"
 #include "layer/SingleColourLayer.h"
 #include "data/model/PowerOfSqrtTwoZoomConstraint.h"
 #include "data/model/RangeSummarisableTimeValueModel.h"
 
+#include "widgets/IconLoader.h"
+
 #include <QPainter>
 #include <QPaintEvent>
 #include <QRect>
@@ -34,6 +37,8 @@
 #include <QTextStream>
 #include <QFont>
 #include <QMessageBox>
+#include <QPushButton>
+#include <QSettings>
 
 #include <iostream>
 #include <cassert>
@@ -41,21 +46,22 @@
 
 #include <unistd.h>
 
+//#define DEBUG_VIEW 1
 //#define DEBUG_VIEW_WIDGET_PAINT 1
 
-
-
-
 View::View(QWidget *w, bool showProgress) :
     QFrame(w),
+    m_id(getNextId()),
     m_centreFrame(0),
     m_zoomLevel(1024),
     m_followPan(true),
     m_followZoom(true),
-    m_followPlay(PlaybackScrollPage),
+    m_followPlay(PlaybackScrollPageWithCentre),
+    m_followPlayIsDetached(false),
     m_playPointerFrame(0),
     m_showProgress(showProgress),
     m_cache(0),
+    m_buffer(0),
     m_cacheCentreFrame(0),
     m_cacheZoomLevel(1024),
     m_selectionCached(false),
@@ -64,12 +70,12 @@
     m_manager(0),
     m_propertyContainer(new ViewPropertyContainer(this))
 {
-    SVDEBUG << "View::View(" << this << ")" << endl;
+//    cerr << "View::View(" << this << ")" << endl;
 }
 
 View::~View()
 {
-//    SVDEBUG << "View::~View(" << this << ")" << endl;
+//    cerr << "View::~View(" << this << ")" << endl;
 
     m_deleting = true;
     delete m_propertyContainer;
@@ -113,8 +119,12 @@
     if (name == "Follow Playback") {
 	if (min) *min = 0;
 	if (max) *max = 2;
-        if (deflt) *deflt = int(PlaybackScrollPage);
-	return int(m_followPlay);
+        if (deflt) *deflt = int(PlaybackScrollPageWithCentre);
+        switch (m_followPlay) {
+        case PlaybackScrollContinuous: return 0;
+        case PlaybackScrollPageWithCentre: case PlaybackScrollPage: return 1;
+        case PlaybackIgnore: return 2;
+        }
     }
     if (min) *min = 0;
     if (max) *max = 0;
@@ -148,43 +158,43 @@
 	switch (value) {
 	default:
 	case 0: setPlaybackFollow(PlaybackScrollContinuous); break;
-	case 1: setPlaybackFollow(PlaybackScrollPage); break;
+	case 1: setPlaybackFollow(PlaybackScrollPageWithCentre); break;
 	case 2: setPlaybackFollow(PlaybackIgnore); break;
 	}
     }
 }
 
-size_t
+int
 View::getPropertyContainerCount() const
 {
-    return m_layers.size() + 1; // the 1 is for me
+    return int(m_fixedOrderLayers.size()) + 1; // the 1 is for me
 }
 
 const PropertyContainer *
-View::getPropertyContainer(size_t i) const
+View::getPropertyContainer(int i) const
 {
     return (const PropertyContainer *)(((View *)this)->
 				       getPropertyContainer(i));
 }
 
 PropertyContainer *
-View::getPropertyContainer(size_t i)
+View::getPropertyContainer(int i)
 {
     if (i == 0) return m_propertyContainer;
-    return m_layers[i-1];
+    return m_fixedOrderLayers[i-1];
 }
 
 bool
-View::getValueExtents(QString unit, float &min, float &max, bool &log) const
+View::getValueExtents(QString unit, double &min, double &max, bool &log) const
 {
     bool have = false;
 
-    for (LayerList::const_iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) { 
+    for (LayerList::const_iterator i = m_layerStack.begin();
+         i != m_layerStack.end(); ++i) { 
 
         QString layerUnit;
-        float layerMin = 0.0, layerMax = 0.0;
-        float displayMin = 0.0, displayMax = 0.0;
+        double layerMin = 0.0, layerMax = 0.0;
+        double displayMin = 0.0, displayMax = 0.0;
         bool layerLog = false;
 
         if ((*i)->getValueExtents(layerMin, layerMax, layerLog, layerUnit) &&
@@ -216,8 +226,8 @@
 {
     std::map<int, Layer *> sortedLayers;
 
-    for (LayerList::const_iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) { 
+    for (LayerList::const_iterator i = m_layerStack.begin();
+         i != m_layerStack.end(); ++i) { 
         if ((*i)->needsTextLabelHeight()) {
             sortedLayers[getObjectExportId(*i)] = *i;
         }
@@ -252,17 +262,17 @@
 
     Layer *selectedLayer = 0;
 
-    for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 	if (*i == pc) {
 	    selectedLayer = *i;
-	    m_layers.erase(i);
+	    m_layerStack.erase(i);
 	    break;
 	}
     }
 
     if (selectedLayer) {
 	m_haveSelectedLayer = true;
-	m_layers.push_back(selectedLayer);
+	m_layerStack.push_back(selectedLayer);
 	update();
     } else {
 	m_haveSelectedLayer = false;
@@ -291,36 +301,36 @@
     // subclass might override this
 }
 
-long
+sv_frame_t
 View::getStartFrame() const
 {
     return getFrameForX(0);
 }
 
-size_t
+sv_frame_t
 View::getEndFrame() const
 {
     return getFrameForX(width()) - 1;
 }
 
 void
-View::setStartFrame(long f)
+View::setStartFrame(sv_frame_t f)
 {
     setCentreFrame(f + m_zoomLevel * (width() / 2));
 }
 
 bool
-View::setCentreFrame(size_t f, bool e)
+View::setCentreFrame(sv_frame_t f, bool e)
 {
     bool changeVisible = false;
 
     if (m_centreFrame != f) {
 
-	int formerPixel = m_centreFrame / m_zoomLevel;
+	int formerPixel = int(m_centreFrame / m_zoomLevel);
 
 	m_centreFrame = f;
 
-	int newPixel = m_centreFrame / m_zoomLevel;
+	int newPixel = int(m_centreFrame / m_zoomLevel);
 	
 	if (newPixel != formerPixel) {
 
@@ -333,8 +343,8 @@
 	}
 
 	if (e) {
-            size_t rf = alignToReference(f);
-#ifdef DEBUG_VIEW_WIDGET_PAINT
+            sv_frame_t rf = alignToReference(f);
+#ifdef DEBUG_VIEW
             cerr << "View[" << this << "]::setCentreFrame(" << f
                       << "): emitting centreFrameChanged("
                       << rf << ")" << endl;
@@ -347,29 +357,31 @@
 }
 
 int
-View::getXForFrame(long frame) const
+View::getXForFrame(sv_frame_t frame) const
 {
-    return (frame - getStartFrame()) / m_zoomLevel;
+    return int((frame - getStartFrame()) / m_zoomLevel);
 }
 
-long
+sv_frame_t
 View::getFrameForX(int x) const
 {
-    long z = (long)m_zoomLevel;
-    long frame = m_centreFrame - (width()/2) * z;
+    sv_frame_t z = m_zoomLevel; // nb not just int, or multiplication may overflow
+    sv_frame_t frame = m_centreFrame - (width()/2) * z;
+
+    frame = (frame / z) * z; // this is start frame
+    frame = frame + x * z;
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-    SVDEBUG << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl;
+    cerr << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl;
 #endif
 
-    frame = (frame / z) * z; // this is start frame
-    return frame + x * z;
+    return frame;
 }
 
-float
-View::getYForFrequency(float frequency,
-		       float minf,
-		       float maxf, 
+double
+View::getYForFrequency(double frequency,
+		       double minf,
+		       double maxf, 
 		       bool logarithmic) const
 {
     Profiler profiler("View::getYForFrequency");
@@ -378,20 +390,20 @@
 
     if (logarithmic) {
 
-	static float lastminf = 0.0, lastmaxf = 0.0;
-	static float logminf = 0.0, logmaxf = 0.0;
+	static double lastminf = 0.0, lastmaxf = 0.0;
+	static double logminf = 0.0, logmaxf = 0.0;
 
 	if (lastminf != minf) {
 	    lastminf = (minf == 0.0 ? 1.0 : minf);
-	    logminf = log10f(minf);
+	    logminf = log10(minf);
 	}
 	if (lastmaxf != maxf) {
 	    lastmaxf = (maxf < lastminf ? lastminf : maxf);
-	    logmaxf = log10f(maxf);
+	    logmaxf = log10(maxf);
 	}
 
 	if (logminf == logmaxf) return 0;
-	return h - (h * (log10f(frequency) - logminf)) / (logmaxf - logminf);
+	return h - (h * (log10(frequency) - logminf)) / (logmaxf - logminf);
 
     } else {
 	
@@ -400,30 +412,30 @@
     }
 }
 
-float
+double
 View::getFrequencyForY(int y,
-		       float minf,
-		       float maxf,
+		       double minf,
+		       double maxf,
 		       bool logarithmic) const
 {
     int h = height();
 
     if (logarithmic) {
 
-	static float lastminf = 0.0, lastmaxf = 0.0;
-	static float logminf = 0.0, logmaxf = 0.0;
+	static double lastminf = 0.0, lastmaxf = 0.0;
+	static double logminf = 0.0, logmaxf = 0.0;
 
 	if (lastminf != minf) {
 	    lastminf = (minf == 0.0 ? 1.0 : minf);
-	    logminf = log10f(minf);
+	    logminf = log10(minf);
 	}
 	if (lastmaxf != maxf) {
 	    lastmaxf = (maxf < lastminf ? lastminf : maxf);
-	    logmaxf = log10f(maxf);
+	    logmaxf = log10(maxf);
 	}
 
 	if (logminf == logmaxf) return 0;
-	return pow(10.f, logminf + ((logmaxf - logminf) * (h - y)) / h);
+	return pow(10.0, logminf + ((logmaxf - logminf) * (h - y)) / h);
 
     } else {
 
@@ -441,9 +453,30 @@
     return m_zoomLevel;
 }
 
+int
+View::effectiveDevicePixelRatio() const
+{
+#ifdef Q_OS_MAC
+    int dpratio = devicePixelRatio();
+    if (dpratio > 1) {
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        if (!settings.value("scaledHiDpi", true).toBool()) {
+            dpratio = 1;
+        }
+        settings.endGroup();
+    }
+    return dpratio;
+#else
+    return 1;
+#endif
+}
+
 void
-View::setZoomLevel(size_t z)
+View::setZoomLevel(int z)
 {
+    int dpratio = effectiveDevicePixelRatio();
+    if (z < dpratio) return;
     if (z < 1) z = 1;
     if (m_zoomLevel != int(z)) {
 	m_zoomLevel = z;
@@ -461,8 +494,8 @@
     Layer::ColourSignificance maxSignificance = Layer::ColourAbsent;
     bool mostSignificantHasDarkBackground = false;
     
-    for (LayerList::const_iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin();
+         i != m_layerStack.end(); ++i) {
 
         Layer::ColourSignificance s = (*i)->getLayerColourSignificance();
         bool light = (*i)->hasLightBackground();
@@ -516,11 +549,6 @@
     else return Qt::white;
 }
 
-View::LayerProgressBar::LayerProgressBar(QWidget *parent) :
-    QProgressBar(parent)
-{
-}
-
 void
 View::addLayer(Layer *layer)
 {
@@ -530,7 +558,8 @@
     SingleColourLayer *scl = dynamic_cast<SingleColourLayer *>(layer);
     if (scl) scl->setDefaultColourFor(this);
 
-    m_layers.push_back(layer);
+    m_fixedOrderLayers.push_back(layer);
+    m_layerStack.push_back(layer);
 
     QProgressBar *pb = new QProgressBar(this);
     pb->setMinimum(0);
@@ -538,7 +567,14 @@
     pb->setFixedWidth(80);
     pb->setTextVisible(false);
 
+    QPushButton *cancel = new QPushButton(this);
+    cancel->setIcon(IconLoader().load("fileclose"));
+    cancel->setFlat(true);
+    cancel->setFixedSize(QSize(20, 20));
+    connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked()));
+    
     ProgressBarRec pbr;
+    pbr.cancel = cancel;
     pbr.bar = pb;
     pbr.lastCheck = 0;
     pbr.checkTimer = new QTimer();
@@ -551,6 +587,8 @@
     int fs = Preferences::getInstance()->getViewFontSize();
     f.setPointSize(std::min(fs, int(ceil(fs * 0.85))));
 
+    cancel->hide();
+
     pb->setFont(f);
     pb->hide();
     
@@ -568,8 +606,8 @@
 	    this,    SLOT(modelCompletionChanged()));
     connect(layer, SIGNAL(modelAlignmentCompletionChanged()),
 	    this,    SLOT(modelAlignmentCompletionChanged()));
-    connect(layer, SIGNAL(modelChanged(size_t, size_t)),
-	    this,    SLOT(modelChanged(size_t, size_t)));
+    connect(layer, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+	    this,    SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
     connect(layer, SIGNAL(modelReplaced()),
 	    this,    SLOT(modelReplaced()));
 
@@ -588,11 +626,23 @@
     delete m_cache;
     m_cache = 0;
 
-    for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::iterator i = m_fixedOrderLayers.begin();
+         i != m_fixedOrderLayers.end();
+         ++i) {
 	if (*i == layer) {
-	    m_layers.erase(i);
+	    m_fixedOrderLayers.erase(i);
+            break;
+        }
+    }
+
+    for (LayerList::iterator i = m_layerStack.begin(); 
+         i != m_layerStack.end();
+         ++i) {
+	if (*i == layer) {
+	    m_layerStack.erase(i);
 	    if (m_progressBars.find(layer) != m_progressBars.end()) {
 		delete m_progressBars[layer].bar;
+                delete m_progressBars[layer].cancel;
 		delete m_progressBars[layer].checkTimer;
 		m_progressBars.erase(layer);
 	    }
@@ -612,8 +662,8 @@
                this,    SLOT(modelCompletionChanged()));
     disconnect(layer, SIGNAL(modelAlignmentCompletionChanged()),
                this,    SLOT(modelAlignmentCompletionChanged()));
-    disconnect(layer, SIGNAL(modelChanged(size_t, size_t)),
-               this,    SLOT(modelChanged(size_t, size_t)));
+    disconnect(layer, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+               this,    SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
     disconnect(layer, SIGNAL(modelReplaced()),
                this,    SLOT(modelReplaced()));
 
@@ -623,10 +673,36 @@
 }
 
 Layer *
+View::getInteractionLayer()
+{
+    Layer *sl = getSelectedLayer();
+    if (sl && !(sl->isLayerDormant(this))) {
+        return sl;
+    }
+    if (!m_layerStack.empty()) {
+        int n = getLayerCount();
+        while (n > 0) {
+            --n;
+            Layer *layer = getLayer(n);
+            if (!(layer->isLayerDormant(this))) {
+                return layer;
+            }
+        }
+    }
+    return 0;
+}
+
+const Layer *
+View::getInteractionLayer() const
+{
+    return const_cast<const Layer *>(const_cast<View *>(this)->getInteractionLayer());
+}
+
+Layer *
 View::getSelectedLayer()
 {
-    if (m_haveSelectedLayer && !m_layers.empty()) {
-	return getLayer(getLayerCount() - 1);
+    if (m_haveSelectedLayer && !m_layerStack.empty()) {
+        return getLayer(getLayerCount() - 1);
     } else {
 	return 0;
     }
@@ -642,29 +718,29 @@
 View::setViewManager(ViewManager *manager)
 {
     if (m_manager) {
-	m_manager->disconnect(this, SLOT(globalCentreFrameChanged(unsigned long)));
-	m_manager->disconnect(this, SLOT(viewCentreFrameChanged(View *, unsigned long)));
-	m_manager->disconnect(this, SLOT(viewManagerPlaybackFrameChanged(unsigned long)));
-	m_manager->disconnect(this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool)));
+	m_manager->disconnect(this, SLOT(globalCentreFrameChanged(sv_frame_t)));
+	m_manager->disconnect(this, SLOT(viewCentreFrameChanged(View *, sv_frame_t)));
+	m_manager->disconnect(this, SLOT(viewManagerPlaybackFrameChanged(sv_frame_t)));
+	m_manager->disconnect(this, SLOT(viewZoomLevelChanged(View *, int, bool)));
         m_manager->disconnect(this, SLOT(toolModeChanged()));
         m_manager->disconnect(this, SLOT(selectionChanged()));
         m_manager->disconnect(this, SLOT(overlayModeChanged()));
         m_manager->disconnect(this, SLOT(zoomWheelsEnabledChanged()));
-        disconnect(m_manager, SLOT(viewCentreFrameChanged(unsigned long, bool, PlaybackFollowMode)));
-	disconnect(m_manager, SLOT(zoomLevelChanged(unsigned long, bool)));
+        disconnect(m_manager, SLOT(viewCentreFrameChanged(sv_frame_t, bool, PlaybackFollowMode)));
+	disconnect(m_manager, SLOT(zoomLevelChanged(int, bool)));
     }
 
     m_manager = manager;
 
-    connect(m_manager, SIGNAL(globalCentreFrameChanged(unsigned long)),
-	    this, SLOT(globalCentreFrameChanged(unsigned long)));
-    connect(m_manager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)),
-	    this, SLOT(viewCentreFrameChanged(View *, unsigned long)));
-    connect(m_manager, SIGNAL(playbackFrameChanged(unsigned long)),
-	    this, SLOT(viewManagerPlaybackFrameChanged(unsigned long)));
-
-    connect(m_manager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)),
-	    this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool)));
+    connect(m_manager, SIGNAL(globalCentreFrameChanged(sv_frame_t)),
+	    this, SLOT(globalCentreFrameChanged(sv_frame_t)));
+    connect(m_manager, SIGNAL(viewCentreFrameChanged(View *, sv_frame_t)),
+	    this, SLOT(viewCentreFrameChanged(View *, sv_frame_t)));
+    connect(m_manager, SIGNAL(playbackFrameChanged(sv_frame_t)),
+	    this, SLOT(viewManagerPlaybackFrameChanged(sv_frame_t)));
+
+    connect(m_manager, SIGNAL(viewZoomLevelChanged(View *, int, bool)),
+	    this, SLOT(viewZoomLevelChanged(View *, int, bool)));
 
     connect(m_manager, SIGNAL(toolModeChanged()),
 	    this, SLOT(toolModeChanged()));
@@ -679,25 +755,30 @@
     connect(m_manager, SIGNAL(zoomWheelsEnabledChanged()),
             this, SLOT(zoomWheelsEnabledChanged()));
 
-    connect(this, SIGNAL(centreFrameChanged(unsigned long, bool,
+    connect(this, SIGNAL(centreFrameChanged(sv_frame_t, bool,
                                             PlaybackFollowMode)),
-            m_manager, SLOT(viewCentreFrameChanged(unsigned long, bool,
+            m_manager, SLOT(viewCentreFrameChanged(sv_frame_t, bool,
                                                    PlaybackFollowMode)));
 
-    connect(this, SIGNAL(zoomLevelChanged(unsigned long, bool)),
-	    m_manager, SLOT(viewZoomLevelChanged(unsigned long, bool)));
-
-//    setCentreFrame(m_manager->getViewInitialCentreFrame());
-
-    if (m_followPlay == PlaybackScrollPage) {
-//        SVDEBUG << "View::setViewManager: setting centre frame to global centre frame: " << m_manager->getGlobalCentreFrame() << endl;
+    connect(this, SIGNAL(zoomLevelChanged(int, bool)),
+	    m_manager, SLOT(viewZoomLevelChanged(int, bool)));
+
+    switch (m_followPlay) {
+        
+    case PlaybackScrollPage:
+    case PlaybackScrollPageWithCentre:
         setCentreFrame(m_manager->getGlobalCentreFrame(), false);
-    } else if (m_followPlay == PlaybackScrollContinuous) {
-//        SVDEBUG << "View::setViewManager: setting centre frame to playback frame: " << m_manager->getPlaybackFrame() << endl;
+        break;
+
+    case PlaybackScrollContinuous:
         setCentreFrame(m_manager->getPlaybackFrame(), false);
-    } else if (m_followPan) {
-//        SVDEBUG << "View::setViewManager: (follow pan) setting centre frame to global centre frame: " << m_manager->getGlobalCentreFrame() << endl;
-        setCentreFrame(m_manager->getGlobalCentreFrame(), false);
+        break;
+
+    case PlaybackIgnore:
+        if (m_followPan) {
+            setCentreFrame(m_manager->getGlobalCentreFrame(), false);
+        }
+        break;
     }
 
     if (m_followZoom) setZoomLevel(m_manager->getGlobalZoom());
@@ -708,7 +789,7 @@
 }
 
 void
-View::setViewManager(ViewManager *vm, long initialCentreFrame)
+View::setViewManager(ViewManager *vm, sv_frame_t initialCentreFrame)
 {
     setViewManager(vm);
     setCentreFrame(initialCentreFrame, false);
@@ -822,18 +903,18 @@
 }
 
 void
-View::modelChanged(size_t startFrame, size_t endFrame)
+View::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
 {
     QObject *obj = sender();
 
-    long myStartFrame = getStartFrame();
-    size_t myEndFrame = getEndFrame();
+    sv_frame_t myStartFrame = getStartFrame();
+    sv_frame_t myEndFrame = getEndFrame();
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-    cerr << "View(" << this << ")::modelChanged(" << startFrame << "," << endFrame << ") [me " << myStartFrame << "," << myEndFrame << "]" << endl;
+    cerr << "View(" << this << ")::modelChangedWithin(" << startFrame << "," << endFrame << ") [me " << myStartFrame << "," << myEndFrame << "]" << endl;
 #endif
 
-    if (myStartFrame > 0 && endFrame < size_t(myStartFrame)) {
+    if (myStartFrame > 0 && endFrame < myStartFrame) {
 	checkProgress(obj);
 	return;
     }
@@ -862,7 +943,7 @@
 	m_cache = 0;
     }
 
-    if (long(startFrame) < myStartFrame) startFrame = myStartFrame;
+    if (startFrame < myStartFrame) startFrame = myStartFrame;
     if (endFrame > myEndFrame) endFrame = myEndFrame;
 
     checkProgress(obj);
@@ -940,11 +1021,11 @@
 }
 
 void
-View::globalCentreFrameChanged(unsigned long rf)
+View::globalCentreFrameChanged(sv_frame_t rf)
 {
     if (m_followPan) {
-        size_t f = alignFromReference(rf);
-#ifdef DEBUG_VIEW_WIDGET_PAINT
+        sv_frame_t f = alignFromReference(rf);
+#ifdef DEBUG_VIEW
         cerr << "View[" << this << "]::globalCentreFrameChanged(" << rf
                   << "): setting centre frame to " << f << endl;
 #endif
@@ -953,30 +1034,42 @@
 }
 
 void
-View::viewCentreFrameChanged(View *, unsigned long )
+View::viewCentreFrameChanged(View *, sv_frame_t )
 {
     // We do nothing with this, but a subclass might
 }
 
 void
-View::viewManagerPlaybackFrameChanged(unsigned long f)
+View::viewManagerPlaybackFrameChanged(sv_frame_t f)
 {
     if (m_manager) {
 	if (sender() != m_manager) return;
     }
 
+#ifdef DEBUG_VIEW        
+    cerr << "View::viewManagerPlaybackFrameChanged(" << f << ")" << endl;
+#endif
+
     f = getAlignedPlaybackFrame();
 
+#ifdef DEBUG_VIEW
+    cerr << " -> aligned frame = " << f << endl;
+#endif
+
     movePlayPointer(f);
 }
 
 void
-View::movePlayPointer(unsigned long newFrame)
+View::movePlayPointer(sv_frame_t newFrame)
 {
+#ifdef DEBUG_VIEW
+    cerr << "View(" << this << ")::movePlayPointer(" << newFrame << ")" << endl;
+#endif
+
     if (m_playPointerFrame == newFrame) return;
     bool visibleChange =
         (getXForFrame(m_playPointerFrame) != getXForFrame(newFrame));
-    size_t oldPlayPointerFrame = m_playPointerFrame;
+    sv_frame_t oldPlayPointerFrame = m_playPointerFrame;
     m_playPointerFrame = newFrame;
     if (!visibleChange) return;
 
@@ -984,6 +1077,12 @@
         ((QApplication::mouseButtons() != Qt::NoButton) ||
          (QApplication::keyboardModifiers() & Qt::AltModifier));
 
+    bool pointerInVisibleArea =
+	long(m_playPointerFrame) >= getStartFrame() &&
+        (m_playPointerFrame < getEndFrame() ||
+         // include old pointer location so we know to refresh when moving out
+         oldPlayPointerFrame < getEndFrame());
+
     switch (m_followPlay) {
 
     case PlaybackScrollContinuous:
@@ -993,59 +1092,79 @@
 	break;
 
     case PlaybackScrollPage:
-    { 
-	int xold = getXForFrame(oldPlayPointerFrame);
-	update(xold - 4, 0, 9, height());
-
-	long w = getEndFrame() - getStartFrame();
-	w -= w/5;
-	long sf = (m_playPointerFrame / w) * w - w/8;
-
-	if (m_manager &&
-	    m_manager->isPlaying() &&
-	    m_manager->getPlaySelectionMode()) {
-	    MultiSelection::SelectionList selections = m_manager->getSelections();
-	    if (!selections.empty()) {
-		size_t selectionStart = selections.begin()->getStartFrame();
-		if (sf < long(selectionStart) - w / 10) {
-		    sf = long(selectionStart) - w / 10;
-		}
-	    }
-	}
+    case PlaybackScrollPageWithCentre:
+
+        if (!pointerInVisibleArea && somethingGoingOn) {
+
+            m_followPlayIsDetached = true;
+
+        } else if (!pointerInVisibleArea && m_followPlayIsDetached) {
+
+            // do nothing; we aren't tracking until the pointer comes back in
+
+        } else {
+
+            int xold = getXForFrame(oldPlayPointerFrame);
+            update(xold - 4, 0, 9, height());
+
+            sv_frame_t w = getEndFrame() - getStartFrame();
+            w -= w/5;
+            sv_frame_t sf = (m_playPointerFrame / w) * w - w/8;
+
+            if (m_manager &&
+                m_manager->isPlaying() &&
+                m_manager->getPlaySelectionMode()) {
+                MultiSelection::SelectionList selections = m_manager->getSelections();
+                if (!selections.empty()) {
+                    sv_frame_t selectionStart = selections.begin()->getStartFrame();
+                    if (sf < selectionStart - w / 10) {
+                        sf = selectionStart - w / 10;
+                    }
+                }
+            }
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-	cerr << "PlaybackScrollPage: f = " << m_playPointerFrame << ", sf = " << sf << ", start frame "
-		  << getStartFrame() << endl;
+            cerr << "PlaybackScrollPage: f = " << m_playPointerFrame << ", sf = " << sf << ", start frame "
+                 << getStartFrame() << endl;
 #endif
 
-	// We don't consider scrolling unless the pointer is outside
-	// the clearly visible range already
-
-	int xnew = getXForFrame(m_playPointerFrame);
+            // We don't consider scrolling unless the pointer is outside
+            // the central visible range already
+
+            int xnew = getXForFrame(m_playPointerFrame);
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-	cerr << "xnew = " << xnew << ", width = " << width() << endl;
+            cerr << "xnew = " << xnew << ", width = " << width() << endl;
 #endif
 
-	if (xnew < width()/8 || xnew > (width()*7)/8) {
-	    if (!somethingGoingOn) {
-		long offset = getFrameForX(width()/2) - getStartFrame();
-		long newCentre = sf + offset;
-		bool changed = setCentreFrame(newCentre, false);
-		if (changed) {
-		    xold = getXForFrame(oldPlayPointerFrame);
-		    update(xold - 4, 0, 9, height());
-		}
-	    }
-	}
-
-	update(xnew - 4, 0, 9, height());
-
-	break;
-    }
+            bool shouldScroll = (xnew > (width() * 7) / 8);
+
+            if (!m_followPlayIsDetached && (xnew < width() / 8)) {
+                shouldScroll = true;
+            }
+
+            if (xnew > width() / 8) {
+                m_followPlayIsDetached = false;
+            } else if (somethingGoingOn) {
+                m_followPlayIsDetached = true;
+            }
+
+            if (!somethingGoingOn && shouldScroll) {
+                sv_frame_t offset = getFrameForX(width()/2) - getStartFrame();
+                sv_frame_t newCentre = sf + offset;
+                bool changed = setCentreFrame(newCentre, false);
+                if (changed) {
+                    xold = getXForFrame(oldPlayPointerFrame);
+                    update(xold - 4, 0, 9, height());
+                }
+            }
+
+            update(xnew - 4, 0, 9, height());
+        }
+        break;
 
     case PlaybackIgnore:
-	if (long(m_playPointerFrame) >= getStartFrame() &&
+	if (m_playPointerFrame >= getStartFrame() &&
             m_playPointerFrame < getEndFrame()) {
 	    update();
 	}
@@ -1054,7 +1173,7 @@
 }
 
 void
-View::viewZoomLevelChanged(View *p, unsigned long z, bool locked)
+View::viewZoomLevelChanged(View *p, int z, bool locked)
 {
 #ifdef DEBUG_VIEW_WIDGET_PAINT
     cerr  << "View[" << this << "]: viewZoomLevelChanged(" << p << ", " << z << ", " << locked << ")" << endl;
@@ -1075,35 +1194,35 @@
     update();
 }
 
-size_t
+sv_frame_t
 View::getFirstVisibleFrame() const
 {
-    long f0 = getStartFrame();
-    size_t f = getModelsStartFrame();
-    if (f0 < 0 || f0 < long(f)) return f;
+    sv_frame_t f0 = getStartFrame();
+    sv_frame_t f = getModelsStartFrame();
+    if (f0 < 0 || f0 < f) return f;
     return f0;
 }
 
-size_t 
+sv_frame_t 
 View::getLastVisibleFrame() const
 {
-    size_t f0 = getEndFrame();
-    size_t f = getModelsEndFrame();
+    sv_frame_t f0 = getEndFrame();
+    sv_frame_t f = getModelsEndFrame();
     if (f0 > f) return f;
     return f0;
 }
 
-size_t
+sv_frame_t
 View::getModelsStartFrame() const
 {
     bool first = true;
-    size_t startFrame = 0;
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    sv_frame_t startFrame = 0;
+
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 
 	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
 
-	    size_t thisStartFrame = (*i)->getModel()->getStartFrame();
+	    sv_frame_t thisStartFrame = (*i)->getModel()->getStartFrame();
 
 	    if (first || thisStartFrame < startFrame) {
 		startFrame = thisStartFrame;
@@ -1114,17 +1233,17 @@
     return startFrame;
 }
 
-size_t
+sv_frame_t
 View::getModelsEndFrame() const
 {
     bool first = true;
-    size_t endFrame = 0;
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    sv_frame_t endFrame = 0;
+
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 
 	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
 
-	    size_t thisEndFrame = (*i)->getModel()->getEndFrame();
+	    sv_frame_t thisEndFrame = (*i)->getModel()->getEndFrame();
 
 	    if (first || thisEndFrame > endFrame) {
 		endFrame = thisEndFrame;
@@ -1137,7 +1256,7 @@
     return endFrame;
 }
 
-int
+sv_samplerate_t
 View::getModelsSampleRate() const
 {
     //!!! Just go for the first, for now.  If we were supporting
@@ -1146,7 +1265,7 @@
 
     //!!! nah, this wants to always return the sr of the main model!
 
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
 	    return (*i)->getModel()->getSampleRate();
 	}
@@ -1189,8 +1308,8 @@
     Model *alignedModel = 0;
     Model *goodModel = 0;
 
-    for (LayerList::const_iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin();
+         i != m_layerStack.end(); ++i) {
 
         Layer *layer = *i;
 
@@ -1216,17 +1335,17 @@
     else return anyModel;
 }
 
-size_t
-View::alignFromReference(size_t f) const
+sv_frame_t
+View::alignFromReference(sv_frame_t f) const
 {
-    if (!m_manager->getAlignMode()) return f;
+    if (!m_manager || !m_manager->getAlignMode()) return f;
     Model *aligningModel = getAligningModel();
     if (!aligningModel) return f;
     return aligningModel->alignFromReference(f);
 }
 
-size_t
-View::alignToReference(size_t f) const
+sv_frame_t
+View::alignToReference(sv_frame_t f) const
 {
     if (!m_manager->getAlignMode()) return f;
     Model *aligningModel = getAligningModel();
@@ -1234,27 +1353,18 @@
     return aligningModel->alignToReference(f);
 }
 
-int
+sv_frame_t
 View::getAlignedPlaybackFrame() const
 {
-    int pf = m_manager->getPlaybackFrame();
+    if (!m_manager) return 0;
+    sv_frame_t pf = m_manager->getPlaybackFrame();
     if (!m_manager->getAlignMode()) return pf;
 
     Model *aligningModel = getAligningModel();
     if (!aligningModel) return pf;
-/*
-    Model *pm = m_manager->getPlaybackModel();
-
-//    cerr << "View[" << this << "]::getAlignedPlaybackFrame: pf = " << pf;
-
-    if (pm) {
-        pf = pm->alignToReference(pf);
-//        cerr << " -> " << pf;
-    }
-*/
-    int af = aligningModel->alignFromReference(pf);
-
-//    cerr << ", aligned = " << af << endl;
+
+    sv_frame_t af = aligningModel->alignFromReference(pf);
+
     return af;
 }
 
@@ -1262,7 +1372,7 @@
 View::areLayersScrollable() const
 {
     // True iff all views are scrollable
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 	if (!(*i)->isLayerScrollable(this)) return false;
     }
     return true;
@@ -1279,7 +1389,7 @@
     LayerList scrollables;
     bool metUnscrollable = false;
 
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 //        SVDEBUG << "View::getScrollableBackLayers: calling isLayerDormant on layer " << *i << endl;
 //        cerr << "(name is " << (*i)->objectName() << ")"
 //                  << endl;
@@ -1315,7 +1425,7 @@
 
     bool started = false;
 
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 	if ((*i)->isLayerDormant(this)) continue;
 	if (!started && (*i)->isLayerScrollable(this)) {
 	    continue;
@@ -1336,22 +1446,22 @@
     return nonScrollables;
 }
 
-size_t
-View::getZoomConstraintBlockSize(size_t blockSize,
+int
+View::getZoomConstraintBlockSize(int blockSize,
 				 ZoomConstraint::RoundingDirection dir)
     const
 {
-    size_t candidate = blockSize;
+    int candidate = blockSize;
     bool haveCandidate = false;
 
     PowerOfSqrtTwoZoomConstraint defaultZoomConstraint;
 
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 
 	const ZoomConstraint *zoomConstraint = (*i)->getZoomConstraint();
 	if (!zoomConstraint) zoomConstraint = &defaultZoomConstraint;
 
-	size_t thisBlockSize =
+	int thisBlockSize =
 	    zoomConstraint->getNearestBlockSize(blockSize, dir);
 
 	// Go for the block size that's furthest from the one
@@ -1370,7 +1480,7 @@
 bool
 View::areLayerColoursSignificant() const
 {
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
 	if ((*i)->getLayerColourSignificance() ==
             Layer::ColourHasMeaningfulValue) return true;
         if ((*i)->isLayerOpaque()) break;
@@ -1381,8 +1491,8 @@
 bool
 View::hasTopLayerTimeXAxis() const
 {
-    LayerList::const_iterator i = m_layers.end();
-    if (i == m_layers.begin()) return false;
+    LayerList::const_iterator i = m_layerStack.end();
+    if (i == m_layerStack.begin()) return false;
     --i;
     return (*i)->hasTimeXAxis();
 }
@@ -1408,7 +1518,7 @@
 void
 View::scroll(bool right, bool lots, bool e)
 {
-    long delta;
+    sv_frame_t delta;
     if (lots) {
 	delta = (getEndFrame() - getStartFrame()) / 2;
     } else {
@@ -1416,9 +1526,9 @@
     }
     if (right) delta = -delta;
 
-    if (int(m_centreFrame) < delta) {
+    if (m_centreFrame < delta) {
 	setCentreFrame(0, e);
-    } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
+    } else if (m_centreFrame - delta >= getModelsEndFrame()) {
 	setCentreFrame(getModelsEndFrame(), e);
     } else {
 	setCentreFrame(m_centreFrame - delta, e);
@@ -1426,6 +1536,25 @@
 }
 
 void
+View::cancelClicked()
+{
+    QPushButton *cancel = qobject_cast<QPushButton *>(sender());
+    if (!cancel) return;
+
+    for (ProgressMap::iterator i = m_progressBars.begin();
+	 i != m_progressBars.end(); ++i) {
+
+        if (i->second.cancel == cancel) {
+
+            Layer *layer = i->first;
+            Model *model = layer->getModel();
+
+            if (model) model->abandon();
+        }
+    }
+}
+
+void
 View::checkProgress(void *object)
 {
     if (!m_showProgress) return;
@@ -1436,6 +1565,7 @@
 	 i != m_progressBars.end(); ++i) {
 
         QProgressBar *pb = i->second.bar;
+        QPushButton *cancel = i->second.cancel;
 
 	if (i->first == object) {
 
@@ -1465,8 +1595,9 @@
 
                 //!!!
                 if (wfm ||
-                    (wfm = dynamic_cast<RangeSummarisableTimeValueModel *>
-                     (model->getSourceModel()))) {
+                    (model && 
+                     (wfm = dynamic_cast<RangeSummarisableTimeValueModel *>
+                      (model->getSourceModel())))) {
                     completion = wfm->getAlignmentCompletion();
 //                    SVDEBUG << "View::checkProgress: Alignment completion = " << completion << endl;
                     if (completion < 100) {
@@ -1481,6 +1612,7 @@
 	    if (completion >= 100) {
 
 		pb->hide();
+                cancel->hide();
                 timer->stop();
 
 	    } else {
@@ -1493,8 +1625,11 @@
                     timer->start();
                 }
 
+                cancel->move(0, ph - pb->height()/2 - 10);
+                cancel->show();
+
 		pb->setValue(completion);
-		pb->move(0, ph - pb->height());
+		pb->move(20, ph - pb->height());
 
 		pb->show();
 		pb->update();
@@ -1544,18 +1679,34 @@
 void
 View::setPaintFont(QPainter &paint)
 {
+    int scaleFactor = 1;
+    int dpratio = effectiveDevicePixelRatio();
+    if (dpratio > 1) {
+        QPaintDevice *dev = paint.device();
+        if (dynamic_cast<QPixmap *>(dev) || dynamic_cast<QImage *>(dev)) {
+            scaleFactor = dpratio;
+        }
+    }
+
     QFont font(paint.font());
-    font.setPointSize(Preferences::getInstance()->getViewFontSize());
+    font.setPointSize(Preferences::getInstance()->getViewFontSize()
+                      * scaleFactor);
     paint.setFont(font);
 }
 
+QRect
+View::getPaintRect() const
+{
+    return rect();
+}
+
 void
 View::paintEvent(QPaintEvent *e)
 {
 //    Profiler prof("View::paintEvent", false);
-//    SVDEBUG << "View::paintEvent: centre frame is " << m_centreFrame << endl;
-
-    if (m_layers.empty()) {
+//    cerr << "View::paintEvent: centre frame is " << m_centreFrame << endl;
+
+    if (m_layerStack.empty()) {
 	QFrame::paintEvent(e);
 	return;
     }
@@ -1585,6 +1736,8 @@
 
     QRect nonCacheRect(cacheRect);
 
+    int dpratio = effectiveDevicePixelRatio();
+
     // If not all layers are scrollable, but some of the back layers
     // are, we should store only those in the cache.
 
@@ -1593,29 +1746,37 @@
     LayerList nonScrollables = getNonScrollableFrontLayers(true, layersChanged);
     bool selectionCacheable = nonScrollables.empty();
     bool haveSelections = m_manager && !m_manager->getSelections().empty();
-    bool selectionDrawn = false;
 
     // If all the non-scrollable layers are non-opaque, then we draw
     // the selection rectangle behind them and cache it.  If any are
-    // opaque, however, we can't cache.
+    // opaque, however, or if our device-pixel ratio is not 1 (so we
+    // need to paint direct to the widget), then we can't cache.
     //
-    if (!selectionCacheable) {
-	selectionCacheable = true;
-	for (LayerList::const_iterator i = nonScrollables.begin();
-	     i != nonScrollables.end(); ++i) {
-	    if ((*i)->isLayerOpaque()) {
-		selectionCacheable = false;
-		break;
-	    }
-	}
-    }
-
-    if (selectionCacheable) {
-	QPoint localPos;
-	bool closeToLeft, closeToRight;
-	if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
-	    selectionCacheable = false;
-	}
+    if (dpratio == 1) {
+
+        if (!selectionCacheable) {
+            selectionCacheable = true;
+            for (LayerList::const_iterator i = nonScrollables.begin();
+                 i != nonScrollables.end(); ++i) {
+                if ((*i)->isLayerOpaque()) {
+                    selectionCacheable = false;
+                    break;
+                }
+            }
+        }
+
+        if (selectionCacheable) {
+            QPoint localPos;
+            bool closeToLeft, closeToRight;
+            if (shouldIlluminateLocalSelection
+                (localPos, closeToLeft, closeToRight)) {
+                selectionCacheable = false;
+            }
+        }
+
+    } else {
+
+        selectionCacheable = false;
     }
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
@@ -1633,6 +1794,14 @@
 	m_selectionCached = false;
     }
 
+    QSize scaledCacheSize(scaledSize(size(), dpratio));
+    QRect scaledCacheRect(scaledRect(cacheRect, dpratio));
+
+    if (!m_buffer || scaledCacheSize != m_buffer->size()) {
+        delete m_buffer;
+        m_buffer = new QPixmap(scaledCacheSize);
+    }
+    
     if (!scrollables.empty()) {
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
@@ -1642,8 +1811,7 @@
 
 	if (!m_cache ||
 	    m_cacheZoomLevel != m_zoomLevel ||
-	    width() != m_cache->width() ||
-	    height() != m_cache->height()) {
+            scaledCacheSize != m_cache->size()) {
 
 	    // cache is not valid
 
@@ -1655,7 +1823,7 @@
 #endif
 	    } else {
 		delete m_cache;
-		m_cache = new QPixmap(width(), height());
+		m_cache = new QPixmap(scaledCacheSize);
 #ifdef DEBUG_VIEW_WIDGET_PAINT
 		cerr << "View(" << this << ")::paintEvent: recreated cache" << endl;
 #endif
@@ -1665,29 +1833,15 @@
 
 	} else if (m_cacheCentreFrame != m_centreFrame) {
 
-	    long dx =
+	    int dx =
 		getXForFrame(m_cacheCentreFrame) -
 		getXForFrame(m_centreFrame);
 
 	    if (dx > -width() && dx < width()) {
-#ifdef PIXMAP_COPY_TO_SELF
-                // This is not normally defined. Copying a pixmap to
-		// itself doesn't work properly on Windows, Mac, or
-		// X11 with the raster backend (it only works when
-		// moving in one direction and then presumably only by
-		// accident).  It does actually seem to be fine on X11
-		// with the native backend, but we prefer not to use
-		// that anyway
-		paint.begin(m_cache);
-		paint.drawPixmap(dx, 0, *m_cache);
-		paint.end();
-#else
 		static QPixmap *tmpPixmap = 0;
-		if (!tmpPixmap ||
-		    tmpPixmap->width() != width() ||
-		    tmpPixmap->height() != height()) {
+		if (!tmpPixmap || tmpPixmap->size() != scaledCacheSize) {
 		    delete tmpPixmap;
-		    tmpPixmap = new QPixmap(width(), height());
+		    tmpPixmap = new QPixmap(scaledCacheSize);
 		}
 		paint.begin(tmpPixmap);
 		paint.drawPixmap(0, 0, *m_cache);
@@ -1695,7 +1849,6 @@
 		paint.begin(m_cache);
 		paint.drawPixmap(dx, 0, *tmpPixmap);
 		paint.end();
-#endif
 		if (dx < 0) {
 		    cacheRect = QRect(width() + dx, 0, -dx, height());
 		} else {
@@ -1716,8 +1869,8 @@
 #ifdef DEBUG_VIEW_WIDGET_PAINT
 	    cerr << "View(" << this << ")::paintEvent: cache is good" << endl;
 #endif
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+	    paint.begin(m_buffer);
+	    paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
 	    paint.end();
 	    QFrame::paintEvent(e);
 	    paintedCacheRect = true;
@@ -1733,16 +1886,26 @@
 
     // Scrollable (cacheable) items first
 
+    ViewProxy proxy(this, dpratio);
+    
     if (!paintedCacheRect) {
 
-	if (repaintCache) paint.begin(m_cache);
-	else paint.begin(this);
+        QRect rectToPaint;
+
+	if (repaintCache) {
+            paint.begin(m_cache);
+            rectToPaint = scaledCacheRect;
+        } else {
+            paint.begin(m_buffer);
+            rectToPaint = scaledCacheRect;
+        }
+
         setPaintFont(paint);
-	paint.setClipRect(cacheRect);
+	paint.setClipRect(rectToPaint);
 
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
-	paint.drawRect(cacheRect);
+	paint.drawRect(rectToPaint);
 
 	paint.setPen(getForeground());
 	paint.setBrush(Qt::NoBrush);
@@ -1750,22 +1913,25 @@
 	for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
 	    paint.setRenderHint(QPainter::Antialiasing, false);
 	    paint.save();
-	    (*i)->paint(this, paint, cacheRect);
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+            cerr << "Painting scrollable layer " << *i << " using proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << rectToPaint.x() << "," << rectToPaint.y() << " " << rectToPaint.width() << "x" << rectToPaint.height() << endl;
+#endif
+            (*i)->paint(&proxy, paint, rectToPaint);
 	    paint.restore();
 	}
 
 	if (haveSelections && selectionCacheable) {
 	    drawSelections(paint);
 	    m_selectionCached = repaintCache;
-	    selectionDrawn = true;
 	}
 	
 	paint.end();
 
 	if (repaintCache) {
 	    cacheRect |= (e ? e->rect() : rect());
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+            scaledCacheRect = scaledRect(cacheRect, dpratio);
+	    paint.begin(m_buffer);
+	    paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
 	    paint.end();
 	}
     }
@@ -1776,13 +1942,15 @@
 
     nonCacheRect |= cacheRect;
 
-    paint.begin(this);
-    paint.setClipRect(nonCacheRect);
+    QRect scaledNonCacheRect = scaledRect(nonCacheRect, dpratio);
+    
+    paint.begin(m_buffer);
+    paint.setClipRect(scaledNonCacheRect);
     setPaintFont(paint);
     if (scrollables.empty()) {
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
-	paint.drawRect(nonCacheRect);
+	paint.drawRect(scaledNonCacheRect);
     }
 	
     paint.setPen(getForeground());
@@ -1790,10 +1958,18 @@
 	
     for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) {
 //        Profiler profiler2("View::paintEvent non-cacheable");
-	(*i)->paint(this, paint, nonCacheRect);
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+        cerr << "Painting non-scrollable layer " << *i << " without proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << nonCacheRect.x() << "," << nonCacheRect.y() << " " << nonCacheRect.width() << "x" << nonCacheRect.height() << endl;
+#endif
+	(*i)->paint(&proxy, paint, scaledNonCacheRect);
     }
 	
     paint.end();
+    
+    paint.begin(this);
+    QRect finalPaintRect = e ? e->rect() : rect();
+    paint.drawPixmap(finalPaintRect, *m_buffer, scaledRect(finalPaintRect, dpratio));
+    paint.end();
 
     paint.begin(this);
     setPaintFont(paint);
@@ -1806,16 +1982,19 @@
     bool showPlayPointer = true;
     if (m_followPlay == PlaybackScrollContinuous) {
         showPlayPointer = false;
-    } else if (long(m_playPointerFrame) <= getStartFrame() ||
+    } else if (m_playPointerFrame <= getStartFrame() ||
                m_playPointerFrame >= getEndFrame()) {
         showPlayPointer = false;
     } else if (m_manager && !m_manager->isPlaying()) {
         if (m_playPointerFrame == getCentreFrame() &&
+            m_manager->shouldShowCentreLine() &&
             m_followPlay != PlaybackIgnore) {
+            // Don't show the play pointer when it is redundant with
+            // the centre line
             showPlayPointer = false;
         }
     }
-
+    
     if (showPlayPointer) {
 
 	paint.begin(this);
@@ -1864,10 +2043,10 @@
         paint.setBrush(Qt::NoBrush);
     }
 
-    int sampleRate = getModelsSampleRate();
+    sv_samplerate_t sampleRate = getModelsSampleRate();
 
     QPoint localPos;
-    long illuminateFrame = -1;
+    sv_frame_t illuminateFrame = -1;
     bool closeToLeft, closeToRight;
 
     if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
@@ -2035,7 +2214,7 @@
     int fontHeight = paint.fontMetrics().height();
     int fontAscent = paint.fontMetrics().ascent();
 
-    float v0, v1;
+    double v0, v1;
     QString u0, u1;
     bool b0 = false, b1 = false;
 
@@ -2107,7 +2286,7 @@
     }
 
     bool bd = false;
-    float dy = 0.f;
+    double dy = 0.f;
     QString du;
 
     // dimension, height
@@ -2118,7 +2297,7 @@
         if (du != "") {
             if (du == "Hz") {
                 int semis;
-                float cents;
+                double cents;
                 semis = Pitch::getPitchForFrequencyDifference(v0, v1, &cents);
                 dys = QString("[%1 %2 (%3)]")
                     .arg(dy).arg(du)
@@ -2223,19 +2402,19 @@
 }
 
 bool
-View::render(QPainter &paint, int xorigin, size_t f0, size_t f1)
+View::render(QPainter &paint, int xorigin, sv_frame_t f0, sv_frame_t f1)
 {
-    size_t x0 = f0 / m_zoomLevel;
-    size_t x1 = f1 / m_zoomLevel;
-
-    size_t w = x1 - x0;
-
-    size_t origCentreFrame = m_centreFrame;
+    int x0 = int(f0 / m_zoomLevel);
+    int x1 = int(f1 / m_zoomLevel);
+
+    int w = x1 - x0;
+
+    sv_frame_t origCentreFrame = m_centreFrame;
 
     bool someLayersIncomplete = false;
 
-    for (LayerList::iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) {
+    for (LayerList::iterator i = m_layerStack.begin();
+         i != m_layerStack.end(); ++i) {
 
         int c = (*i)->getCompletion(this);
         if (c < 100) {
@@ -2253,11 +2432,11 @@
 
         while (layerCompletion < 100) {
 
-            for (LayerList::iterator i = m_layers.begin();
-                 i != m_layers.end(); ++i) {
+            for (LayerList::iterator i = m_layerStack.begin();
+                 i != m_layerStack.end(); ++i) {
 
                 int c = (*i)->getCompletion(this);
-                if (i == m_layers.begin() || c < layerCompletion) {
+                if (i == m_layerStack.begin() || c < layerCompletion) {
                     layerCompletion = c;
                 }
             }
@@ -2278,7 +2457,7 @@
     QProgressDialog progress(tr("Rendering image..."),
                              tr("Cancel"), 0, w / width(), this);
 
-    for (size_t x = 0; x < w; x += width()) {
+    for (int x = 0; x < w; x += width()) {
 
         progress.setValue(x / width());
         qApp->processEvents();
@@ -2300,25 +2479,25 @@
 	paint.setPen(getForeground());
 	paint.setBrush(Qt::NoBrush);
 
-	for (LayerList::iterator i = m_layers.begin();
-             i != m_layers.end(); ++i) {
-		if(!((*i)->isLayerDormant(this))){
-
-		    paint.setRenderHint(QPainter::Antialiasing, false);
-
-		    paint.save();
-	            paint.translate(xorigin + x, 0);
-
-	            cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl;
-
-	            (*i)->setSynchronousPainting(true);
-
-		    (*i)->paint(this, paint, chunk);
-
-	            (*i)->setSynchronousPainting(false);
-
-		    paint.restore();
-		}
+	for (LayerList::iterator i = m_layerStack.begin();
+             i != m_layerStack.end(); ++i) {
+            if (!((*i)->isLayerDormant(this))){
+
+                paint.setRenderHint(QPainter::Antialiasing, false);
+
+                paint.save();
+                paint.translate(xorigin + x, 0);
+
+                cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl;
+
+                (*i)->setSynchronousPainting(true);
+
+                (*i)->paint(this, paint, chunk);
+
+                (*i)->setSynchronousPainting(false);
+
+                paint.restore();
+            }
 	}
     }
 
@@ -2330,17 +2509,17 @@
 QImage *
 View::toNewImage()
 {
-    size_t f0 = getModelsStartFrame();
-    size_t f1 = getModelsEndFrame();
+    sv_frame_t f0 = getModelsStartFrame();
+    sv_frame_t f1 = getModelsEndFrame();
 
     return toNewImage(f0, f1);
 }
 
 QImage *
-View::toNewImage(size_t f0, size_t f1)
+View::toNewImage(sv_frame_t f0, sv_frame_t f1)
 {
-    size_t x0 = f0 / getZoomLevel();
-    size_t x1 = f1 / getZoomLevel();
+    int x0 = int(f0 / getZoomLevel());
+    int x1 = int(f1 / getZoomLevel());
     
     QImage *image = new QImage(x1 - x0, height(), QImage::Format_RGB32);
 
@@ -2358,17 +2537,17 @@
 QSize
 View::getImageSize()
 {
-    size_t f0 = getModelsStartFrame();
-    size_t f1 = getModelsEndFrame();
+    sv_frame_t f0 = getModelsStartFrame();
+    sv_frame_t f1 = getModelsEndFrame();
 
     return getImageSize(f0, f1);
 }
     
 QSize
-View::getImageSize(size_t f0, size_t f1)
+View::getImageSize(sv_frame_t f0, sv_frame_t f1)
 {
-    size_t x0 = f0 / getZoomLevel();
-    size_t x1 = f1 / getZoomLevel();
+    int x0 = int(f0 / getZoomLevel());
+    int x1 = int(f1 / getZoomLevel());
 
     return QSize(x1 - x0, height());
 }
@@ -2391,14 +2570,16 @@
 	.arg(m_followPan)
 	.arg(m_followZoom)
 	.arg(m_followPlay == PlaybackScrollContinuous ? "scroll" :
-	     m_followPlay == PlaybackScrollPage ? "page" : "ignore")
+	     m_followPlay == PlaybackScrollPageWithCentre ? "page" :
+	     m_followPlay == PlaybackScrollPage ? "daw" :
+             "ignore")
 	.arg(extraAttributes);
 
-    for (size_t i = 0; i < m_layers.size(); ++i) {
-        bool visible = !m_layers[i]->isLayerDormant(this);
-        m_layers[i]->toBriefXml(stream, indent + "  ",
-                                QString("visible=\"%1\"")
-                                .arg(visible ? "true" : "false"));
+    for (int i = 0; i < (int)m_fixedOrderLayers.size(); ++i) {
+        bool visible = !m_fixedOrderLayers[i]->isLayerDormant(this);
+        m_fixedOrderLayers[i]->toBriefXml(stream, indent + "  ",
+                                          QString("visible=\"%1\"")
+                                          .arg(visible ? "true" : "false"));
     }
 
     stream << indent + "</view>\n";
@@ -2407,7 +2588,11 @@
 ViewPropertyContainer::ViewPropertyContainer(View *v) :
     m_v(v)
 {
+//    cerr << "ViewPropertyContainer: " << this << " is owned by View " << v << endl;
     connect(m_v, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
 	    this, SIGNAL(propertyChanged(PropertyContainer::PropertyName)));
 }
 
+ViewPropertyContainer::~ViewPropertyContainer()
+{
+}
--- a/view/View.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/View.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,16 +19,21 @@
 #include <QFrame>
 #include <QProgressBar>
 
+#include "LayerGeometryProvider.h"
+
 #include "base/ZoomConstraint.h"
 #include "base/PropertyContainer.h"
 #include "ViewManager.h"
 #include "base/XmlExportable.h"
+#include "base/BaseTypes.h"
 
 // #define DEBUG_VIEW_WIDGET_PAINT 1
 
 class Layer;
 class ViewPropertyContainer;
 
+class QPushButton;
+
 #include <map>
 #include <set>
 
@@ -46,7 +51,8 @@
  */
 
 class View : public QFrame,
-	     public XmlExportable
+	     public XmlExportable,
+             public LayerGeometryProvider
 {
     Q_OBJECT
 
@@ -58,17 +64,23 @@
     virtual ~View();
 
     /**
+     * Retrieve the id of this object. Views have their own unique
+     * ids, but ViewProxy objects share the id of their View.
+     */
+    int getId() const { return m_id; }
+    
+    /**
      * Retrieve the first visible sample frame on the widget.
      * This is a calculated value based on the centre-frame, widget
      * width and zoom level.  The result may be negative.
      */
-    long getStartFrame() const;
+    sv_frame_t getStartFrame() const;
 
     /**
      * Set the widget pan based on the given first visible frame.  The
      * frame value may be negative.
      */
-    void setStartFrame(long);
+    void setStartFrame(sv_frame_t);
 
     /**
      * Return the centre frame of the visible widget.  This is an
@@ -76,30 +88,44 @@
      * frame values (start, end) are calculated from this based on the
      * zoom and other factors.
      */
-    size_t getCentreFrame() const { return m_centreFrame; }
+    sv_frame_t getCentreFrame() const { return m_centreFrame; }
 
     /**
      * Set the centre frame of the visible widget.
      */
-    void setCentreFrame(size_t f) { setCentreFrame(f, true); }
+    void setCentreFrame(sv_frame_t f) { setCentreFrame(f, true); }
 
     /**
      * Retrieve the last visible sample frame on the widget.
      * This is a calculated value based on the centre-frame, widget
      * width and zoom level.
      */
-    size_t getEndFrame() const;
+    sv_frame_t getEndFrame() const;
 
     /**
      * Return the pixel x-coordinate corresponding to a given sample
      * frame (which may be negative).
      */
-    int getXForFrame(long frame) const;
+    int getXForFrame(sv_frame_t frame) const;
 
     /**
      * Return the closest frame to the given pixel x-coordinate.
      */
-    long getFrameForX(int x) const;
+    sv_frame_t getFrameForX(int x) const;
+
+    /**
+     * Return the closest pixel x-coordinate corresponding to a given
+     * view x-coordinate. Default is no scaling, ViewProxy handles
+     * scaling case.
+     */
+    int getXForViewX(int viewx) const { return viewx; }
+
+    /**
+     * Return the closest view x-coordinate corresponding to a given
+     * pixel x-coordinate. Default is no scaling, ViewProxy handles
+     * scaling case.
+     */
+    int getViewXForX(int x) const { return x; }
 
     /**
      * Return the pixel y-coordinate corresponding to a given
@@ -109,7 +135,7 @@
      *
      * Not thread-safe in logarithmic mode.  Call only from GUI thread.
      */
-    float getYForFrequency(float frequency, float minFreq, float maxFreq, 
+    double getYForFrequency(double frequency, double minFreq, double maxFreq, 
 			   bool logarithmic) const;
 
     /**
@@ -118,7 +144,7 @@
      *
      * Not thread-safe in logarithmic mode.  Call only from GUI thread.
      */
-    float getFrequencyForY(int y, float minFreq, float maxFreq,
+    double getFrequencyForY(int y, double minFreq, double maxFreq,
 			   bool logarithmic) const;
 
     /**
@@ -131,7 +157,7 @@
      * centre frame will be unchanged; the start and end frames will
      * change.
      */
-    virtual void setZoomLevel(size_t z);
+    virtual void setZoomLevel(int z);
 
     /**
      * Zoom in or out.
@@ -143,39 +169,91 @@
      */
     virtual void scroll(bool right, bool lots, bool doEmit = true);
 
+    /**
+     * Add a layer to the view. (Normally this should be handled
+     * through some command abstraction instead of using this function
+     * directly.)
+     */
     virtual void addLayer(Layer *v);
-    virtual void removeLayer(Layer *v); // does not delete the layer
-    virtual int getLayerCount() const { return m_layers.size(); }
 
     /**
-     * Return a layer, counted in stacking order.  That is, layer 0 is
-     * the bottom layer and layer "getLayerCount()-1" is the top one.
+     * Remove a layer from the view. Does not delete the
+     * layer. (Normally this should be handled through some command
+     * abstraction instead of using this function directly.)
+     */
+    virtual void removeLayer(Layer *v);
+
+    /**
+     * Return the number of layers, regardless of whether visible or
+     * dormant, i.e. invisible, in this view.
+     */
+    virtual int getLayerCount() const { return int(m_layerStack.size()); }
+
+    /**
+     * Return the nth layer, counted in stacking order.  That is,
+     * layer 0 is the bottom layer and layer "getLayerCount()-1" is
+     * the top one. The returned layer may be visible or it may be
+     * dormant, i.e. invisible.
      */
     virtual Layer *getLayer(int n) {
-        if (n < int(m_layers.size())) return m_layers[n]; else return 0;
+        if (in_range_for(m_layerStack, n)) return m_layerStack[n];
+        else return 0;
     }
 
     /**
-     * Return the top layer.  This is the same as
-     * getLayer(getLayerCount()-1) if there is at least one layer, and
-     * 0 otherwise.
+     * Return the nth layer, counted in the order they were
+     * added. Unlike the stacking order used in getLayer(), which
+     * changes each time a layer is selected, this ordering remains
+     * fixed. The returned layer may be visible or it may be dormant,
+     * i.e. invisible.
      */
-    virtual Layer *getTopLayer() {
-        return m_layers.empty() ? 0 : m_layers[m_layers.size()-1];
+    virtual Layer *getFixedOrderLayer(int n) {
+        if (n < int(m_fixedOrderLayers.size())) return m_fixedOrderLayers[n];
+        else return 0;
     }
 
     /**
-     * Return the layer last selected by the user.  This is normally
-     * the top layer, the same as getLayer(getLayerCount()-1).
-     * However, if the user has selected the pane itself more recently
-     * than any of the layers on it, this function will return 0.  It
-     * will also return 0 if there are no layers.
+     * Return the layer currently active for tool interaction. This is
+     * the topmost non-dormant (i.e. visible) layer in the view. If
+     * there are no visible layers in the view, return 0.
+     */
+    virtual Layer *getInteractionLayer();
+
+    virtual const Layer *getInteractionLayer() const;
+
+    /**
+     * Return the layer most recently selected by the user. This is
+     * the layer that any non-tool-driven commands should operate on,
+     * in the case where this view is the "current" one.
+     *
+     * If the user has selected the view itself more recently than any
+     * of the layers on it, this function will return 0, and any
+     * non-tool-driven layer commands should be deactivated while this
+     * view is current. It will also return 0 if there are no layers
+     * in the view.
+     *
+     * Note that, unlike getInteractionLayer(), this could return an
+     * invisible (dormant) layer.
      */
     virtual Layer *getSelectedLayer();
+
     virtual const Layer *getSelectedLayer() const;
 
+    /**
+     * Return the "top" layer in the view, whether visible or dormant.
+     * This is the same as getLayer(getLayerCount()-1) if there is at
+     * least one layer, and 0 otherwise.
+     *
+     * For most purposes involving interaction or commands, you
+     * probably want either getInteractionLayer() or
+     * getSelectedLayer() instead.
+     */
+    virtual Layer *getTopLayer() {
+        return m_layerStack.empty() ? 0 : m_layerStack[m_layerStack.size()-1];
+    }
+
     virtual void setViewManager(ViewManager *m);
-    virtual void setViewManager(ViewManager *m, long initialFrame);
+    virtual void setViewManager(ViewManager *m, sv_frame_t initialFrame);
     virtual ViewManager *getViewManager() const { return m_manager; }
 
     virtual void setFollowGlobalPan(bool f);
@@ -188,18 +266,15 @@
     virtual QColor getForeground() const;
     virtual QColor getBackground() const;
 
-    enum TextStyle {
-	BoxedText,
-	OutlinedText,
-        OutlinedItalicText
-    };
-
     virtual void drawVisibleText(QPainter &p, int x, int y,
 				 QString text, TextStyle style) const;
 
     virtual void drawMeasurementRect(QPainter &p, const Layer *,
                                      QRect rect, bool focus) const;
 
+    virtual bool shouldShowFeatureLabels() const {
+        return m_manager && m_manager->shouldShowFeatureLabels();
+    }
     virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const {
 	return false;
     }
@@ -229,41 +304,60 @@
     }
     virtual QString getPropertyContainerIconName() const = 0;
 
-    virtual size_t getPropertyContainerCount() const;
+    virtual int getPropertyContainerCount() const;
 
-    virtual const PropertyContainer *getPropertyContainer(size_t i) const;
-    virtual PropertyContainer *getPropertyContainer(size_t i);
+    // The 0th property container is the view's own; the rest are the
+    // layers in fixed-order series
+    virtual const PropertyContainer *getPropertyContainer(int i) const;
+    virtual PropertyContainer *getPropertyContainer(int i);
 
     // Render the contents on a wide canvas
-    virtual QImage *toNewImage(size_t f0, size_t f1);
+    virtual QImage *toNewImage(sv_frame_t f0, sv_frame_t f1);
     virtual QImage *toNewImage();
-    virtual QSize getImageSize(size_t f0, size_t f1);
+    virtual QSize getImageSize(sv_frame_t f0, sv_frame_t f1);
     virtual QSize getImageSize();
 
     virtual int getTextLabelHeight(const Layer *layer, QPainter &) const;
 
-    virtual bool getValueExtents(QString unit, float &min, float &max,
+    virtual bool getValueExtents(QString unit, double &min, double &max,
                                  bool &log) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
 
     // First frame actually in model, to right of scale, if present
-    virtual size_t getFirstVisibleFrame() const;
-    virtual size_t getLastVisibleFrame() const;
+    virtual sv_frame_t getFirstVisibleFrame() const;
+    virtual sv_frame_t getLastVisibleFrame() const;
 
-    size_t getModelsStartFrame() const;
-    size_t getModelsEndFrame() const;
+    sv_frame_t getModelsStartFrame() const;
+    sv_frame_t getModelsEndFrame() const;
+
+    /**
+     * To be called from a layer, to obtain the extent of the surface
+     * that the layer is currently painting to. This may be the extent
+     * of the view (if 1x display scaling is in effect) or of a larger
+     * cached pixmap (if greater display scaling is in effect).
+     */
+    QRect getPaintRect() const;
+
+    QSize getPaintSize() const { return getPaintRect().size(); }
+    int getPaintWidth() const { return getPaintRect().width(); }
+    int getPaintHeight() const { return getPaintRect().height(); }
 
     typedef std::set<Model *> ModelSet;
     ModelSet getModels();
 
     //!!!
     Model *getAligningModel() const;
-    size_t alignFromReference(size_t) const;
-    size_t alignToReference(size_t) const;
-    int getAlignedPlaybackFrame() const;
+    sv_frame_t alignFromReference(sv_frame_t) const;
+    sv_frame_t alignToReference(sv_frame_t) const;
+    sv_frame_t getAlignedPlaybackFrame() const;
 
+    void updatePaintRect(QRect r) { update(r); }
+    
+    View *getView() { return this; } 
+    const View *getView() const { return this; } 
+    
 signals:
     void propertyContainerAdded(PropertyContainer *pc);
     void propertyContainerRemoved(PropertyContainer *pc);
@@ -275,17 +369,17 @@
 
     void layerModelChanged();
 
-    void centreFrameChanged(unsigned long frame,
+    void centreFrameChanged(sv_frame_t frame,
                             bool globalScroll,
                             PlaybackFollowMode followMode);
 
-    void zoomLevelChanged(unsigned long, bool);
+    void zoomLevelChanged(int, bool);
 
     void contextHelpChanged(const QString &);
 
 public slots:
     virtual void modelChanged();
-    virtual void modelChanged(size_t startFrame, size_t endFrame);
+    virtual void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     virtual void modelCompletionChanged();
     virtual void modelAlignmentCompletionChanged();
     virtual void modelReplaced();
@@ -294,10 +388,10 @@
     virtual void layerMeasurementRectsChanged();
     virtual void layerNameChanged();
 
-    virtual void globalCentreFrameChanged(unsigned long);
-    virtual void viewCentreFrameChanged(View *, unsigned long);
-    virtual void viewManagerPlaybackFrameChanged(unsigned long);
-    virtual void viewZoomLevelChanged(View *, unsigned long, bool);
+    virtual void globalCentreFrameChanged(sv_frame_t);
+    virtual void viewCentreFrameChanged(View *, sv_frame_t);
+    virtual void viewManagerPlaybackFrameChanged(sv_frame_t);
+    virtual void viewZoomLevelChanged(View *, int, bool);
 
     virtual void propertyContainerSelected(View *, PropertyContainer *pc);
 
@@ -306,23 +400,36 @@
     virtual void overlayModeChanged();
     virtual void zoomWheelsEnabledChanged();
 
+    virtual void cancelClicked();
+
     virtual void progressCheckStalledTimerElapsed();
 
 protected:
     View(QWidget *, bool showProgress);
+
+    int m_id;
+    
     virtual void paintEvent(QPaintEvent *e);
     virtual void drawSelections(QPainter &);
     virtual bool shouldLabelSelections() const { return true; }
-    virtual bool render(QPainter &paint, int x0, size_t f0, size_t f1);
+    virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1);
     virtual void setPaintFont(QPainter &paint);
+
+    QSize scaledSize(const QSize &s, int factor) {
+        return QSize(s.width() * factor, s.height() * factor);
+    }
+    QRect scaledRect(const QRect &r, int factor) {
+        return QRect(r.x() * factor, r.y() * factor,
+                     r.width() * factor, r.height() * factor);
+    }
     
     typedef std::vector<Layer *> LayerList;
 
-    int getModelsSampleRate() const;
+    sv_samplerate_t getModelsSampleRate() const;
     bool areLayersScrollable() const;
     LayerList getScrollableBackLayers(bool testChanged, bool &changed) const;
     LayerList getNonScrollableFrontLayers(bool testChanged, bool &changed) const;
-    size_t getZoomConstraintBlockSize(size_t blockSize,
+    int getZoomConstraintBlockSize(int blockSize,
 				      ZoomConstraint::RoundingDirection dir =
 				      ZoomConstraint::RoundNearest) const;
 
@@ -337,30 +444,35 @@
     // false.
     bool hasTopLayerTimeXAxis() const;
 
-    bool setCentreFrame(size_t f, bool doEmit);
+    bool setCentreFrame(sv_frame_t f, bool doEmit);
 
-    void movePlayPointer(unsigned long f);
+    void movePlayPointer(sv_frame_t f);
 
     void checkProgress(void *object);
     int getProgressBarWidth() const; // if visible
 
-    size_t              m_centreFrame;
+    int effectiveDevicePixelRatio() const;
+
+    sv_frame_t          m_centreFrame;
     int                 m_zoomLevel;
     bool                m_followPan;
     bool                m_followZoom;
     PlaybackFollowMode  m_followPlay;
-    size_t              m_playPointerFrame;
+    bool                m_followPlayIsDetached;
+    sv_frame_t          m_playPointerFrame;
     bool                m_lightBackground;
     bool                m_showProgress;
 
     QPixmap            *m_cache;
-    size_t              m_cacheCentreFrame;
+    QPixmap            *m_buffer;
+    sv_frame_t          m_cacheCentreFrame;
     int                 m_cacheZoomLevel;
     bool                m_selectionCached;
 
     bool                m_deleting;
 
-    LayerList           m_layers; // I don't own these, but see dtor note above
+    LayerList           m_layerStack; // I don't own these, but see dtor note above
+    LayerList           m_fixedOrderLayers;
     bool                m_haveSelectedLayer;
 
     QString             m_lastError;
@@ -369,16 +481,8 @@
     mutable LayerList m_lastScrollableBackLayers;
     mutable LayerList m_lastNonScrollableBackLayers;
 
-    class LayerProgressBar : public QProgressBar {
-    public:
-	LayerProgressBar(QWidget *parent);
-	virtual QString text() const { return m_text; }
-	virtual void setText(QString text) { m_text = text; }
-    protected:
-	QString m_text;
-    };
-
     struct ProgressBarRec {
+        QPushButton *cancel;
         QProgressBar *bar;
         int lastCheck;
         QTimer *checkTimer;
@@ -401,6 +505,8 @@
 
 public:
     ViewPropertyContainer(View *v);
+    virtual ~ViewPropertyContainer();
+
     PropertyList getProperties() const { return m_v->getProperties(); }
     QString getPropertyLabel(const PropertyName &n) const {
         return m_v->getPropertyLabel(n);
--- a/view/ViewManager.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/ViewManager.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -43,11 +43,12 @@
     m_playSelectionMode(false),
     m_playSoloMode(false),
     m_alignMode(false),
-    m_overlayMode(MinimalOverlays),
+    m_overlayMode(StandardOverlays),
     m_zoomWheelsEnabled(true),
     m_showCentreLine(true),
     m_illuminateLocalFeatures(true),
     m_showWorkTitle(false),
+    m_showDuration(true),
     m_lightPalette(QApplication::palette()),
     m_darkPalette(QApplication::palette())
 {
@@ -57,9 +58,9 @@
         (settings.value("overlay-mode", int(m_overlayMode)).toInt());
 
     if (m_overlayMode != NoOverlays &&
-        m_overlayMode != MinimalOverlays &&
+        m_overlayMode != StandardOverlays &&
         m_overlayMode != AllOverlays) {
-        m_overlayMode = MinimalOverlays;
+        m_overlayMode = StandardOverlays;
     }
 
     m_zoomWheelsEnabled =
@@ -125,7 +126,7 @@
 {
 }
 
-unsigned long
+sv_frame_t
 ViewManager::getGlobalCentreFrame() const
 {
 #ifdef DEBUG_VIEW_MANAGER
@@ -135,7 +136,7 @@
 }
 
 void
-ViewManager::setGlobalCentreFrame(unsigned long f)
+ViewManager::setGlobalCentreFrame(sv_frame_t f)
 {
 #ifdef DEBUG_VIEW_MANAGER
     cerr << "ViewManager::setGlobalCentreFrame to " << f << endl;
@@ -144,7 +145,7 @@
     emit globalCentreFrameChanged(f);
 }
 
-unsigned long
+int
 ViewManager::getGlobalZoom() const
 {
 #ifdef DEBUG_VIEW_MANAGER
@@ -153,7 +154,7 @@
     return m_globalZoom;
 }
 
-unsigned long
+sv_frame_t
 ViewManager::getPlaybackFrame() const
 {
     if (m_playSource && m_playSource->isPlaying()) {
@@ -163,8 +164,9 @@
 }
 
 void
-ViewManager::setPlaybackFrame(unsigned long f)
+ViewManager::setPlaybackFrame(sv_frame_t f)
 {
+    if (f < 0) f = 0;
     if (m_playbackFrame != f) {
 	m_playbackFrame = f;
 	emit playbackFrameChanged(f);
@@ -186,18 +188,38 @@
     m_playbackModel = model;
 }
 
-size_t
-ViewManager::alignPlaybackFrameToReference(size_t frame) const
+sv_frame_t
+ViewManager::alignPlaybackFrameToReference(sv_frame_t frame) const
 {
-    if (!m_playbackModel) return frame;
-    else return m_playbackModel->alignToReference(frame);
+#ifdef DEBUG_VIEW_MANAGER
+    cerr << "ViewManager::alignPlaybackFrameToReference(" << frame << "): playback model is " << m_playbackModel << endl;
+#endif
+    if (!m_playbackModel) {
+        return frame;
+    } else {
+        sv_frame_t f = m_playbackModel->alignToReference(frame);
+#ifdef DEBUG_VIEW_MANAGER
+        cerr << "aligned frame = " << f << endl;
+#endif
+        return f;
+    }
 }
 
-size_t
-ViewManager::alignReferenceToPlaybackFrame(size_t frame) const
+sv_frame_t
+ViewManager::alignReferenceToPlaybackFrame(sv_frame_t frame) const
 {
-    if (!m_playbackModel) return frame;
-    else return m_playbackModel->alignFromReference(frame);
+#ifdef DEBUG_VIEW_MANAGER
+    cerr << "ViewManager::alignReferenceToPlaybackFrame(" << frame << "): playback model is " << m_playbackModel << endl;
+#endif
+    if (!m_playbackModel) {
+        return frame;
+    } else {
+        sv_frame_t f = m_playbackModel->alignFromReference(frame);
+#ifdef DEBUG_VIEW_MANAGER
+        cerr << "aligned frame = " << f << endl;
+#endif
+        return f;
+    }
 }
 
 bool
@@ -258,6 +280,14 @@
 }
 
 void
+ViewManager::addSelectionQuietly(const Selection &selection)
+{
+    MultiSelection ms(m_selections);
+    ms.addSelection(selection);
+    setSelections(ms, true);
+}
+
+void
 ViewManager::removeSelection(const Selection &selection)
 {
     MultiSelection ms(m_selections);
@@ -274,15 +304,18 @@
 }
 
 void
-ViewManager::setSelections(const MultiSelection &ms)
+ViewManager::setSelections(const MultiSelection &ms, bool quietly)
 {
     if (m_selections.getSelections() == ms.getSelections()) return;
     SetSelectionCommand *command = new SetSelectionCommand(this, ms);
     CommandHistory::getInstance()->addCommand(command);
+    if (!quietly) {
+        emit selectionChangedByUser();
+    }
 }
 
-size_t
-ViewManager::constrainFrameToSelection(size_t frame) const
+sv_frame_t
+ViewManager::constrainFrameToSelection(sv_frame_t frame) const
 {
     MultiSelection::SelectionList sl = getSelections();
     if (sl.empty()) return frame;
@@ -341,7 +374,7 @@
 }
 
 Selection
-ViewManager::getContainingSelection(size_t frame, bool defaultToFollowing) const
+ViewManager::getContainingSelection(sv_frame_t frame, bool defaultToFollowing) const
 {
     return m_selections.getContainingSelection(frame, defaultToFollowing);
 }
@@ -360,9 +393,32 @@
     case DrawMode: emit activity(tr("Enter Draw mode")); break;
     case EraseMode: emit activity(tr("Enter Erase mode")); break;
     case MeasureMode: emit activity(tr("Enter Measure mode")); break;
+    case NoteEditMode: emit activity(tr("Enter NoteEdit mode")); break; // GF: NoteEditMode activity (I'm not yet certain why we need to emit this.)
     };
 }
 
+ViewManager::ToolMode
+ViewManager::getToolModeFor(const View *v) const
+{
+    if (m_toolModeOverrides.find(v) == m_toolModeOverrides.end()) {
+        return getToolMode();
+    } else {
+        return m_toolModeOverrides.find(v)->second;
+    }
+}
+
+void
+ViewManager::setToolModeFor(const View *v, ToolMode mode)
+{
+    m_toolModeOverrides[v] = mode;
+}
+
+void
+ViewManager::clearToolModeOverrides()
+{
+    m_toolModeOverrides.clear();
+}
+
 void
 ViewManager::setPlayLoopMode(bool mode)
 {
@@ -423,7 +479,7 @@
     }
 }
 
-size_t 
+sv_samplerate_t 
 ViewManager::getPlaybackSampleRate() const
 {
     if (m_playSource) {
@@ -432,7 +488,7 @@
     return 0;
 }
 
-size_t
+sv_samplerate_t
 ViewManager::getOutputSampleRate() const
 {
     if (m_playSource) {
@@ -504,7 +560,7 @@
 }
 
 void
-ViewManager::viewCentreFrameChanged(unsigned long f, bool locked,
+ViewManager::viewCentreFrameChanged(sv_frame_t f, bool locked,
                                     PlaybackFollowMode mode)
 {
     View *v = dynamic_cast<View *>(sender());
@@ -528,23 +584,22 @@
         }
     }
 
-    if (mode == PlaybackIgnore) {
-        return;
+    if (mode == PlaybackScrollPageWithCentre ||
+        mode == PlaybackScrollContinuous) {
+        seek(f);
     }
-
-    seek(f);
 }
 
 void
-ViewManager::seek(unsigned long f)
+ViewManager::seek(sv_frame_t f)
 {
 #ifdef DEBUG_VIEW_MANAGER 
     cerr << "ViewManager::seek(" << f << ")" << endl;
 #endif
 
     if (m_playSource && m_playSource->isPlaying()) {
-	unsigned long playFrame = m_playSource->getCurrentPlayingFrame();
-	unsigned long diff = std::max(f, playFrame) - std::min(f, playFrame);
+	sv_frame_t playFrame = m_playSource->getCurrentPlayingFrame();
+	sv_frame_t diff = std::max(f, playFrame) - std::min(f, playFrame);
 	if (diff > 20000) {
 	    m_playbackFrame = f;
 	    m_playSource->play(f);
@@ -562,7 +617,7 @@
 }
 
 void
-ViewManager::viewZoomLevelChanged(unsigned long z, bool locked)
+ViewManager::viewZoomLevelChanged(int z, bool locked)
 {
     View *v = dynamic_cast<View *>(sender());
 
@@ -646,11 +701,13 @@
         m_lightPalette = QApplication::palette();
     }
 
+#ifndef Q_OS_MAC
     if (dark) {
         QApplication::setPalette(m_darkPalette);
     } else {
         QApplication::setPalette(m_lightPalette);
     }
+#endif
 }
 
 bool
@@ -664,3 +721,23 @@
     return dark;
 }
 
+int
+ViewManager::scalePixelSize(int pixels)
+{
+    static double ratio = 0.0;
+    if (ratio == 0.0) {
+        double baseEm;
+#ifdef Q_OS_MAC
+        baseEm = 17.0;
+#else
+        baseEm = 15.0;
+#endif
+        double em = QFontMetrics(QFont()).height();
+        ratio = em / baseEm;
+    }
+
+    int scaled = int(pixels * ratio + 0.5);
+//    cerr << "scaledSize: " << pixels << " -> " << scaled << " at ratio " << ratio << endl;
+    if (pixels != 0 && scaled == 0) scaled = 1;
+    return scaled;
+}
--- a/view/ViewManager.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/view/ViewManager.h	Wed Apr 20 12:06:28 2016 +0100
@@ -26,13 +26,37 @@
 #include "base/Selection.h"
 #include "base/Command.h"
 #include "base/Clipboard.h"
+#include "base/BaseTypes.h"
 
 class AudioPlaySource;
 class Model;
 
 enum PlaybackFollowMode {
+
+    /**
+     * View scrolls continuously during playback, keeping the playback
+     * position at the centre.
+     */
     PlaybackScrollContinuous,
+
+    /**
+     * View follows playback page-by-page, but dragging the view
+     * relocates playback to the centre frame. This is the classic
+     * Sonic Visualiser behaviour.
+     */
+    PlaybackScrollPageWithCentre,
+
+    /**
+     * View follows playback page-by-page, and the play head is moved
+     * (by the user) separately from dragging the view. This is
+     * roughly the behaviour of a typical DAW or audio editor.
+     */
     PlaybackScrollPage,
+
+    /**
+     * View is detached from playback. It doesn't follow playback, and
+     * dragging the view does not affect the play head.
+     */
     PlaybackIgnore
 };
 
@@ -59,17 +83,17 @@
 
     bool isPlaying() const;
 
-    unsigned long getGlobalCentreFrame() const; // the set method is a slot
-    unsigned long getGlobalZoom() const;
+    sv_frame_t getGlobalCentreFrame() const; // the set method is a slot
+    int getGlobalZoom() const;
 
-    unsigned long getPlaybackFrame() const; // the set method is a slot
+    sv_frame_t getPlaybackFrame() const; // the set method is a slot
 
     // Only meaningful in solo mode, and used for optional alignment feature
     Model *getPlaybackModel() const;
     void setPlaybackModel(Model *);
 
-    size_t alignPlaybackFrameToReference(size_t) const;
-    size_t alignReferenceToPlaybackFrame(size_t) const;
+    sv_frame_t alignPlaybackFrameToReference(sv_frame_t) const;
+    sv_frame_t alignReferenceToPlaybackFrame(sv_frame_t) const;
 
     bool haveInProgressSelection() const;
     const Selection &getInProgressSelection(bool &exclusive) const;
@@ -83,7 +107,14 @@
     void addSelection(const Selection &selection);
     void removeSelection(const Selection &selection);
     void clearSelections();
-    size_t constrainFrameToSelection(size_t frame) const;
+    sv_frame_t constrainFrameToSelection(sv_frame_t frame) const;
+
+    /**
+     * Adding a selection normally emits the selectionChangedByUser
+     * signal. Call this to add a selection without emitting that signal.
+     * This is used in session file load, for example.
+     */
+    void addSelectionQuietly(const Selection &selection);
 
     /**
      * Return the selection that contains a given frame.
@@ -91,7 +122,7 @@
      * selected area, return the next selection after the given frame.
      * Return the empty selection if no appropriate selection is found.
      */
-    Selection getContainingSelection(size_t frame, bool defaultToFollowing) const;
+    Selection getContainingSelection(sv_frame_t frame, bool defaultToFollowing) const;
 
     Clipboard &getClipboard() { return m_clipboard; }
 
@@ -101,11 +132,19 @@
         EditMode,
 	DrawMode,
 	EraseMode,
-	MeasureMode
+	MeasureMode,
+	NoteEditMode //GF: Tonioni: this tool mode will be context sensitive.
     };
     ToolMode getToolMode() const { return m_toolMode; }
     void setToolMode(ToolMode mode);
 
+    /// Override the tool mode for a specific view 
+    void setToolModeFor(const View *v, ToolMode mode);
+    /// Return override mode if it exists for this view or global mode otherwise
+    ToolMode getToolModeFor(const View *v) const;
+    /// Clear all current view-specific overrides
+    void clearToolModeOverrides();
+
     bool getPlayLoopMode() const { return m_playLoopMode; }
     void setPlayLoopMode(bool on);
 
@@ -120,6 +159,7 @@
 
     void setIlluminateLocalFeatures(bool i) { m_illuminateLocalFeatures = i; }
     void setShowWorkTitle(bool show) { m_showWorkTitle = show; }
+    void setShowDuration(bool show) { m_showDuration = show; }
 
     /**
      * The sample rate that is used for playback.  This is usually the
@@ -127,27 +167,35 @@
      * differ from this will play back at the wrong speed -- there is
      * no per-model resampler.
      */
-    size_t getPlaybackSampleRate() const;
+    sv_samplerate_t getPlaybackSampleRate() const;
 
     /**
      * The sample rate of the audio output device.  If the playback
      * sample rate differs from this, everything will be resampled at
      * the output stage.
      */
-    size_t getOutputSampleRate() const;
+    sv_samplerate_t getOutputSampleRate() const;
 
     /**
      * The sample rate of the current main model.  This may in theory
      * differ from the playback sample rate, in which case even the
      * main model will play at the wrong speed.
      */
-    size_t getMainModelSampleRate() const { return m_mainModelSampleRate; }
+    sv_samplerate_t getMainModelSampleRate() const { return m_mainModelSampleRate; }
 
-    void setMainModelSampleRate(size_t sr) { m_mainModelSampleRate = sr; }
+    void setMainModelSampleRate(sv_samplerate_t sr) { m_mainModelSampleRate = sr; }
 
+    /**
+     * Take a "design pixel" size and scale it for the actual
+     * display. This is relevant to hi-dpi systems that do not do
+     * pixel doubling (i.e. Windows and Linux rather than OS/X).
+     */
+    int scalePixelSize(int pixels);
+    
     enum OverlayMode {
         NoOverlays,
-        MinimalOverlays,
+        GlobalOverlays,
+        StandardOverlays,
         AllOverlays
     };
     void setOverlayMode(OverlayMode mode);
@@ -157,7 +205,7 @@
     bool shouldShowCentreLine() const { return m_showCentreLine; }
 
     bool shouldShowDuration() const {
-        return m_overlayMode != NoOverlays;
+        return m_overlayMode != NoOverlays && m_showDuration;
     }
     bool shouldShowFrameCount() const {
         return m_showCentreLine && shouldShowDuration();
@@ -169,7 +217,7 @@
         return m_overlayMode == AllOverlays;
     }
     bool shouldShowSelectionExtents() const {
-        return m_overlayMode != NoOverlays;
+        return m_overlayMode != NoOverlays && m_overlayMode != GlobalOverlays;
     }
     bool shouldShowLayerNames() const {
         return m_overlayMode == AllOverlays;
@@ -183,6 +231,9 @@
     bool shouldIlluminateLocalFeatures() const {
         return m_illuminateLocalFeatures;
     }
+    bool shouldShowFeatureLabels() const {
+        return m_overlayMode != NoOverlays && m_overlayMode != GlobalOverlays;
+    }
 
     void setZoomWheelsEnabled(bool enable);
     bool getZoomWheelsEnabled() const { return m_zoomWheelsEnabled; }
@@ -192,23 +243,28 @@
 
 signals:
     /** Emitted when user causes the global centre frame to change. */
-    void globalCentreFrameChanged(unsigned long frame);
+    void globalCentreFrameChanged(sv_frame_t frame);
 
     /** Emitted when user scrolls a view, but doesn't affect global centre. */
-    void viewCentreFrameChanged(View *v, unsigned long frame);
+    void viewCentreFrameChanged(View *v, sv_frame_t frame);
 
     /** Emitted when a view zooms. */
-    void viewZoomLevelChanged(View *v, unsigned long zoom, bool locked);
+    void viewZoomLevelChanged(View *v, int zoom, bool locked);
 
     /** Emitted when the playback frame changes. */
-    void playbackFrameChanged(unsigned long frame);
+    void playbackFrameChanged(sv_frame_t frame);
 
     /** Emitted when the output levels change. Values in range 0.0 -> 1.0. */
     void outputLevelsChanged(float left, float right);
 
-    /** Emitted when the selection has changed. */
+    /** Emitted whenever the selection has changed. */
     void selectionChanged();
 
+    /** Emitted when the selection has been changed through an
+     * explicit selection-editing action. *Not* emitted when the
+     * selection has been changed through undo or redo. */
+    void selectionChangedByUser();
+
     /** Emitted when the in-progress (rubberbanding) selection has changed. */
     void inProgressSelectionChanged();
 
@@ -244,24 +300,24 @@
     void activity(QString);
 
 public slots:
-    void viewCentreFrameChanged(unsigned long, bool, PlaybackFollowMode);
-    void viewZoomLevelChanged(unsigned long, bool);
-    void setGlobalCentreFrame(unsigned long);
-    void setPlaybackFrame(unsigned long);
+    void viewCentreFrameChanged(sv_frame_t, bool, PlaybackFollowMode);
+    void viewZoomLevelChanged(int, bool);
+    void setGlobalCentreFrame(sv_frame_t);
+    void setPlaybackFrame(sv_frame_t);
     void playStatusChanged(bool playing);
 
 protected slots:
     void checkPlayStatus();
-    void seek(unsigned long);
-//!!!    void considerZoomChange(void *, unsigned long, bool);
+    void seek(sv_frame_t);
+//!!!    void considerZoomChange(void *, int, bool);
 
 protected:
     AudioPlaySource *m_playSource;
-    unsigned long m_globalCentreFrame;
-    unsigned long m_globalZoom;
-    mutable unsigned long m_playbackFrame;
+    sv_frame_t m_globalCentreFrame;
+    int m_globalZoom;
+    mutable sv_frame_t m_playbackFrame;
     Model *m_playbackModel; //!!!
-    size_t m_mainModelSampleRate;
+    sv_samplerate_t m_mainModelSampleRate;
 
     float m_lastLeft;
     float m_lastRight;
@@ -273,13 +329,14 @@
     Clipboard m_clipboard;
 
     ToolMode m_toolMode;
+    std::map<const View *, ToolMode> m_toolModeOverrides;
 
     bool m_playLoopMode;
     bool m_playSelectionMode;
     bool m_playSoloMode;
     bool m_alignMode;
 
-    void setSelections(const MultiSelection &ms);
+    void setSelections(const MultiSelection &ms, bool quietly = false);
     void signalSelectionChange();
 
     class SetSelectionCommand : public Command
@@ -302,6 +359,7 @@
     bool m_showCentreLine;
     bool m_illuminateLocalFeatures;
     bool m_showWorkTitle;
+    bool m_showDuration;
 
     QPalette m_lightPalette;
     QPalette m_darkPalette;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/ViewProxy.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,156 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef VIEW_PROXY_H
+#define VIEW_PROXY_H
+
+#include "LayerGeometryProvider.h"
+
+class ViewProxy : public LayerGeometryProvider
+{
+public:
+    ViewProxy(View *view, int scaleFactor) :
+	m_view(view), m_scaleFactor(scaleFactor) { }
+
+    virtual int getId() const {
+        return m_view->getId();
+    }
+    virtual sv_frame_t getStartFrame() const {
+	return m_view->getStartFrame();
+    }
+    virtual sv_frame_t getCentreFrame() const {
+	return m_view->getCentreFrame();
+    }
+    virtual sv_frame_t getEndFrame() const {
+	return m_view->getEndFrame();
+    }
+    virtual int getXForFrame(sv_frame_t frame) const {
+        //!!! not actually correct, if frame lies between view's pixels
+	return m_scaleFactor * m_view->getXForFrame(frame);
+    }
+    virtual sv_frame_t getFrameForX(int x) const {
+        sv_frame_t f0 = m_view->getFrameForX(x / m_scaleFactor);
+        if (m_scaleFactor == 1) return f0;
+        sv_frame_t f1 = m_view->getFrameForX((x / m_scaleFactor) + 1);
+        return f0 + ((f1 - f0) * (x % m_scaleFactor)) / m_scaleFactor;
+    }
+    virtual int getXForViewX(int viewx) const {
+        return viewx * m_scaleFactor;
+    }
+    virtual int getViewXForX(int x) const {
+        return x / m_scaleFactor;
+    }
+    virtual sv_frame_t getModelsStartFrame() const {
+	return m_view->getModelsStartFrame();
+    }
+    virtual sv_frame_t getModelsEndFrame() const {
+	return m_view->getModelsEndFrame();
+    }
+    virtual double getYForFrequency(double frequency,
+				    double minFreq, double maxFreq, 
+                                    bool logarithmic) const {
+	return m_scaleFactor *
+	    m_view->getYForFrequency(frequency, minFreq, maxFreq, logarithmic);
+    }
+    virtual double getFrequencyForY(int y, double minFreq, double maxFreq,
+				    bool logarithmic) const {
+        double f0 = m_view->getFrequencyForY
+            (y / m_scaleFactor, minFreq, maxFreq, logarithmic);
+        if (m_scaleFactor == 1) return f0;
+        double f1 = m_view->getFrequencyForY
+            ((y / m_scaleFactor) + 1, minFreq, maxFreq, logarithmic);
+        return f0 + ((f1 - f0) * (y % m_scaleFactor)) / m_scaleFactor;
+    }
+    virtual int getTextLabelHeight(const Layer *layer, QPainter &paint) const {
+	return m_scaleFactor * m_view->getTextLabelHeight(layer, paint);
+    }
+    virtual bool getValueExtents(QString unit, double &min, double &max,
+                                 bool &log) const {
+	return m_view->getValueExtents(unit, min, max, log);
+    }
+    virtual int getZoomLevel() const {
+	int z = m_view->getZoomLevel();
+//	cerr << "getZoomLevel: from " << z << " to ";
+	z = z / m_scaleFactor;
+//	cerr << z << endl;
+        if (z < 1) z = 1;
+	return z;
+    }
+    virtual QRect getPaintRect() const {
+	QRect r = m_view->getPaintRect();
+	return QRect(r.x() * m_scaleFactor,
+		     r.y() * m_scaleFactor,
+		     r.width() * m_scaleFactor,
+		     r.height() * m_scaleFactor);
+    }
+    virtual QSize getPaintSize() const {
+        return getPaintRect().size();
+    }
+    virtual int getPaintWidth() const { 
+        return getPaintRect().width();
+    }
+    virtual int getPaintHeight() const { 
+        return getPaintRect().height();
+    }
+    virtual bool hasLightBackground() const {
+	return m_view->hasLightBackground();
+    }
+    virtual QColor getForeground() const {
+	return m_view->getForeground();
+    }
+    virtual QColor getBackground() const {
+	return m_view->getBackground();
+    }
+    virtual ViewManager *getViewManager() const {
+	return m_view->getViewManager();
+    }
+	
+    virtual bool shouldIlluminateLocalFeatures(const Layer *layer,
+                                               QPoint &point) const {
+        QPoint p;
+	bool should = m_view->shouldIlluminateLocalFeatures(layer, p);
+        point = QPoint(p.x() * m_scaleFactor, p.y() * m_scaleFactor);
+        return should;
+    }
+
+    virtual bool shouldShowFeatureLabels() const {
+	return m_view->shouldShowFeatureLabels();
+    }
+
+    virtual void drawVisibleText(QPainter &p, int x, int y,
+				 QString text, TextStyle style) const {
+	m_view->drawVisibleText(p, x, y, text, style);
+    }
+
+    virtual void drawMeasurementRect(QPainter &p, const Layer *layer,
+                                     QRect rect, bool focus) const {
+	m_view->drawMeasurementRect(p, layer, rect, focus);
+    }
+
+    virtual void updatePaintRect(QRect r) {
+        m_view->update(r.x() / m_scaleFactor,
+                       r.y() / m_scaleFactor,
+                       r.width() / m_scaleFactor,
+                       r.height() / m_scaleFactor);
+    }
+    
+    virtual View *getView() { return m_view; }
+    virtual const View *getView() const { return m_view; }
+
+private:
+    View *m_view;
+    int m_scaleFactor;
+};
+
+#endif
--- a/view/view.pro	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-TEMPLATE = lib
-
-SV_UNIT_PACKAGES =
-load(../prf/sv.prf)
-
-CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
-QT += xml
-
-TARGET = svview
-
-DEPENDPATH += . ..
-INCLUDEPATH += . ..
-OBJECTS_DIR = tmp_obj
-MOC_DIR = tmp_moc
-
-# Input
-HEADERS += Overview.h \
-           Pane.h \
-           PaneStack.h \
-           View.h \
-           ViewManager.h
-SOURCES += Overview.cpp \
-           Pane.cpp \
-           PaneStack.cpp \
-           View.cpp \
-           ViewManager.cpp
--- a/widgets/ActivityLog.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/ActivityLog.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -25,6 +25,13 @@
 
 #include <iostream>
 
+#include "base/Debug.h"
+
+using std::cerr;
+using std::endl;
+
+//#define PRINT_ACTIVITY 1
+
 ActivityLog::ActivityLog() : QDialog()
 {
     setWindowTitle(tr("Activity Log"));
@@ -53,9 +60,16 @@
 ActivityLog::activityHappened(QString name)
 {
     name = name.replace("&", "");
-//    SVDEBUG << "ActivityLog::activityHappened(" << name << ")" << endl;
+
+#ifdef PRINT_ACTIVITY
+    cerr << "ActivityLog: " << name;
     if (name == m_prevName) {
-//        cerr << "(ignoring duplicate)" << endl;
+        cerr << " (duplicate)";
+    }
+    cerr << endl;
+#endif
+
+    if (name == m_prevName) {
         return;
     }
     m_prevName = name;
--- a/widgets/AudioDial.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/AudioDial.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -121,10 +121,10 @@
 
     QPainter paint;
 
-    float angle = AUDIO_DIAL_MIN // offset
+    double angle = AUDIO_DIAL_MIN // offset
 	+ (AUDIO_DIAL_RANGE *
-	   (float(QDial::value() - QDial::minimum()) /
-	    (float(QDial::maximum() - QDial::minimum()))));
+	   (double(QDial::value() - QDial::minimum()) /
+	    (double(QDial::maximum() - QDial::minimum()))));
     int degrees = int(angle * 180.0 / M_PI);
 
     int ns = notchSize();
@@ -231,31 +231,31 @@
 	c = c.light(110);
     }
 
-    // Scale shadow...
+    // Scale shadow, omitting the bottom part...
 
     shadowAngle = 2160;
-    c = palette().dark().color();
-    for (int arc = 120; arc < 2880; arc += 240) {
+    c = palette().shadow().color();
+    for (int i = 0; i < 5; ++i) {
 	pen.setColor(c);
 	paint.setPen(pen);
+        int arc = i * 240 + 120;
 	paint.drawArc(scale/2, scale/2,
 		      width-scale, width-scale, shadowAngle + arc, 240);
+	c = c.light(110);
+    }
+    c = palette().shadow().color();
+    for (int i = 0; i < 12; ++i) {
+	pen.setColor(c);
+	paint.setPen(pen);
+        int arc = i * 240 + 120;
 	paint.drawArc(scale/2, scale/2,
 		      width-scale, width-scale, shadowAngle - arc, 240);
-	c = c.light(108);
+	c = c.light(110);
     }
 
-    // Undraw the bottom part...
-
-    pen.setColor(palette().background().color());
-    pen.setWidth(scale * 4);
-    paint.setPen(pen);
-    paint.drawArc(scale/2, scale/2,
-		  width-scale, width-scale, -45 * 16, -92 * 16);
-
     // Scale ends...
 
-    pen.setColor(palette().dark().color());
+    pen.setColor(palette().shadow().color());
     pen.setWidth(scale);
     paint.setPen(pen);
     for (int i = 0; i < numTicks; ++i) {
@@ -268,15 +268,15 @@
 
     // Pointer notch...
 
-    float hyp = float(width) / 2.0;
-    float len = hyp - indent;
+    double hyp = double(width) / 2.0;
+    double len = hyp - indent;
     --len;
 
-    float x0 = hyp;
-    float y0 = hyp;
+    double x0 = hyp;
+    double y0 = hyp;
 
-    float x = hyp - len * sin(angle);
-    float y = hyp + len * cos(angle);
+    double x = hyp - len * sin(angle);
+    double y = hyp + len * cos(angle);
 
     c = palette().dark().color();
     pen.setColor(isEnabled() ? c.dark(130) : c);
@@ -289,27 +289,27 @@
 
 
 void AudioDial::drawTick(QPainter &paint,
-			 float angle, int size, bool internal)
+			 double angle, int size, bool internal)
 {
-    float hyp = float(size) / 2.0;
-    float x0 = hyp - (hyp - 1) * sin(angle);
-    float y0 = hyp + (hyp - 1) * cos(angle);
+    double hyp = double(size) / 2.0;
+    double x0 = hyp - (hyp - 1) * sin(angle);
+    double y0 = hyp + (hyp - 1) * cos(angle);
 
 //    cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl;
     
     if (internal) {
 
-	float len = hyp / 4;
-	float x1 = hyp - (hyp - len) * sin(angle);
-	float y1 = hyp + (hyp - len) * cos(angle);
+	double len = hyp / 4;
+	double x1 = hyp - (hyp - len) * sin(angle);
+	double y1 = hyp + (hyp - len) * cos(angle);
 		
 	paint.drawLine(int(x0), int(y0), int(x1), int(y1));
 
     } else {
 
-	float len = hyp / 4;
-	float x1 = hyp - (hyp + len) * sin(angle);
-	float y1 = hyp + (hyp + len) * cos(angle);
+	double len = hyp / 4;
+	double x1 = hyp - (hyp + len) * sin(angle);
+	double y1 = hyp + (hyp + len) * cos(angle);
 
 	paint.drawLine(int(x0), int(y0), int(x1), int(y1));
     }
@@ -350,7 +350,7 @@
     updateMappedValue(value);
 }
 
-void AudioDial::setDefaultMappedValue(float value)
+void AudioDial::setDefaultMappedValue(double value)
 {
     m_defaultMappedValue = value;
     if (m_rangeMapper) {
@@ -358,7 +358,7 @@
     }
 }
 
-void AudioDial::setMappedValue(float mappedValue)
+void AudioDial::setMappedValue(double mappedValue)
 {
     if (m_rangeMapper) {
         int newPosition = m_rangeMapper->getPositionForValue(mappedValue);
@@ -387,7 +387,7 @@
 }
 
 
-float AudioDial::mappedValue() const
+double AudioDial::mappedValue() const
 {
     if (m_rangeMapper) {
 //        SVDEBUG << "AudioDial::mappedValue(): value = " << value() << ", mappedValue = " << m_mappedValue << endl;
@@ -464,11 +464,11 @@
 
     if (m_rangeMapper) {
         
-        float min = m_rangeMapper->getValueForPosition(minimum());
-        float max = m_rangeMapper->getValueForPosition(maximum());
+        double min = m_rangeMapper->getValueForPosition(minimum());
+        double max = m_rangeMapper->getValueForPosition(maximum());
         
         if (min > max) { 
-            float tmp = min;
+            double tmp = min;
             min = max;
             max = tmp;
         }
@@ -494,7 +494,7 @@
             }
         }
         
-        float newValue = QInputDialog::getDouble
+        double newValue = QInputDialog::getDouble
             (this,
              tr("Enter new value"),
              text,
--- a/widgets/AudioDial.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/AudioDial.h	Wed Apr 20 12:06:28 2016 +0100
@@ -73,7 +73,7 @@
 
     void setRangeMapper(RangeMapper *mapper); // I take ownership, will delete
     const RangeMapper *rangeMapper() const { return m_rangeMapper; }
-    float mappedValue() const;
+    double mappedValue() const;
 
     int defaultValue() const { return m_defaultValue; }
 
@@ -107,14 +107,14 @@
 
     void setValue(int value);
 
-    void setDefaultMappedValue(float mappedValue);
+    void setDefaultMappedValue(double mappedValue);
 
-    void setMappedValue(float mappedValue);
+    void setMappedValue(double mappedValue);
 
     void setToDefault();
 
 protected:
-    void drawTick(QPainter &paint, float angle, int size, bool internal);
+    void drawTick(QPainter &paint, double angle, int size, bool internal);
     virtual void paintEvent(QPaintEvent *);
 
     // Alternate mouse behavior event handlers.
@@ -133,8 +133,8 @@
     QColor m_meterColor;
     
     int m_defaultValue;
-    float m_defaultMappedValue;
-    float m_mappedValue;
+    double m_defaultMappedValue;
+    double m_mappedValue;
     bool m_noMappedUpdate;
 
     // Alternate mouse behavior tracking.
--- a/widgets/CSVFormatDialog.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/CSVFormatDialog.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -30,6 +30,7 @@
 #include <QDialogButtonBox>
 
 #include <iostream>
+#include <cmath>
 
 #include "base/Debug.h"
 
@@ -62,7 +63,7 @@
     exampleFrame->setPalette(palette);
 
     QFont fp;
-    fp.setPointSize(fp.pointSize() * 0.9);
+    fp.setPointSize(int(floor(fp.pointSize() * 0.9)));
 //    fp.setFixedPitch(true);
 //    fp.setStyleHint(QFont::TypeWriter);
 //    fp.setFamily("Monospaced");
@@ -94,10 +95,14 @@
         cpc->addItem(tr("End time")); // ColumnEndTime
         cpc->addItem(tr("Duration")); // ColumnDuration
         cpc->addItem(tr("Value"));    // ColumnValue
+        cpc->addItem(tr("Pitch"));    // ColumnPitch
         cpc->addItem(tr("Label"));    // ColumnLabel
         cpc->setCurrentIndex(int(m_format.getColumnPurpose(i)));
 
         for (int j = 0; j < example.size() && j < 6; ++j) {
+            if (i >= example[j].size()) {
+                continue;
+            }
             QLabel *label = new QLabel;
             label->setTextFormat(Qt::PlainText);
             QString text = TextAbbrev::abbreviate(example[j][i], 35);
@@ -116,26 +121,48 @@
     layout->addWidget(new QLabel(tr("Timing is specified:")), row, 0);
     
     m_timingTypeCombo = new QComboBox;
-    m_timingTypeCombo->addItem(tr("Explicitly, in seconds"));
-    m_timingTypeCombo->addItem(tr("Explicitly, in audio sample frames"));
-    m_timingTypeCombo->addItem(tr("Implicitly: rows are equally spaced in time"));
+
+    m_timingLabels = {
+        { TimingExplicitSeconds, tr("Explicitly, in seconds") },
+        { TimingExplicitMsec, tr("Explicitly, in milliseconds") },
+        { TimingExplicitSamples, tr("Explicitly, in audio sample frames") },
+        { TimingImplicit, tr("Implicitly: rows are equally spaced in time") }
+    };
+
+    for (auto &l: m_timingLabels) {
+        m_timingTypeCombo->addItem(l.second);
+    }
+
     layout->addWidget(m_timingTypeCombo, row++, 1, 1, 2);
+
     connect(m_timingTypeCombo, SIGNAL(activated(int)),
 	    this, SLOT(timingTypeChanged(int)));
-    m_timingTypeCombo->setCurrentIndex
-        (m_format.getTimingType() == CSVFormat::ExplicitTiming ?
-         m_format.getTimeUnits() == CSVFormat::TimeSeconds ? 0 : 1 : 2);
+
+    m_initialTimingOption = TimingImplicit;
+    if (m_format.getTimingType() == CSVFormat::ExplicitTiming) {
+        switch (m_format.getTimeUnits()) {
+        case CSVFormat::TimeSeconds:
+            m_initialTimingOption = TimingExplicitSeconds; break;
+        case CSVFormat::TimeMilliseconds:
+            m_initialTimingOption = TimingExplicitMsec; break;
+        case CSVFormat::TimeAudioFrames:
+            m_initialTimingOption = TimingExplicitSamples; break;
+        case CSVFormat::TimeWindows:
+            m_initialTimingOption = TimingImplicit; break;
+        }
+    }
+    m_timingTypeCombo->setCurrentIndex(int(m_initialTimingOption));
 
     m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):"));
     layout->addWidget(m_sampleRateLabel, row, 0);
     
-    size_t sampleRates[] = {
+    int sampleRates[] = {
 	8000, 11025, 12000, 22050, 24000, 32000,
 	44100, 48000, 88200, 96000, 176400, 192000
     };
 
     m_sampleRateCombo = new QComboBox;
-    for (size_t i = 0; i < sizeof(sampleRates) / sizeof(sampleRates[0]); ++i) {
+    for (int i = 0; i < int(sizeof(sampleRates) / sizeof(sampleRates[0])); ++i) {
 	m_sampleRateCombo->addItem(QString("%1").arg(sampleRates[i]));
 	if (sampleRates[i] == m_format.getSampleRate()) {
             m_sampleRateCombo->setCurrentIndex(i);
@@ -183,7 +210,6 @@
     setLayout(layout);
 
     timingTypeChanged(m_timingTypeCombo->currentIndex());
-    updateModelLabel();
 }
 
 CSVFormatDialog::~CSVFormatDialog()
@@ -212,6 +238,9 @@
     case CSVFormat::TwoDimensionalModelWithDuration:
         s = f->getLayerPresentationName(LayerFactory::Regions);
         break;
+    case CSVFormat::TwoDimensionalModelWithDurationAndPitch:
+        s = f->getLayerPresentationName(LayerFactory::Notes);
+        break;
     case CSVFormat::ThreeDimensionalModel:
         s = f->getLayerPresentationName(LayerFactory::Colour3DPlot);
         break;
@@ -221,37 +250,72 @@
 }
 
 void
+CSVFormatDialog::applyStartTimePurpose()
+{
+    // First check if we already have any. NB there may be fewer than
+    // m_format.getColumnCount() elements in m_columnPurposeCombos
+    // (because of the fuzzy column behaviour). Note also that the
+    // fuzzy column (which is the one just showing how many more
+    // columns there are) has a different combo with only two items
+    // (ignore or Values)
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        if (i == m_fuzzyColumn) continue;
+        QComboBox *cb = m_columnPurposeCombos[i];
+        if (cb->currentIndex() == int(CSVFormat::ColumnStartTime)) {
+            return;
+        }
+    }
+    // and if not, select one
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        if (i == m_fuzzyColumn) continue;
+        QComboBox *cb = m_columnPurposeCombos[i];
+        if (cb->currentIndex() == int(CSVFormat::ColumnValue)) {
+            cb->setCurrentIndex(int(CSVFormat::ColumnStartTime));
+            return;
+        }
+    }
+}
+
+void
+CSVFormatDialog::removeStartTimePurpose()
+{
+    // NB there may be fewer than m_format.getColumnCount() elements
+    // in m_columnPurposeCombos (because of the fuzzy column
+    // behaviour)
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        if (i == m_fuzzyColumn) continue;
+        QComboBox *cb = m_columnPurposeCombos[i];
+        if (cb->currentIndex() == int(CSVFormat::ColumnStartTime)) {
+            cb->setCurrentIndex(int(CSVFormat::ColumnValue));
+        }
+    }
+}
+
+void
+CSVFormatDialog::updateComboVisibility()
+{
+    bool wantRate = (m_format.getTimingType() == CSVFormat::ImplicitTiming ||
+                     m_format.getTimeUnits() == CSVFormat::TimeAudioFrames);
+    bool wantWindow = (m_format.getTimingType() == CSVFormat::ImplicitTiming);
+    
+    m_sampleRateCombo->setEnabled(wantRate);
+    m_sampleRateLabel->setEnabled(wantRate);
+
+    m_windowSizeCombo->setEnabled(wantWindow);
+    m_windowSizeLabel->setEnabled(wantWindow);
+}
+
+void
 CSVFormatDialog::timingTypeChanged(int type)
 {
-    switch (type) {
-
-    case 0:
-	m_format.setTimingType(CSVFormat::ExplicitTiming);
-	m_format.setTimeUnits(CSVFormat::TimeSeconds);
-	m_sampleRateCombo->setEnabled(false);
-	m_sampleRateLabel->setEnabled(false);
-	m_windowSizeCombo->setEnabled(false);
-	m_windowSizeLabel->setEnabled(false);
-	break;
-
-    case 1:
-	m_format.setTimingType(CSVFormat::ExplicitTiming);
-	m_format.setTimeUnits(CSVFormat::TimeAudioFrames);
-	m_sampleRateCombo->setEnabled(true);
-	m_sampleRateLabel->setEnabled(true);
-	m_windowSizeCombo->setEnabled(false);
-	m_windowSizeLabel->setEnabled(false);
-	break;
-
-    case 2:
-	m_format.setTimingType(CSVFormat::ImplicitTiming);
-	m_format.setTimeUnits(CSVFormat::TimeWindows);
-	m_sampleRateCombo->setEnabled(true);
-	m_sampleRateLabel->setEnabled(true);
-	m_windowSizeCombo->setEnabled(true);
-	m_windowSizeLabel->setEnabled(true);
-	break;
+    // Update any column purpose combos
+    if (TimingOption(type) == TimingImplicit) {
+        removeStartTimePurpose();
+    } else {
+        applyStartTimePurpose();
     }
+    updateFormatFromDialog();
+    updateComboVisibility();
 }
 
 void
@@ -274,44 +338,31 @@
 CSVFormatDialog::columnPurposeChanged(int p)
 {
     QObject *o = sender();
-
     QComboBox *cb = qobject_cast<QComboBox *>(o);
     if (!cb) return;
 
     CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)p;
 
-    bool haveStartTime = false;
-    bool haveDuration = false;
-    int valueCount = 0;
-
+    bool haveStartTime = false; // so as to update timing type combo appropriately
+    
+    // Ensure the column purpose combos are consistent with one
+    // another, without reference to m_format (which we'll update
+    // separately)
+    
     for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
 
-        CSVFormat::ColumnPurpose cp = m_format.getColumnPurpose(i);
+        // The fuzzy column combo only has the entries <ignore> or
+        // Values, so it can't affect the timing type and none of this
+        // logic affects it
+        if (i == m_fuzzyColumn) continue;
 
-        bool thisChanged = (cb == m_columnPurposeCombos[i]);
+        QComboBox *thisCombo = m_columnPurposeCombos[i];
         
-        if (thisChanged) {
-
-            cerr << "i == " << i << ", fuzzy == " << m_fuzzyColumn
-                      << ", p == " << p << endl;
-
-            if (i == m_fuzzyColumn) {
-                for (int j = i; j < m_format.getColumnCount(); ++j) {
-                    if (p == 0) { // Ignore
-                        m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown);
-                    } else { // Value
-                        m_format.setColumnPurpose(j, CSVFormat::ColumnValue);
-                        ++valueCount;
-                    }
-                }
-                continue;
-            }
-
-            cp = purpose;
-
-        } else {
-
-            if (i == m_fuzzyColumn) continue;
+        CSVFormat::ColumnPurpose cp = (CSVFormat::ColumnPurpose)
+            (thisCombo->currentIndex());
+        bool thisChanged = (cb == thisCombo);
+        
+        if (!thisChanged) {
 
             // We can only have one ColumnStartTime column, and only
             // one of either ColumnDuration or ColumnEndTime
@@ -334,30 +385,107 @@
                     cp = CSVFormat::ColumnUnknown;
                 }
             }
+
+            if (cp == CSVFormat::ColumnStartTime) {
+                haveStartTime = true;
+            }
+        
+            thisCombo->setCurrentIndex(int(cp));
+
+        } else {
+            if (purpose == CSVFormat::ColumnStartTime) {
+                haveStartTime = true;
+            }
         }
-
-        if (cp == CSVFormat::ColumnStartTime) {
-            haveStartTime = true;
-        }
-        if (cp == CSVFormat::ColumnEndTime ||
-            cp == CSVFormat::ColumnDuration) {
-            haveDuration = true;
-        }
-        if (cp == CSVFormat::ColumnValue) {
-            ++valueCount;
-        }
-
-        m_columnPurposeCombos[i]->setCurrentIndex(int(cp));
-        m_format.setColumnPurpose(i, cp);
     }
 
     if (!haveStartTime) {
-        m_timingTypeCombo->setCurrentIndex(2);
-        timingTypeChanged(2);
+        m_timingTypeCombo->setCurrentIndex(int(TimingImplicit));
+    } else if (m_timingTypeCombo->currentIndex() == int(TimingImplicit)) {
+        if (m_initialTimingOption == TimingImplicit) {
+            m_timingTypeCombo->setCurrentIndex(TimingExplicitSeconds);
+        } else {
+            m_timingTypeCombo->setCurrentIndex(m_initialTimingOption);
+        }
+    }
+
+    updateFormatFromDialog();
+    updateComboVisibility();
+}
+
+void
+CSVFormatDialog::updateFormatFromDialog()
+{
+    switch (TimingOption(m_timingTypeCombo->currentIndex())) {
+
+    case TimingExplicitSeconds:
+	m_format.setTimingType(CSVFormat::ExplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeSeconds);
+	break;
+
+    case TimingExplicitMsec:
+	m_format.setTimingType(CSVFormat::ExplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeMilliseconds);
+	break;
+
+    case TimingExplicitSamples:
+	m_format.setTimingType(CSVFormat::ExplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeAudioFrames);
+	break;
+
+    case TimingImplicit:
+	m_format.setTimingType(CSVFormat::ImplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeWindows);
+	break;
+    }
+
+    bool haveStartTime = false;
+    bool haveDuration = false;
+    bool havePitch = false;
+    int valueCount = 0;
+
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+
+        QComboBox *thisCombo = m_columnPurposeCombos[i];
+        
+        CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)
+            (thisCombo->currentIndex());
+
+        if (i == m_fuzzyColumn) {
+            for (int j = i; j < m_format.getColumnCount(); ++j) {
+                if (purpose == CSVFormat::ColumnUnknown) {
+                    m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown);
+                } else { // Value
+                    m_format.setColumnPurpose(j, CSVFormat::ColumnValue);
+                    ++valueCount;
+                }
+            }
+        } else {
+        
+            if (purpose == CSVFormat::ColumnStartTime) {
+                haveStartTime = true;
+            }
+            if (purpose == CSVFormat::ColumnEndTime ||
+                purpose == CSVFormat::ColumnDuration) {
+                haveDuration = true;
+            }
+            if (purpose == CSVFormat::ColumnPitch) {
+                havePitch = true;
+            }
+            if (purpose == CSVFormat::ColumnValue) {
+                ++valueCount;
+            }
+
+            m_format.setColumnPurpose(i, purpose);
+        }
     }
 
     if (haveStartTime && haveDuration) {
-        m_format.setModelType(CSVFormat::TwoDimensionalModelWithDuration);
+        if (havePitch) {
+            m_format.setModelType(CSVFormat::TwoDimensionalModelWithDurationAndPitch);
+        } else {
+            m_format.setModelType(CSVFormat::TwoDimensionalModelWithDuration);
+        }
     } else {
         if (valueCount > 1) {
             m_format.setModelType(CSVFormat::ThreeDimensionalModel);
@@ -372,3 +500,4 @@
 }
 
 
+
--- a/widgets/CSVFormatDialog.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/CSVFormatDialog.h	Wed Apr 20 12:06:28 2016 +0100
@@ -40,11 +40,26 @@
     void sampleRateChanged(QString);
     void windowSizeChanged(QString);
     void columnPurposeChanged(int purpose);
+
+    void updateFormatFromDialog();
     void updateModelLabel();
 
 protected:
     CSVFormat m_format;
     int m_maxDisplayCols;
+
+    enum TimingOption {
+        TimingExplicitSeconds = 0,
+        TimingExplicitMsec,
+        TimingExplicitSamples,
+        TimingImplicit
+    };
+    std::map<TimingOption, QString> m_timingLabels;
+    TimingOption m_initialTimingOption;
+
+    void updateComboVisibility();
+    void applyStartTimePurpose();
+    void removeStartTimePurpose();
     
     QComboBox *m_timingTypeCombo;
     QLabel *m_sampleRateLabel;
--- a/widgets/CommandHistory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/CommandHistory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -26,6 +26,8 @@
 
 #include "base/Command.h"
 
+#include "IconLoader.h"
+
 #include <QRegExp>
 #include <QMenu>
 #include <QToolBar>
@@ -53,12 +55,16 @@
     m_bundleTimer(0),
     m_bundleTimeout(3000)
 {
-    m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
+    IconLoader loader;
+    QIcon undoIcon(loader.load("undo"));
+    QIcon redoIcon(loader.load("redo"));
+    
+    m_undoAction = new QAction(undoIcon, ("&Undo"), this);
     m_undoAction->setShortcut(tr("Ctrl+Z"));
     m_undoAction->setStatusTip(tr("Undo the last editing operation"));
     connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
     
-    m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
+    m_undoMenuAction = new QAction(undoIcon, tr("&Undo"), this);
     connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo()));
     
     m_undoMenu = new QMenu(tr("&Undo"));
@@ -66,12 +72,12 @@
     connect(m_undoMenu, SIGNAL(triggered(QAction *)),
 	    this, SLOT(undoActivated(QAction*)));
 
-    m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
+    m_redoAction = new QAction(redoIcon, tr("Re&do"), this);
     m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
     m_redoAction->setStatusTip(tr("Redo the last operation that was undone"));
     connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
     
-    m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
+    m_redoMenuAction = new QAction(redoIcon, tr("Re&do"), this);
     connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo()));
 
     m_redoMenu = new QMenu(tr("Re&do"));
@@ -101,7 +107,7 @@
 CommandHistory::clear()
 {
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::clear()" << endl;
+    cerr << "CommandHistory::clear()" << endl;
 #endif
     closeBundle();
     m_savedAt = -1;
@@ -143,7 +149,7 @@
     if (!command) return;
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::addCommand: " << command->getName() << " of type " << typeid(*command).name() << " at " << command << ": execute = " << execute << ", bundle = " << bundle << " (m_currentCompound = " << m_currentCompound << ", m_currentBundle = " << m_currentBundle << ")" << endl;
+    cerr << "CommandHistory::addCommand: " << command->getName() << " of type " << typeid(*command).name() << " at " << command << ": execute = " << execute << ", bundle = " << bundle << " (m_currentCompound = " << m_currentCompound << ", m_currentBundle = " << m_currentBundle << ")" << endl;
 #endif
 
     if (m_currentCompound) {
@@ -160,7 +166,7 @@
 
 #ifdef DEBUG_COMMAND_HISTORY
     if (!m_redoStack.empty()) {
-        SVDEBUG << "CommandHistory::clearing redo stack" << endl;
+        cerr << "CommandHistory::clearing redo stack" << endl;
     }
 #endif
 
@@ -192,8 +198,8 @@
     if (m_currentBundle) {
 	if (!command || (command->getName() != m_currentBundleName)) {
 #ifdef DEBUG_COMMAND_HISTORY
-            SVDEBUG << "CommandHistory::addToBundle: "
-                      << command->getName()                      << ": closing current bundle" << endl;
+            cerr << "CommandHistory::addToBundle: " << command->getName()
+                 << ": closing current bundle" << endl;
 #endif
 	    closeBundle();
 	}
@@ -204,8 +210,8 @@
     if (!m_currentBundle) {
 
 #ifdef DEBUG_COMMAND_HISTORY
-        SVDEBUG << "CommandHistory::addToBundle: "
-                  << command->getName()                  << ": creating new bundle" << endl;
+        cerr << "CommandHistory::addToBundle: " << command->getName()
+             << ": creating new bundle" << endl;
 #endif
 
 	// need to addCommand before setting m_currentBundle, as addCommand
@@ -219,8 +225,8 @@
     }
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::addToBundle: "
-              << command->getName()              << ": adding to bundle" << endl;
+    cerr << "CommandHistory::addToBundle: " << command->getName()
+         << ": adding to bundle" << endl;
 #endif
 
     if (execute) command->execute();
@@ -242,11 +248,12 @@
 void
 CommandHistory::closeBundle()
 {
+    if (m_currentBundle) {
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::closeBundle" << endl;
+        cerr << "CommandHistory::closeBundle" << endl;
 #endif
-
-    if (m_currentBundle) emit activity(m_currentBundle->getName());
+        emit activity(m_currentBundle->getName());
+    }
     m_currentBundle = 0;
     m_currentBundleName = "";
 }
@@ -255,7 +262,7 @@
 CommandHistory::bundleTimerTimeout()
 {
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::bundleTimerTimeout: bundle is " << m_currentBundle << endl;
+    cerr << "CommandHistory::bundleTimerTimeout: bundle is " << m_currentBundle << endl;
 #endif
 
     closeBundle();
@@ -264,14 +271,15 @@
 void
 CommandHistory::addToCompound(Command *command, bool execute)
 {
-#ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::addToCompound: " << command->getName() << endl;
-#endif
     if (!m_currentCompound) {
-	SVDEBUG << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << endl;
+	cerr << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << endl;
         return;
     }
 
+#ifdef DEBUG_COMMAND_HISTORY
+    cerr << "CommandHistory::addToCompound[" << m_currentCompound->getName() << "]: " << command->getName() << " (exec: " << execute << ")" << endl;
+#endif
+
     if (execute) command->execute();
     m_currentCompound->addCommand(command);
 }
@@ -280,13 +288,17 @@
 CommandHistory::startCompoundOperation(QString name, bool execute)
 {
     if (m_currentCompound) {
-	SVDEBUG << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << endl;
+	cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << endl;
 	cerr << "(name is " << m_currentCompound->getName() << ")" << endl;
         return;
     }
  
+#ifdef DEBUG_COMMAND_HISTORY
+    cerr << "CommandHistory::startCompoundOperation: " << name << " (exec: " << execute << ")" << endl;
+#endif
+   
     closeBundle();
-   
+
     m_currentCompound = new MacroCommand(name);
     m_executeCompound = execute;
 }
@@ -295,9 +307,13 @@
 CommandHistory::endCompoundOperation()
 {
     if (!m_currentCompound) {
-	SVDEBUG << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << endl;
+	cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << endl;
         return;
     }
+ 
+#ifdef DEBUG_COMMAND_HISTORY
+    cerr << "CommandHistory::endCompoundOperation: " << m_currentCompound->getName() << endl;
+#endif
 
     MacroCommand *toAdd = m_currentCompound;
     m_currentCompound = 0;
@@ -329,7 +345,7 @@
     if (m_undoStack.empty()) return;
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::undo()" << endl;
+    cerr << "CommandHistory::undo()" << endl;
 #endif
 
     closeBundle();
@@ -355,7 +371,7 @@
     if (m_redoStack.empty()) return;
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::redo()" << endl;
+    cerr << "CommandHistory::redo()" << endl;
 #endif
 
     closeBundle();
@@ -410,14 +426,14 @@
 CommandHistory::documentSaved()
 {
     closeBundle();
-    m_savedAt = m_undoStack.size();
+    m_savedAt = int(m_undoStack.size());
 }
 
 void
 CommandHistory::clipCommands()
 {
-    if ((int)m_undoStack.size() > m_undoLimit) {
-	m_savedAt -= (m_undoStack.size() - m_undoLimit);
+    if (int(m_undoStack.size()) > m_undoLimit) {
+	m_savedAt -= (int(m_undoStack.size()) - m_undoLimit);
     }
 
     clipStack(m_undoStack, m_undoLimit);
@@ -436,7 +452,7 @@
 	for (i = 0; i < limit; ++i) {
 #ifdef DEBUG_COMMAND_HISTORY
 	    Command *command = stack.top();
-	    SVDEBUG << "CommandHistory::clipStack: Saving recent command: " << command->getName() << " at " << command << endl;
+	    cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName() << " at " << command << endl;
 #endif
 	    tempStack.push(stack.top());
 	    stack.pop();
@@ -458,7 +474,7 @@
 	Command *command = stack.top();
 	// Not safe to call getName() on a command about to be deleted
 #ifdef DEBUG_COMMAND_HISTORY
-	SVDEBUG << "CommandHistory::clearStack: About to delete command " << command << endl;
+	cerr << "CommandHistory::clearStack: About to delete command " << command << endl;
 #endif
 	delete command;
 	stack.pop();
--- a/widgets/Fader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/Fader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -48,7 +48,9 @@
     m_value(1.0),
     m_peakLeft(0.0),
     m_peakRight(0.0),
-    m_mousePressed(false)
+    m_mousePressed(false),
+    m_mousePressX(0),
+    m_mousePressValue(0)
 {
     setMinimumSize(116, 23);
     setMaximumSize(116, 23);
@@ -108,7 +110,7 @@
     if (vx > getMaxX()) vx = getMaxX();
     if (vx < 0) vx = 0;
 
-    float fval = AudioLevel::fader_to_multiplier
+    float fval = (float)AudioLevel::fader_to_multiplier
 	(vx, getMaxX(), AudioLevel::LongFader);
 
     setValue(fval);
@@ -130,20 +132,20 @@
 Fader::mouseDoubleClickEvent(QMouseEvent *)
 {
     bool ok = false;
-    float min = AudioLevel::fader_to_dB
+    float min = (float)AudioLevel::fader_to_dB
         (0, getMaxX(), AudioLevel::LongFader);
-    float max = AudioLevel::fader_to_dB
+    float max = (float)AudioLevel::fader_to_dB
         (getMaxX(), getMaxX(), AudioLevel::LongFader);
-    float deft = AudioLevel::multiplier_to_dB(m_value);
+    float deft = (float)AudioLevel::multiplier_to_dB(m_value);
 
-    float dB = QInputDialog::getDouble
+    float dB = (float)QInputDialog::getDouble
         (this,
          tr("Enter new fader level"),
          tr("New fader level, from %1 to %2 dBFS:").arg(min).arg(max),
          deft, min, max, 3, &ok);
 
     if (ok) {
-        float value = AudioLevel::dB_to_multiplier(dB);
+        float value = (float)AudioLevel::dB_to_multiplier(dB);
         setValue(value);
         emit valueChanged(value);
         update();
@@ -177,9 +179,9 @@
     //!!! needs improvement
 
     if (ev->delta() > 0) {
-	setValue(m_value * 1.1);
+	setValue(m_value * 1.f);
     } else {
-	setValue(m_value / 1.1);
+	setValue(m_value / 1.f);
     }
 
     update();
@@ -201,7 +203,7 @@
 void
 Fader::setValue(float v)
 {
-    float max = AudioLevel::dB_to_multiplier(10.0);
+    float max = (float)AudioLevel::dB_to_multiplier(10.0);
 
     if (v > max) {
 	v = max;
@@ -211,7 +213,7 @@
 
     if (m_value != v) {
 	m_value = v;
-	float db = AudioLevel::multiplier_to_dB(m_value);
+	float db = (float)AudioLevel::multiplier_to_dB(m_value);
         QString text;
 	if (db <= AudioLevel::DB_FLOOR) {
             text = tr("Level: Off");
--- a/widgets/IconLoader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/IconLoader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -19,8 +19,17 @@
 #include <QApplication>
 #include <QPainter>
 #include <QPalette>
+#include <QFile>
+#include <QSvgRenderer>
 
-static const char *autoInvertExceptions[] = {
+#include <vector>
+#include <set>
+
+#include "base/Debug.h"
+
+using namespace std;
+
+static set<QString> autoInvertExceptions {
     // These are the icons that look OK in their default colours, even
     // in a colour scheme with a black background.  (They may also be
     // icons that would look worse if we tried to auto-invert them.)
@@ -29,16 +38,13 @@
     // supply inverted versions -- the loader will load xx_inverse.png
     // in preference to xx.png if a dark background is found.)
     "fileclose",
-    "filenew-22",
     "filenew",
-    "fileopen-22",
     "fileopen",
     "fileopenaudio",
     "fileopensession",
-    "filesave-22",
     "filesave",
-    "filesaveas-22",
     "filesaveas",
+    "filesaveas-sv",
     "help",
     "editcut",
     "editcopy",
@@ -51,42 +57,121 @@
     "zoom"
 };
 
+static vector<int> sizes { 0, 16, 22, 24, 32, 48, 64, 128 };
+
 QIcon
 IconLoader::load(QString name)
 {
-    QPixmap pmap(loadPixmap(name));
-    if (pmap.isNull()) return QIcon();
-    else return QIcon(pmap);
+    QIcon icon;
+    for (int sz: sizes) {
+        QPixmap pmap(loadPixmap(name, sz));
+        if (!pmap.isNull()) icon.addPixmap(pmap);
+    }
+    return icon;
+}
+
+bool
+IconLoader::shouldInvert() const
+{
+    QColor bg = QApplication::palette().window().color();
+    bool darkBackground = (bg.red() + bg.green() + bg.blue() <= 384);
+    return darkBackground;
+}
+
+bool
+IconLoader::shouldAutoInvert(QString name) const
+{
+    if (shouldInvert()) {
+        return (autoInvertExceptions.find(name) == autoInvertExceptions.end());
+    } else {
+        return false;
+    }
 }
 
 QPixmap
-IconLoader::loadPixmap(QString name)
+IconLoader::loadPixmap(QString name, int size)
 {
-    QColor bg = QApplication::palette().window().color();
-    if (bg.red() + bg.green() + bg.blue() > 384) { // light background
-        QPixmap pmap(QString(":icons/%1").arg(name));
-        if (pmap.isNull()) {
-            pmap = QPixmap(QString(":icons/%1.png").arg(name));
-        }
-        return pmap;
+    bool invert = shouldInvert();
+
+    QString scalableName, nonScalableName;
+    QPixmap pmap;
+
+    nonScalableName = makeNonScalableFilename(name, size, invert);
+    pmap = QPixmap(nonScalableName);
+    if (!pmap.isNull()) return pmap;
+
+    if (size > 0) {
+        scalableName = makeScalableFilename(name, invert);
+        pmap = loadScalable(scalableName, size);
+        if (!pmap.isNull()) return pmap;
     }
 
-    QPixmap pmap(QString(":icons/%1").arg(name));
-    if (pmap.isNull()) {
-        pmap = QPixmap(QString(":icons/%1_inverse.png").arg(name));
-        if (pmap.isNull()) {
-            pmap = QPixmap(QString(":icons/%1.png").arg(name));
-        }
-    }
-    if (pmap.isNull()) return pmap;
+    if (invert && shouldAutoInvert(name)) {
 
-    for (int i = 0; i < sizeof(autoInvertExceptions)/
-                        sizeof(autoInvertExceptions[0]); ++i) {
-        if (autoInvertExceptions[i] == name) {
-            return pmap;
+        nonScalableName = makeNonScalableFilename(name, size, false);
+        pmap = QPixmap(nonScalableName);
+        if (!pmap.isNull()) return invertPixmap(pmap);
+
+        if (size > 0) {
+            scalableName = makeScalableFilename(name, false);
+            pmap = loadScalable(scalableName, size);
+            if (!pmap.isNull()) return invertPixmap(pmap);
         }
     }
 
+    return QPixmap();
+}
+
+QPixmap
+IconLoader::loadScalable(QString name, int size)
+{
+    if (!QFile(name).exists()) {
+//        cerr << "loadScalable: no such file as: \"" << name << "\"" << endl;
+        return QPixmap();
+    }
+    QPixmap pmap(size, size);
+    pmap.fill(Qt::transparent);
+    QSvgRenderer renderer(name);
+    QPainter painter;
+    painter.begin(&pmap);
+//    cerr << "calling renderer for " << name << " at size " << size << "..." << endl;
+    renderer.render(&painter);
+//    cerr << "renderer completed" << endl;
+    painter.end();
+    return pmap;
+}
+
+QString
+IconLoader::makeNonScalableFilename(QString name, int size, bool invert)
+{
+    if (invert) {
+        if (size == 0) {
+            return QString(":icons/%1_inverse.png").arg(name);
+        } else {
+            return QString(":icons/%1-%2_inverse.png").arg(name).arg(size);
+        }
+    } else {
+        if (size == 0) {
+            return QString(":icons/%1.png").arg(name);
+        } else {
+            return QString(":icons/%1-%2.png").arg(name).arg(size);
+        }
+    }
+}
+
+QString
+IconLoader::makeScalableFilename(QString name, bool invert)
+{
+    if (invert) {
+        return QString(":icons/scalable/%1_inverse.svg").arg(name);
+    } else {
+        return QString(":icons/scalable/%1.svg").arg(name);
+    }
+}
+
+QPixmap
+IconLoader::invertPixmap(QPixmap pmap)
+{
     // No suitable inverted icon found for black background; try to
     // auto-invert the default one
 
--- a/widgets/IconLoader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/IconLoader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -25,7 +25,15 @@
     virtual ~IconLoader() { }
 
     QIcon load(QString name);
-    QPixmap loadPixmap(QString name);
+
+private:
+    bool shouldInvert() const;
+    bool shouldAutoInvert(QString) const;
+    QPixmap loadPixmap(QString, int);
+    QPixmap loadScalable(QString, int);
+    QPixmap invertPixmap(QPixmap);
+    QString makeScalableFilename(QString, bool);
+    QString makeNonScalableFilename(QString, int, bool);
 };
 
 #endif
--- a/widgets/InteractiveFileFinder.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/InteractiveFileFinder.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -33,7 +33,9 @@
 InteractiveFileFinder::m_instance;
 
 InteractiveFileFinder::InteractiveFileFinder() :
-    m_lastLocatedLocation("")
+    m_sessionExtension("sv"),
+    m_lastLocatedLocation(""),
+    m_parent(0)
 {
     SVDEBUG << "Registering interactive file finder" << endl;
     FileFinder::registerFileFinder(this);
@@ -43,10 +45,22 @@
 {
 }
 
+void
+InteractiveFileFinder::setParentWidget(QWidget *parent)
+{
+    getInstance()->m_parent = parent;
+}
+
+void
+InteractiveFileFinder::setApplicationSessionExtension(QString extension)
+{
+    m_sessionExtension = extension;
+}
+
 QString
 InteractiveFileFinder::getOpenFileName(FileType type, QString fallbackLocation)
 {
-    QString settingsKey;
+    QString settingsKeyStub;
     QString lastPath = fallbackLocation;
     
     QString title = tr("Select file");
@@ -55,41 +69,60 @@
     switch (type) {
 
     case SessionFile:
-        settingsKey = "sessionpath";
+        settingsKeyStub = "session";
         title = tr("Select a session file");
-        filter = tr("Sonic Visualiser session files (*.sv)\nRDF files (%1)\nAll files (*.*)").arg(RDFImporter::getKnownExtensions());
+        filter = tr("%1 session files (*.%1)\nRDF files (%3)\nAll files (*.*)")
+            .arg(QApplication::applicationName())
+            .arg(m_sessionExtension)
+            .arg(RDFImporter::getKnownExtensions());
         break;
 
     case AudioFile:
-        settingsKey = "audiopath";
+        settingsKeyStub = "audio";
         title = "Select an audio file";
         filter = tr("Audio files (%1)\nAll files (*.*)")
             .arg(AudioFileReaderFactory::getKnownExtensions());
         break;
 
     case LayerFile:
-        settingsKey = "layerpath";
+        settingsKeyStub = "layer";
         filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
             .arg(DataFileReaderFactory::getKnownExtensions())
             .arg(RDFImporter::getKnownExtensions());
         break;
 
     case LayerFileNoMidi:
-        settingsKey = "layerpath";
+        settingsKeyStub = "layer";
         filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nText files (*.txt)\nAll files (*.*)")
             .arg(DataFileReaderFactory::getKnownExtensions())
             .arg(RDFImporter::getKnownExtensions());
         break;
 
+    case LayerFileNonSV:
+        settingsKeyStub = "layer";
+        filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
+            .arg(DataFileReaderFactory::getKnownExtensions())
+            .arg(RDFImporter::getKnownExtensions());
+        break;
+
+    case LayerFileNoMidiNonSV:
+        settingsKeyStub = "layer";
+        filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nText files (*.txt)\nAll files (*.*)")
+            .arg(DataFileReaderFactory::getKnownExtensions())
+            .arg(RDFImporter::getKnownExtensions());
+        break;
+
     case SessionOrAudioFile:
-        settingsKey = "lastpath";
-        filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)")
+        settingsKeyStub = "last";
+        filter = tr("All supported files (*.sv %1 %2)\n%3 session files (*.%4)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)")
             .arg(RDFImporter::getKnownExtensions())
-            .arg(AudioFileReaderFactory::getKnownExtensions());
+            .arg(AudioFileReaderFactory::getKnownExtensions())
+            .arg(QApplication::applicationName())
+            .arg(m_sessionExtension);
         break;
 
     case ImageFile:
-        settingsKey = "imagepath";
+        settingsKeyStub = "image";
         {
             QStringList fmts;
             QList<QByteArray> formats = QImageReader::supportedImageFormats();
@@ -103,22 +136,24 @@
         break;
 
     case CSVFile:
-        settingsKey = "layerpath";
+        settingsKeyStub = "layer";
         filter = tr("Comma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)");
         break;
 
     case IMAFile:
-        settingsKey = "imafpath";
+        settingsKeyStub = "imaf";
         title = "Select an IMAF file";
         filter = tr("IMAF files (*.ima)\nAll files (*.*)");
         break;
 
     case AnyFile:
-        settingsKey = "lastpath";
-        filter = tr("All supported files (*.sv %1 %2 %3)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nRDF files (%3)\nAll files (*.*)")
+        settingsKeyStub = "last";
+        filter = tr("All supported files (*.sv %1 %2 %3)\n%4 session files (*.%5)\nAudio files (%1)\nLayer files (%2)\nRDF files (%3)\nAll files (*.*)")
             .arg(AudioFileReaderFactory::getKnownExtensions())
             .arg(DataFileReaderFactory::getKnownExtensions())
-            .arg(RDFImporter::getKnownExtensions());
+            .arg(RDFImporter::getKnownExtensions())
+            .arg(QApplication::applicationName())
+            .arg(m_sessionExtension);
         break;
     };
 
@@ -134,13 +169,13 @@
 
     QSettings settings;
     settings.beginGroup("FileFinder");
-    lastPath = settings.value(settingsKey, lastPath).toString();
+    lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
 
     QString path = "";
 
     // Use our own QFileDialog just for symmetry with getSaveFileName below
 
-    QFileDialog dialog;
+    QFileDialog dialog(m_parent);
     dialog.setNameFilters(filter.split('\n'));
     dialog.setWindowTitle(title);
     dialog.setDirectory(lastPath);
@@ -187,7 +222,7 @@
     }
 
     if (path != "") {
-        settings.setValue(settingsKey,
+        settings.setValue(settingsKeyStub + "path",
                           QFileInfo(path).absoluteDir().canonicalPath());
     }
     
@@ -195,9 +230,10 @@
 }
 
 QString
-InteractiveFileFinder::getSaveFileName(FileType type, QString fallbackLocation)
+InteractiveFileFinder::getSaveFileName(FileType type, 
+                                       QString fallbackLocation)
 {
-    QString settingsKey;
+    QString settingsKeyStub;
     QString lastPath = fallbackLocation;
     
     QString title = tr("Select file");
@@ -206,46 +242,63 @@
     switch (type) {
 
     case SessionFile:
-        settingsKey = "savesessionpath";
+        settingsKeyStub = "savesession";
         title = tr("Select a session file");
-        filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
+        filter = tr("%1 session files (*.%2)\nAll files (*.*)")
+            .arg(QApplication::applicationName()).arg(m_sessionExtension);
         break;
 
     case AudioFile:
-        settingsKey = "saveaudiopath";
+        settingsKeyStub = "saveaudio";
         title = "Select an audio file";
         title = tr("Select a file to export to");
         filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
         break;
 
     case LayerFile:
-        settingsKey = "savelayerpath";
+        settingsKeyStub = "savelayer";
         title = tr("Select a file to export to");
         filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
         break;
 
     case LayerFileNoMidi:
-        settingsKey = "savelayerpath";
+        settingsKeyStub = "savelayer";
         title = tr("Select a file to export to");
         filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
         break;
 
+    case LayerFileNonSV:
+        settingsKeyStub = "savelayer";
+        title = tr("Select a file to export to");
+        filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
+        break;
+
+    case LayerFileNoMidiNonSV:
+        settingsKeyStub = "savelayer";
+        title = tr("Select a file to export to");
+        filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
+        break;
+
     case SessionOrAudioFile:
         cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << endl;
         abort();
 
     case ImageFile:
-        settingsKey = "saveimagepath";
+        settingsKeyStub = "saveimage";
         title = tr("Select a file to export to");
         filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
         break;
 
     case CSVFile:
-        settingsKey = "savelayerpath";
+        settingsKeyStub = "savelayer";
         title = tr("Select a file to export to");
         filter = tr("Comma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
         break;
 
+    case IMAFile:
+        cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: IMAFile cannot be used here" << endl;
+        abort();
+
     case AnyFile:
         cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: AnyFile cannot be used here" << endl;
         abort();
@@ -263,30 +316,44 @@
 
     QSettings settings;
     settings.beginGroup("FileFinder");
-    lastPath = settings.value(settingsKey, lastPath).toString();
+    lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
 
     QString path = "";
 
     // Use our own QFileDialog instead of static functions, as we may
     // need to adjust the file extension based on the selected filter
 
-    QFileDialog dialog;
-    dialog.setNameFilters(filter.split('\n'));
+    QFileDialog dialog(m_parent);
+
+    QStringList filters = filter.split('\n');
+
+    dialog.setNameFilters(filters);
     dialog.setWindowTitle(title);
     dialog.setDirectory(lastPath);
-
     dialog.setAcceptMode(QFileDialog::AcceptSave);
     dialog.setFileMode(QFileDialog::AnyFile);
     dialog.setConfirmOverwrite(false); // we'll do that
-        
+    
+    QString defaultSuffix;
     if (type == SessionFile) {
-        dialog.setDefaultSuffix("sv");
+        defaultSuffix = m_sessionExtension;
     } else if (type == AudioFile) {
-        dialog.setDefaultSuffix("wav");
+        defaultSuffix = "wav";
     } else if (type == ImageFile) {
-        dialog.setDefaultSuffix("png");
+        defaultSuffix = "png";
     } else if (type == CSVFile) {
-        dialog.setDefaultSuffix("csv");
+        defaultSuffix = "csv";
+    }
+
+    defaultSuffix = 
+        settings.value(settingsKeyStub + "suffix", defaultSuffix).toString();
+
+    dialog.setDefaultSuffix(defaultSuffix);
+
+    foreach (QString f, filters) {
+        if (f.contains("." + defaultSuffix)) {
+            dialog.selectNameFilter(f);
+        }
     }
 
     bool good = false;
@@ -305,7 +372,8 @@
 
         cerr << "type = " << type << ", suffix = " << fi.suffix() << endl;
         
-        if ((type == LayerFile || type == LayerFileNoMidi)
+        if ((type == LayerFile || type == LayerFileNoMidi || 
+             type == LayerFileNonSV || type == LayerFileNoMidiNonSV)
             && fi.suffix() == "") {
             QString expectedExtension;
             QString selectedFilter = dialog.selectedNameFilter();
@@ -346,8 +414,10 @@
     }
         
     if (path != "") {
-        settings.setValue(settingsKey,
+        settings.setValue(settingsKeyStub + "path",
                           QFileInfo(path).absoluteDir().canonicalPath());
+        settings.setValue(settingsKeyStub + "suffix",
+                          QFileInfo(path).suffix());
     }
     
     return path;
@@ -356,39 +426,51 @@
 void
 InteractiveFileFinder::registerLastOpenedFilePath(FileType type, QString path)
 {
-    QString settingsKey;
+    QString settingsKeyStub;
 
     switch (type) {
     case SessionFile:
-        settingsKey = "sessionpath";
+        settingsKeyStub = "session";
         break;
 
     case AudioFile:
-        settingsKey = "audiopath";
+        settingsKeyStub = "audio";
         break;
 
     case LayerFile:
-        settingsKey = "layerpath";
+        settingsKeyStub = "layer";
         break;
 
     case LayerFileNoMidi:
-        settingsKey = "layerpath";
+        settingsKeyStub = "layer";
+        break;
+
+    case LayerFileNonSV:
+        settingsKeyStub = "layer";
+        break;
+
+    case LayerFileNoMidiNonSV:
+        settingsKeyStub = "layer";
         break;
 
     case SessionOrAudioFile:
-        settingsKey = "lastpath";
+        settingsKeyStub = "last";
         break;
 
     case ImageFile:
-        settingsKey = "imagepath";
+        settingsKeyStub = "image";
         break;
 
     case CSVFile:
-        settingsKey = "layerpath";
+        settingsKeyStub = "layer";
+        break;
+
+    case IMAFile:
+        settingsKeyStub = "imaf";
         break;
 
     case AnyFile:
-        settingsKey = "lastpath";
+        settingsKeyStub = "last";
         break;
     }
 
@@ -396,7 +478,9 @@
         QSettings settings;
         settings.beginGroup("FileFinder");
         path = QFileInfo(path).absoluteDir().canonicalPath();
-        settings.setValue(settingsKey, path);
+        QString suffix = QFileInfo(path).suffix();
+        settings.setValue(settingsKeyStub + "path", path);
+        settings.setValue(settingsKeyStub + "suffix", suffix);
         settings.setValue("lastpath", path);
     }
 }
--- a/widgets/InteractiveFileFinder.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/InteractiveFileFinder.h	Wed Apr 20 12:06:28 2016 +0100
@@ -18,6 +18,7 @@
 
 #include "data/fileio/FileFinder.h"
 
+#include <QApplication>
 #include <QString>
 #include <QObject>
 
@@ -29,12 +30,22 @@
 public:
     virtual ~InteractiveFileFinder();
 
+    /// Specify the extension for this application's session files
+    /// (without the dot)
+    void setApplicationSessionExtension(QString extension);
+
+    QString getApplicationSessionExtension() const {
+        return m_sessionExtension;
+    }
+
     QString getOpenFileName(FileType type, QString fallbackLocation = "");
     QString getSaveFileName(FileType type, QString fallbackLocation = "");
     void registerLastOpenedFilePath(FileType type, QString path);
 
     QString find(FileType type, QString location, QString lastKnownLocation = "");
 
+    static void setParentWidget(QWidget *);
+
     static InteractiveFileFinder *getInstance() { return &m_instance; }
 
 protected:
@@ -44,7 +55,10 @@
     QString findRelative(QString location, QString relativeTo);
     QString locateInteractive(FileType type, QString thing);
 
+    QString m_sessionExtension;
     QString m_lastLocatedLocation;
+
+    QWidget *m_parent;
 };
 
 #endif
--- a/widgets/ItemEditDialog.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/ItemEditDialog.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -28,10 +28,13 @@
 #include <float.h> // for FLT_MIN/MAX
 
 
-ItemEditDialog::ItemEditDialog(size_t sampleRate, int options,
+ItemEditDialog::ItemEditDialog(sv_samplerate_t sampleRate, int options,
                                QString valueUnits, QWidget *parent) :
     QDialog(parent),
     m_sampleRate(sampleRate),
+    m_defaultFrame(0),
+    m_defaultDuration(0),
+    m_defaultValue(0),
     m_frameTimeSpinBox(0),
     m_realTimeSecsSpinBox(0),
     m_realTimeUSecsSpinBox(0),
@@ -50,7 +53,7 @@
 
     int row = 0, subrow = 0;
 
-    size_t singleStep = RealTime::frame2RealTime(2, sampleRate).usec() - 1;
+    int singleStep = RealTime::frame2RealTime(2, sampleRate).usec() - 1;
 
     if ((options & ShowTime) || (options & ShowDuration)) {
 
@@ -190,19 +193,19 @@
 }
 
 void
-ItemEditDialog::setFrameTime(long frame)
+ItemEditDialog::setFrameTime(sv_frame_t frame)
 {
     if (!m_frameTimeSpinBox) return;
 
     RealTime rt(RealTime::frame2RealTime(frame, m_sampleRate));
     m_realTimeSecsSpinBox->setValue(rt.sec);
     m_realTimeUSecsSpinBox->setValue(rt.usec());
-    m_frameTimeSpinBox->setValue(frame);
+    m_frameTimeSpinBox->setValue(int(frame));
     m_defaultFrame = frame;
     m_resetButton->setEnabled(false);
 }
 
-long
+sv_frame_t
 ItemEditDialog::getFrameTime() const
 {
     return m_frameTimeSpinBox->value();
@@ -221,19 +224,19 @@
 }
 
 void
-ItemEditDialog::setFrameDuration(long duration)
+ItemEditDialog::setFrameDuration(sv_frame_t duration)
 {
     if (!m_frameDurationSpinBox) return;
 
     RealTime rt(RealTime::frame2RealTime(duration, m_sampleRate));
     m_realDurationSecsSpinBox->setValue(rt.sec);
     m_realDurationUSecsSpinBox->setValue(rt.usec());
-    m_frameDurationSpinBox->setValue(duration);
+    m_frameDurationSpinBox->setValue(int(duration));
     m_defaultDuration = duration;
     m_resetButton->setEnabled(false);
 }
 
-long
+sv_frame_t
 ItemEditDialog::getFrameDuration() const
 {
     return m_frameDurationSpinBox->value();
@@ -264,7 +267,7 @@
 float
 ItemEditDialog::getValue() const
 {
-    return m_valueSpinBox->value();
+    return float(m_valueSpinBox->value());
 }
 
 void
@@ -303,8 +306,8 @@
 {
     RealTime rt = getRealTime();
     rt.sec = i;
-    size_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
-    m_frameTimeSpinBox->setValue(frame);
+    sv_frame_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
+    m_frameTimeSpinBox->setValue(int(frame));
     m_resetButton->setEnabled(true);
 }
 
@@ -313,8 +316,8 @@
 {
     RealTime rt = getRealTime();
     rt.nsec = i * 1000;
-    size_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
-    m_frameTimeSpinBox->setValue(frame);
+    sv_frame_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
+    m_frameTimeSpinBox->setValue(int(frame));
     m_resetButton->setEnabled(true);
 }
 
@@ -338,8 +341,8 @@
 {
     RealTime rt = getRealDuration();
     rt.sec = i;
-    size_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
-    m_frameDurationSpinBox->setValue(frame);
+    sv_frame_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
+    m_frameDurationSpinBox->setValue(int(frame));
     m_resetButton->setEnabled(true);
 }
 
@@ -348,8 +351,8 @@
 {
     RealTime rt = getRealDuration();
     rt.nsec = i * 1000;
-    size_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
-    m_frameDurationSpinBox->setValue(frame);
+    sv_frame_t frame = RealTime::realTime2Frame(rt, m_sampleRate);
+    m_frameDurationSpinBox->setValue(int(frame));
     m_resetButton->setEnabled(true);
 }
 
--- a/widgets/ItemEditDialog.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/ItemEditDialog.h	Wed Apr 20 12:06:28 2016 +0100
@@ -37,17 +37,17 @@
         ShowText       = 1 << 3
     };
 
-    ItemEditDialog(size_t sampleRate, int options, QString valueUnits = "",
+    ItemEditDialog(sv_samplerate_t sampleRate, int options, QString valueUnits = "",
                    QWidget *parent = 0);
 
-    void setFrameTime(long frame);
-    long getFrameTime() const;
+    void setFrameTime(sv_frame_t frame);
+    sv_frame_t getFrameTime() const;
 
     void setRealTime(RealTime rt);
     RealTime getRealTime() const;
 
-    void setFrameDuration(long frame);
-    long getFrameDuration() const;
+    void setFrameDuration(sv_frame_t frame);
+    sv_frame_t getFrameDuration() const;
     
     void setRealDuration(RealTime rt);
     RealTime getRealDuration() const;
@@ -59,10 +59,10 @@
     QString getText() const;
 
 protected slots:
-    void frameTimeChanged(int);
+    void frameTimeChanged(int); // must be int as invoked from int signal
     void realTimeSecsChanged(int);
     void realTimeUSecsChanged(int);
-    void frameDurationChanged(int);
+    void frameDurationChanged(int); // must be int as invoked from int signal
     void realDurationSecsChanged(int);
     void realDurationUSecsChanged(int);
     void valueChanged(double);
@@ -70,9 +70,9 @@
     void reset();
 
 protected:
-    size_t m_sampleRate;
-    long m_defaultFrame;
-    long m_defaultDuration;
+    sv_samplerate_t m_sampleRate;
+    sv_frame_t m_defaultFrame;
+    sv_frame_t m_defaultDuration;
     float m_defaultValue;
     QString m_defaultText;
     QSpinBox *m_frameTimeSpinBox;
--- a/widgets/KeyReference.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/KeyReference.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -24,6 +24,7 @@
 #include <QDesktopWidget>
 
 KeyReference::KeyReference() :
+    m_text(0),
     m_dialog(0)
 {
 }
@@ -49,7 +50,7 @@
     QString name = action->text();
     if (overrideName != "") name = overrideName;
 
-    QString shortcut = action->shortcut().toString();
+    QString shortcut = action->shortcut().toString(QKeySequence::NativeText);
     QString tip = action->statusTip();
 
     registerShortcut(name, shortcut, tip);
@@ -87,6 +88,13 @@
 }
 
 void
+KeyReference::registerAlternativeShortcut(QAction *action, QKeySequence shortcut)
+{
+    QString name = action->text();
+    registerAlternativeShortcut(name, shortcut.toString(QKeySequence::NativeText));
+}
+
+void
 KeyReference::registerAlternativeShortcut(QString name, QString alternative)
 {
     name.replace(tr("&"), "");
@@ -102,6 +110,12 @@
 }
 
 void
+KeyReference::registerAlternativeShortcut(QString name, QKeySequence shortcut)
+{
+    registerAlternativeShortcut(name, shortcut.toString(QKeySequence::NativeText));
+}
+
+void
 KeyReference::show()
 {
     if (m_dialog) {
@@ -147,7 +161,7 @@
                 altdesc = tr("</b>&nbsp;(%1)<b>").arg(altdesc);
             }
 
-            text += QString("<tr><td>&nbsp;<b>%1%2</b></td><td>&nbsp;%3</td><td>%4</td></tr>\n")
+            text += QString("<tr><td width=\"12%\">&nbsp;<b>%1%2</b></td><td>&nbsp;%3</td><td>%4</td></tr>\n")
                 .arg(shortcut).arg(altdesc).arg(actionName).arg(tip);
         }
     }
@@ -159,7 +173,8 @@
     m_text->setReadOnly(true);
 
     m_dialog = new QDialog;
-    m_dialog->setWindowTitle(tr("Sonic Visualiser: Key and Mouse Reference"));
+    m_dialog->setWindowTitle(tr("%1: Key and Mouse Reference")
+                             .arg(QApplication::applicationName()));
 
     QVBoxLayout *layout = new QVBoxLayout;
     m_dialog->setLayout(layout);
--- a/widgets/KeyReference.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/KeyReference.h	Wed Apr 20 12:06:28 2016 +0100
@@ -20,6 +20,7 @@
 #include <QString>
 #include <vector>
 #include <map>
+#include <QKeySequence>
 
 class QAction;
 class QTextEdit;
@@ -38,9 +39,11 @@
 
     void registerShortcut(QAction *, QString overrideName = "");
     void registerAlternativeShortcut(QAction *, QString alternative);
+    void registerAlternativeShortcut(QAction *, QKeySequence alternative);
 
     void registerShortcut(QString actionName, QString shortcut, QString tipText);
     void registerAlternativeShortcut(QString actionName, QString alternative);
+    void registerAlternativeShortcut(QString actionName, QKeySequence alternative);
 
     void show();
     void hide();
--- a/widgets/LEDButton.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/LEDButton.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -38,8 +38,6 @@
 
     int dark_factor;
     QColor offcolor;
-    QPixmap *off_map;
-    QPixmap *on_map;
 };
 
 
@@ -51,8 +49,6 @@
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
     d->offcolor = col.dark(300);
-    d->off_map = 0;
-    d->on_map = 0;
     
     setColor(col);
 }
@@ -65,8 +61,6 @@
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
     d->offcolor = col.dark(300);
-    d->off_map = 0;
-    d->on_map = 0;
 
     setColor(col);
 }
@@ -78,16 +72,12 @@
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
     d->offcolor = col.dark(300);
-    d->off_map = 0;
-    d->on_map = 0;
 
     setColor(col);
 }
 
 LEDButton::~LEDButton()
 {
-    delete d->off_map;
-    delete d->on_map;
     delete d;
 }
 
@@ -135,40 +125,7 @@
     if (width < 0) 
 	width = 0;
 
-    QPixmap *tmpMap = 0;
-
-    if (led_state) {
-	if (d->on_map) {
-            if (d->on_map->size() == size()) {
-                paint.begin(this);
-                paint.drawPixmap(0, 0, *d->on_map);
-                paint.end();
-                return;
-            } else {
-                delete d->on_map;
-                d->on_map = 0;
-            }
-	}
-    } else {
-	if (d->off_map) {
-            if (d->off_map->size() == size()) {
-                paint.begin(this);
-                paint.drawPixmap(0, 0, *d->off_map);
-                paint.end();
-                return;
-            } else {
-                delete d->off_map;
-                d->off_map = 0;
-            }
-	}
-    }
-
-    int scale = 1;
-    width *= scale;
-
-    tmpMap = new QPixmap(width, width);
-    tmpMap->fill(palette().background().color());
-    paint.begin(tmpMap);
+    paint.begin(this);
 
     paint.setRenderHint(QPainter::Antialiasing, true);
 
@@ -182,14 +139,14 @@
     paint.setBrush(brush);
 
     // Draws a "flat" LED with the given color:
-    paint.drawEllipse( scale, scale, width - scale*2, width - scale*2 );
+    paint.drawEllipse( 1, 1, width - 2, width - 2 );
 
     // Draw the bright light spot of the LED now, using modified "old"
     // painter routine taken from KDEUI´s LEDButton widget:
 
     // Setting the new width of the pen is essential to avoid "pixelized"
     // shadow like it can be observed with the old LED code
-    pen.setWidth( 2 * scale );
+    pen.setWidth( 2 );
 
     // shrink the light on the LED to a size about 2/3 of the complete LED
     int pos = width/5 + 1;
@@ -217,12 +174,13 @@
 	pos++; light_width--;
     }
 
+    paint.drawPoint(pos, pos);
+
     // Drawing of bright spot finished, now draw a thin border
     // around the LED which resembles a shadow with light coming
     // from the upper left.
 
-//    pen.setWidth( 2 * scale + 1 ); // ### shouldn't this value be smaller for smaller LEDs?
-    pen.setWidth(2 * scale);
+    pen.setWidth(2);
     brush.setStyle(Qt::NoBrush);
     paint.setBrush(brush); // This avoids filling of the ellipse
 
@@ -235,36 +193,13 @@
     for (int arc = 120; arc < 2880; arc += 240) {
 	pen.setColor(color);
 	paint.setPen(pen);
-	int w = width - pen.width()/2 - scale + 1;
+	int w = width - pen.width()/2;
 	paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle + arc, 240);
 	paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle - arc, 240);
 	color = color.dark(110); //FIXME: this should somehow use the contrast value
     }	// end for ( angle = 720; angle < 6480; angle += 160 )
 
     paint.end();
-    //
-    // painting done
-
-    QPixmap *&dest = led_state ? d->on_map : d->off_map;
-
-    if (scale > 1) {
-
-	QImage i = tmpMap->toImage();
-	width /= scale;
-	delete tmpMap;
-	dest = new QPixmap(QPixmap::fromImage
-			   (i.scaled(width, width, 
-				     Qt::KeepAspectRatio,
-				     Qt::SmoothTransformation)));
-
-    } else {
-
-	dest = tmpMap;
-    }
-
-    paint.begin(this);
-    paint.drawPixmap(0, 0, *dest);
-    paint.end();
 }
 
 bool
@@ -303,10 +238,6 @@
     if(led_color!=col) {
 	led_color = col;
 	d->offcolor = col.dark(d->dark_factor);
-	delete d->on_map;
-	d->on_map = 0;
-	delete d->off_map;
-	d->off_map = 0;
 	update();
     }
 }
--- a/widgets/LayerTree.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/LayerTree.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -155,12 +155,12 @@
 }
 
 void
-ModelMetadataModel::propertyContainerPropertyChanged(PropertyContainer *pc)
+ModelMetadataModel::propertyContainerPropertyChanged(PropertyContainer *)
 {
 }
 
 void
-ModelMetadataModel::playParametersAudibilityChanged(bool a)
+ModelMetadataModel::playParametersAudibilityChanged(bool )
 {
 }
 
@@ -169,7 +169,7 @@
 {
     if (!index.isValid()) return QVariant();
 
-    QObject *obj = static_cast<QObject *>(index.internalPointer());
+//    QObject *obj = static_cast<QObject *>(index.internalPointer());
     int row = index.row(), col = index.column();
 
     //!!! not exactly the ideal use of a std::set
@@ -206,13 +206,13 @@
 }
 
 bool
-ModelMetadataModel::setData(const QModelIndex &index, const QVariant &value, int role)
+ModelMetadataModel::setData(const QModelIndex &, const QVariant &, int )
 {
     return false;
 }
 
 Qt::ItemFlags
-ModelMetadataModel::flags(const QModelIndex &index) const
+ModelMetadataModel::flags(const QModelIndex &) const
 {
     Qt::ItemFlags flags = Qt::ItemIsEnabled;
     return flags;
@@ -237,7 +237,7 @@
 ModelMetadataModel::index(int row, int column, const QModelIndex &parent) const
 {
     if (!parent.isValid()) {
-        if (row >= m_models.size()) return QModelIndex();
+        if (row >= (int)m_models.size()) return QModelIndex();
 	return createIndex(row, column, (void *)0);
     }
 
@@ -245,7 +245,7 @@
 }
 
 QModelIndex
-ModelMetadataModel::parent(const QModelIndex &index) const
+ModelMetadataModel::parent(const QModelIndex &) const
 {
     return QModelIndex();
 }
@@ -253,12 +253,12 @@
 int
 ModelMetadataModel::rowCount(const QModelIndex &parent) const
 {
-    if (!parent.isValid()) return m_models.size();
+    if (!parent.isValid()) return int(m_models.size());
     return 0;
 }
 
 int
-ModelMetadataModel::columnCount(const QModelIndex &parent) const
+ModelMetadataModel::columnCount(const QModelIndex &) const
 {
     return m_columnCount;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanToolButton.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,173 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LevelPanToolButton.h"
+#include "LevelPanWidget.h"
+
+#include <QMenu>
+#include <QWidgetAction>
+#include <QImage>
+#include <QStylePainter>
+#include <QStyleOptionToolButton>
+
+#include <iostream>
+using std::cerr;
+using std::endl;
+
+LevelPanToolButton::LevelPanToolButton(QWidget *parent) :
+    QToolButton(parent),
+    m_pixels(32),
+    m_pixelsBig(32 * 3),
+    m_muted(false),
+    m_savedLevel(1.f)
+{
+    m_lpw = new LevelPanWidget();
+
+    connect(m_lpw, SIGNAL(levelChanged(float)), this, SIGNAL(levelChanged(float)));
+    connect(m_lpw, SIGNAL(levelChanged(float)), this, SLOT(selfLevelChanged(float)));
+
+    connect(m_lpw, SIGNAL(panChanged(float)), this, SIGNAL(panChanged(float)));
+    connect(m_lpw, SIGNAL(panChanged(float)), this, SLOT(update()));
+
+    connect(this, SIGNAL(clicked(bool)), this, SLOT(selfClicked()));
+    
+    QMenu *menu = new QMenu();
+    QWidgetAction *wa = new QWidgetAction(menu);
+    wa->setDefaultWidget(m_lpw);
+    menu->addAction(wa);
+
+    setPopupMode(InstantPopup);
+    setMenu(menu);
+
+    setImageSize(m_pixels);
+    setBigImageSize(m_pixelsBig);
+}
+
+LevelPanToolButton::~LevelPanToolButton()
+{
+}
+
+float
+LevelPanToolButton::getLevel() const
+{
+    return m_lpw->getLevel();
+}
+
+float
+LevelPanToolButton::getPan() const
+{
+    return m_lpw->getPan();
+}
+
+bool
+LevelPanToolButton::includesMute() const
+{
+    return m_lpw->includesMute();
+}
+
+void
+LevelPanToolButton::setImageSize(int pixels)
+{
+    m_pixels = pixels;
+
+    QPixmap px(m_pixels, m_pixels);
+    px.fill(Qt::transparent);
+    setIcon(px);
+}
+
+void
+LevelPanToolButton::setBigImageSize(int pixels)
+{
+    m_pixelsBig = pixels;
+
+    m_lpw->setFixedWidth(m_pixelsBig);
+    m_lpw->setFixedHeight(m_pixelsBig);
+}
+
+void
+LevelPanToolButton::setLevel(float level)
+{
+    m_lpw->setLevel(level);
+    update();
+}
+
+void
+LevelPanToolButton::setPan(float pan)
+{
+    m_lpw->setPan(pan);
+    update();
+}
+
+void
+LevelPanToolButton::setIncludeMute(bool include)
+{
+    m_lpw->setIncludeMute(include);
+    update();
+}
+
+void
+LevelPanToolButton::setEnabled(bool enabled)
+{
+    m_lpw->setEnabled(enabled);
+    QToolButton::setEnabled(enabled);
+}
+
+void
+LevelPanToolButton::selfLevelChanged(float level)
+{
+    if (level > 0.f) {
+	m_muted = false;
+    } else {
+	m_muted = true;
+	m_savedLevel = 1.f;
+    }
+    update();
+}
+
+void
+LevelPanToolButton::selfClicked()
+{
+    cerr << "selfClicked" << endl;
+    
+    if (m_muted) {
+	m_muted = false;
+	m_lpw->setLevel(m_savedLevel);
+	emit levelChanged(m_savedLevel);
+    } else {
+	m_savedLevel = m_lpw->getLevel();
+	m_muted = true;
+	m_lpw->setLevel(0.f);
+	emit levelChanged(0.f);
+    }
+    update();
+}
+
+void
+LevelPanToolButton::paintEvent(QPaintEvent *)
+{
+    QStylePainter p(this);
+    QStyleOptionToolButton opt;
+    initStyleOption(&opt);
+    opt.features &= (~QStyleOptionToolButton::HasMenu);
+    p.drawComplexControl(QStyle::CC_ToolButton, opt);
+    
+    if (m_pixels >= height()) {
+        setImageSize(height()-1);
+    }
+    
+    double margin = (double(height()) - m_pixels) / 2.0;
+    m_lpw->renderTo(this, QRectF(margin, margin, m_pixels, m_pixels), false);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanToolButton.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,73 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef LEVEL_PAN_TOOLBUTTON_H
+#define LEVEL_PAN_TOOLBUTTON_H
+
+#include <QToolButton>
+
+class LevelPanWidget;
+
+class LevelPanToolButton : public QToolButton
+{
+    Q_OBJECT
+
+public:
+    LevelPanToolButton(QWidget *parent = 0);
+    ~LevelPanToolButton();
+    
+    /// Return level as a gain value in the range [0,1]
+    float getLevel() const; 
+    
+    /// Return pan as a value in the range [-1,1]
+    float getPan() const;
+
+    /// Discover whether the level range includes muting or not
+    bool includesMute() const;
+
+    void setImageSize(int pixels);
+			
+    void setBigImageSize(int pixels);
+			
+public slots:
+    /// Set level in the range [0,1] -- will be rounded
+    void setLevel(float);
+
+    /// Set pan in the range [-1,1] -- will be rounded
+    void setPan(float);
+
+    /// Specify whether the level range should include muting or not
+    void setIncludeMute(bool);
+
+    void setEnabled(bool enabled);
+    
+signals:
+    void levelChanged(float);
+    void panChanged(float);
+
+private slots:
+    void selfLevelChanged(float);
+    void selfClicked();
+    
+protected:
+    void paintEvent(QPaintEvent *);
+    
+    LevelPanWidget *m_lpw;
+    int m_pixels;
+    int m_pixelsBig;
+    bool m_muted;
+    float m_savedLevel;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanWidget.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,388 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LevelPanWidget.h"
+
+#include <QPainter>
+#include <QMouseEvent>
+#include <QWheelEvent>
+
+#include "layer/ColourMapper.h"
+#include "base/AudioLevel.h"
+
+#include <iostream>
+#include <cmath>
+#include <cassert>
+
+using std::cerr;
+using std::endl;
+
+static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
+static const int maxPan = 2; // range is -maxPan to maxPan
+
+LevelPanWidget::LevelPanWidget(QWidget *parent) :
+    QWidget(parent),
+    m_level(maxLevel),
+    m_pan(0),
+    m_editable(true),
+    m_includeMute(true)
+{
+}
+
+LevelPanWidget::~LevelPanWidget()
+{
+}
+
+QSize
+LevelPanWidget::sizeHint() const
+{
+    static double ratio = 0.0;
+    if (ratio == 0.0) {
+        double baseEm;
+#ifdef Q_OS_MAC
+        baseEm = 17.0;
+#else
+        baseEm = 15.0;
+#endif
+        double em = QFontMetrics(QFont()).height();
+        ratio = em / baseEm;
+    }
+
+    int pixels = 40;
+    int scaled = int(pixels * ratio + 0.5);
+    if (pixels != 0 && scaled == 0) scaled = 1;
+    return QSize(scaled, scaled);
+}
+
+static int
+db_to_level(double db)
+{
+    // Only if !m_includeMute, otherwise AudioLevel is used.
+    // Levels are: +6 0 -6 -12 -20
+    assert(maxLevel == 4);
+    if (db > 3.) return 4;
+    else if (db > -3.) return 3;
+    else if (db > -9.) return 2;
+    else if (db > -16.) return 1;
+    else return 0;
+}
+
+static double
+level_to_db(int level)
+{
+    // Only if !m_includeMute, otherwise AudioLevel is used.
+    // Levels are: +6 0 -6 -12 -20
+    assert(maxLevel == 4);
+    if (level >= 4) return 6.;
+    else if (level == 3) return 0.;
+    else if (level == 2) return -6.;
+    else if (level == 1) return -12.;
+    else return -20.;
+}
+
+void
+LevelPanWidget::setLevel(float flevel)
+{
+    int level;
+    if (m_includeMute) {
+        level = AudioLevel::multiplier_to_fader
+            (flevel, maxLevel, AudioLevel::ShortFader);
+    } else {
+        level = db_to_level(AudioLevel::multiplier_to_dB(flevel));
+    }
+    if (level < 0) level = 0;
+    if (level > maxLevel) level = maxLevel;
+    if (level != m_level) {
+	m_level = level;
+	float convertsTo = getLevel();
+	if (fabsf(convertsTo - flevel) > 1e-5) {
+	    emitLevelChanged();
+	}
+	update();
+    }
+}
+
+float
+LevelPanWidget::getLevel() const
+{
+    if (m_includeMute) {
+        return float(AudioLevel::fader_to_multiplier
+                     (m_level, maxLevel, AudioLevel::ShortFader));
+    } else {
+        return float(AudioLevel::dB_to_multiplier(level_to_db(m_level)));
+    }
+}
+
+void
+LevelPanWidget::setPan(float pan)
+{
+    m_pan = int(round(pan * maxPan));
+    if (m_pan < -maxPan) m_pan = -maxPan;
+    if (m_pan > maxPan) m_pan = maxPan;
+    update();
+}
+
+bool
+LevelPanWidget::isEditable() const
+{
+    return m_editable;
+}
+
+bool
+LevelPanWidget::includesMute() const
+{
+    return m_includeMute;
+}
+
+void
+LevelPanWidget::setEditable(bool editable)
+{
+    m_editable = editable;
+    update();
+}
+
+void
+LevelPanWidget::setIncludeMute(bool include)
+{
+    m_includeMute = include;
+    emitLevelChanged();
+    update();
+}
+
+float
+LevelPanWidget::getPan() const
+{
+    return float(m_pan) / float(maxPan);
+}
+
+void
+LevelPanWidget::emitLevelChanged()
+{
+    cerr << "emitting levelChanged(" << getLevel() << ")" << endl;
+    emit levelChanged(getLevel());
+}
+
+void
+LevelPanWidget::emitPanChanged()
+{
+    cerr << "emitting panChanged(" << getPan() << ")" << endl;
+    emit panChanged(getPan());
+}
+
+void
+LevelPanWidget::mousePressEvent(QMouseEvent *e)
+{
+    mouseMoveEvent(e);
+}
+
+void
+LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
+{
+    if (!m_editable) return;
+    
+    int level, pan;
+    toCell(rect(), e->pos(), level, pan);
+    if (level == m_level && pan == m_pan) {
+	return;
+    }
+    if (level != m_level) {
+	m_level = level;
+	emitLevelChanged();
+    }
+    if (pan != m_pan) {
+	m_pan = pan;
+	emitPanChanged();
+    }
+    update();
+}
+
+void
+LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
+{
+    mouseMoveEvent(e);
+}
+
+void
+LevelPanWidget::wheelEvent(QWheelEvent *e)
+{
+    if (e->modifiers() & Qt::ControlModifier) {
+	if (e->delta() > 0) {
+	    if (m_pan < maxPan) {
+		++m_pan;
+		emitPanChanged();
+		update();
+	    }
+	} else {
+	    if (m_pan > -maxPan) {
+		--m_pan;
+		emitPanChanged();
+		update();
+	    }
+	}
+    } else {
+	if (e->delta() > 0) {
+	    if (m_level < maxLevel) {
+		++m_level;
+		emitLevelChanged();
+		update();
+	    }
+	} else {
+	    if (m_level > 0) {
+		--m_level;
+		emitLevelChanged();
+		update();
+	    }
+	}
+    }
+}
+
+void
+LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
+{
+    double w = rect.width(), h = rect.height();
+
+    int npan = maxPan * 2 + 1;
+    int nlevel = maxLevel + 1;
+
+    double wcell = w / npan, hcell = h / nlevel;
+
+    level = int((h - (loc.y() - rect.y())) / hcell);
+    if (level < 0) level = 0;
+    if (level > maxLevel) level = maxLevel;
+
+    pan = int((loc.x() - rect.x()) / wcell) - maxPan;
+    if (pan < -maxPan) pan = -maxPan;
+    if (pan > maxPan) pan = maxPan;
+}
+
+QSizeF
+LevelPanWidget::cellSize(QRectF rect) const
+{
+    double w = rect.width(), h = rect.height();
+    int npan = maxPan * 2 + 1;
+    int nlevel = maxLevel + 1;
+    double wcell = w / npan, hcell = h / nlevel;
+    return QSizeF(wcell, hcell);
+}
+
+QPointF
+LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
+{
+    QSizeF cs = cellSize(rect);
+    return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
+		   rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
+}
+
+QSizeF
+LevelPanWidget::cellLightSize(QRectF rect) const
+{
+    double extent = 3. / 4.;
+    QSizeF cs = cellSize(rect);
+    double m = std::min(cs.width(), cs.height());
+    return QSizeF(m * extent, m * extent);
+}
+
+QRectF
+LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
+{
+    QSizeF cls = cellLightSize(rect);
+    QPointF cc = cellCentre(rect, level, pan);
+    return QRectF(cc.x() - cls.width() / 2., 
+		  cc.y() - cls.height() / 2.,
+		  cls.width(),
+		  cls.height());
+}
+
+double
+LevelPanWidget::thinLineWidth(QRectF rect) const
+{
+    double tw = ceil(rect.width() / (maxPan * 2. * 10.));
+    double th = ceil(rect.height() / (maxLevel * 10.));
+    return std::min(th, tw);
+}
+
+static QColor
+level_to_colour(int level)
+{
+    assert(maxLevel == 4);
+    if (level == 0) return Qt::black;
+    else if (level == 1) return QColor(80, 0, 0);
+    else if (level == 2) return QColor(160, 0, 0);
+    else if (level == 3) return QColor(255, 0, 0);
+    else return QColor(255, 255, 0);
+}
+
+void
+LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
+{
+    QPainter paint(dev);
+
+    paint.setRenderHint(QPainter::Antialiasing, true);
+
+    QPen pen;
+
+    double thin = thinLineWidth(rect);
+
+    pen.setColor(QColor(127, 127, 127, 127));
+    pen.setWidthF(cellLightSize(rect).width() + thin);
+    pen.setCapStyle(Qt::RoundCap);
+    paint.setPen(pen);
+
+    for (int pan = -maxPan; pan <= maxPan; ++pan) {
+	paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
+    }
+
+    if (isEnabled()) {
+	pen.setColor(Qt::black);
+    } else {
+	pen.setColor(Qt::darkGray);
+    }
+
+    if (!asIfEditable && m_includeMute && m_level == 0) {
+        pen.setWidthF(thin * 2);
+        pen.setCapStyle(Qt::RoundCap);
+        paint.setPen(pen);
+        paint.drawLine(cellCentre(rect, 0, -maxPan),
+                       cellCentre(rect, maxLevel, maxPan));
+        paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
+                       cellCentre(rect, 0, maxPan));
+        return;
+    }
+    
+    pen.setWidthF(thin);
+    pen.setCapStyle(Qt::FlatCap);
+    paint.setPen(pen);
+    
+    for (int level = 0; level <= m_level; ++level) {
+	if (isEnabled()) {
+	    paint.setBrush(level_to_colour(level));
+	}
+	QRectF clr = cellLightRect(rect, level, m_pan);
+	if (m_includeMute && m_level == 0) {
+	    paint.drawLine(clr.topLeft(), clr.bottomRight());
+	    paint.drawLine(clr.bottomLeft(), clr.topRight());
+	} else {
+	    paint.drawEllipse(clr);
+	}
+    }
+}
+
+void
+LevelPanWidget::paintEvent(QPaintEvent *)
+{
+    renderTo(this, rect(), m_editable);
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanWidget.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,89 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef LEVEL_PAN_WIDGET_H
+#define LEVEL_PAN_WIDGET_H
+
+#include <QWidget>
+
+/**
+ * A simple widget for coarse level and pan control.
+ */
+
+class LevelPanWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    LevelPanWidget(QWidget *parent = 0);
+    ~LevelPanWidget();
+    
+    /// Return level as a gain value in the range [0,1]
+    float getLevel() const; 
+    
+    /// Return pan as a value in the range [-1,1]
+    float getPan() const;
+
+    /// Find out whether the widget is editable
+    bool isEditable() const;
+
+    /// Discover whether the level range includes muting or not
+    bool includesMute() const;
+
+    /// Draw a suitably sized copy of the widget's contents to the given device
+    void renderTo(QPaintDevice *, QRectF, bool asIfEditable) const;
+
+    QSize sizeHint() const;
+                                               
+public slots:
+    /// Set level in the range [0,1] -- will be rounded
+    void setLevel(float);
+
+    /// Set pan in the range [-1,1] -- will be rounded
+    void setPan(float);
+
+    /// Specify whether the widget is editable or read-only (default editable)
+    void setEditable(bool);
+
+    /// Specify whether the level range should include muting or not
+    void setIncludeMute(bool);
+    
+signals:
+    void levelChanged(float);
+    void panChanged(float);
+
+protected:
+    virtual void mousePressEvent(QMouseEvent *ev);
+    virtual void mouseMoveEvent(QMouseEvent *ev);
+    virtual void mouseReleaseEvent(QMouseEvent *ev);
+    virtual void wheelEvent(QWheelEvent *ev);
+    virtual void paintEvent(QPaintEvent *ev);
+
+    void emitLevelChanged();
+    void emitPanChanged();
+    
+    int m_level;
+    int m_pan;
+    bool m_editable;
+    bool m_includeMute;
+
+    QSizeF cellSize(QRectF) const;
+    QPointF cellCentre(QRectF, int level, int pan) const;
+    QSizeF cellLightSize(QRectF) const;
+    QRectF cellLightRect(QRectF, int level, int pan) const;
+    double thinLineWidth(QRectF) const;
+    void toCell(QRectF, QPointF loc, int &level, int &pan) const;
+};
+
+#endif
--- a/widgets/ListInputDialog.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/ListInputDialog.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -67,7 +67,7 @@
 {
     for (size_t i = 0; i < m_radioButtons.size(); ++i) {
         if (m_radioButtons[i]->isChecked()) {
-            return m_strings[i];
+            return m_strings[int(i)];
         }
     }
     return "";
--- a/widgets/ModelDataTableDialog.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/ModelDataTableDialog.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -167,14 +167,14 @@
 }
 
 void
-ModelDataTableDialog::userScrolledToFrame(unsigned long frame)
+ModelDataTableDialog::userScrolledToFrame(sv_frame_t frame)
 {
     QModelIndex index = m_table->getModelIndexForFrame(frame);
     makeCurrent(index.row());
 }
 
 void
-ModelDataTableDialog::playbackScrolledToFrame(unsigned long frame)
+ModelDataTableDialog::playbackScrolledToFrame(sv_frame_t frame)
 {
     if (m_trackPlayback) {
         QModelIndex index = m_table->getModelIndexForFrame(frame);
@@ -247,14 +247,14 @@
 }
 
 void
-ModelDataTableDialog::viewPressed(const QModelIndex &index)
+ModelDataTableDialog::viewPressed(const QModelIndex &)
 {
 //    SVDEBUG << "ModelDataTableDialog::viewPressed: " << index.row() << ", " << index.column() << endl;
 }
 
 void
 ModelDataTableDialog::currentChanged(const QModelIndex &current,
-                                     const QModelIndex &previous)
+                                     const QModelIndex &)
 {
 //    SVDEBUG << "ModelDataTableDialog::currentChanged: from "
 //              << previous.row() << ", " << previous.column()
--- a/widgets/ModelDataTableDialog.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/ModelDataTableDialog.h	Wed Apr 20 12:06:28 2016 +0100
@@ -18,6 +18,8 @@
 
 #include <QMainWindow>
 
+#include "base/BaseTypes.h"
+
 class TabularModel;
 class ModelDataTableModel;
 class QTableView;
@@ -38,11 +40,11 @@
     QToolBar *getPlayToolbar() { return m_playToolbar; }
 
 signals:
-    void scrollToFrame(unsigned long frame);
+    void scrollToFrame(sv_frame_t frame);
 
 public slots:
-    void userScrolledToFrame(unsigned long frame);
-    void playbackScrolledToFrame(unsigned long frame);
+    void userScrolledToFrame(sv_frame_t frame);
+    void playbackScrolledToFrame(sv_frame_t frame);
     void addCommand(Command *);
 
 protected slots:
--- a/widgets/Panner.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/Panner.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -36,7 +36,9 @@
     m_thumbColour(palette().highlightedText().color()),
     m_backgroundAlpha(255),
     m_thumbAlpha(255),
-    m_clicked(false)
+    m_clicked(false),
+    m_dragStartX(0),
+    m_dragStartY(0)
 {
 }
 
@@ -63,7 +65,7 @@
     float unit = m_scrollUnit;
     if (unit == 0.f) {
         unit = float(m_rectHeight) / (6 * float(height()));
-        if (unit < 0.01) unit = 0.01;
+        if (unit < 0.01f) unit = 0.01f;
     }
 
     if (!up) {
@@ -163,24 +165,24 @@
 
     paint.setBrush(hl);
 
-    int rw = lrintf((width() - 1) * m_rectWidth);
-    int rh = lrintf((height() - 1) * m_rectHeight);
+    int rw = int(lrintf(float(width() - 1) * m_rectWidth));
+    int rh = int(lrintf(float(height() - 1) * m_rectHeight));
     if (rw < 2) rw = 2;
     if (rh < 2) rh = 2;
 
-    paint.drawRect(lrintf(width() * m_rectX),
-                   lrintf(height() * m_rectY),
+    paint.drawRect(int(lrintf(float(width()) * m_rectX)),
+                   int(lrintf(float(height()) * m_rectY)),
                    rw, rh);
 }
 
 void
 Panner::normalise()
 {
-    if (m_rectWidth > 1.0) m_rectWidth = 1.0;
-    if (m_rectHeight > 1.0) m_rectHeight = 1.0;
-    if (m_rectX + m_rectWidth > 1.0) m_rectX = 1.0 - m_rectWidth;
+    if (m_rectWidth > 1.f) m_rectWidth = 1.f;
+    if (m_rectHeight > 1.f) m_rectHeight = 1.f;
+    if (m_rectX + m_rectWidth > 1.f) m_rectX = 1.f - m_rectWidth;
     if (m_rectX < 0) m_rectX = 0;
-    if (m_rectY + m_rectHeight > 1.0) m_rectY = 1.0 - m_rectHeight;
+    if (m_rectY + m_rectHeight > 1.f) m_rectY = 1.f - m_rectHeight;
     if (m_rectY < 0) m_rectY = 0;
 
     if (!m_defaultsSet) {
--- a/widgets/PluginParameterBox.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/PluginParameterBox.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -70,12 +70,12 @@
 
         m_programCombo = new QComboBox;
         m_programCombo->setMaxVisibleItems
-            (m_programs.size() < 25 ? m_programs.size() : 20);
+            (int(m_programs.size() < 25 ? m_programs.size() : 20));
 
-        for (size_t i = 0; i < m_programs.size(); ++i) {
+        for (int i = 0; in_range_for(m_programs, i); ++i) {
             m_programCombo->addItem(m_programs[i].c_str());
             if (m_programs[i] == currentProgram) {
-                m_programCombo->setCurrentIndex(i);
+                m_programCombo->setCurrentIndex(int(i));
             }
         }
 
@@ -88,7 +88,7 @@
         offset = 1;
     }
 
-    for (size_t i = 0; i < params.size(); ++i) {
+    for (int i = 0; in_range_for(params, i); ++i) {
 
         QString identifier = params[i].identifier.c_str();
         QString name = params[i].name.c_str();
@@ -120,9 +120,9 @@
 
         if (!(hint & PortHint::Logarithmic)) {
             if (qtz > 0.0) {
-                imax = lrintf((max - min) / qtz);
+                imax = int(lrintf((max - min) / qtz));
             } else {
-                qtz = (max - min) / 100.0;
+                qtz = (max - min) / 100.f;
             }
         }
 
@@ -131,7 +131,9 @@
 
         QLabel *label = new QLabel(name);
         if (params[i].description != "") {
-            label->setToolTip(params[i].description.c_str());
+            label->setToolTip(QString("<qt>%1</qt>")
+                              .arg(params[i].description.c_str())
+                              .replace("\n", "<br>"));
         }
         m_layout->addWidget(label, i + offset, 0);
 
@@ -243,19 +245,19 @@
     
     if (ad && ad->rangeMapper()) {
         
-        newValue = ad->mappedValue();
+        newValue = float(ad->mappedValue());
         if (newValue < min) newValue = min;
         if (newValue > max) newValue = max;
         if (qtz != 0.0) {
-            ival = lrintf((newValue - min) / qtz);
-            newValue = min + ival * qtz;
+            ival = int(lrintf((newValue - min) / qtz));
+            newValue = min + float(ival) * qtz;
         }
 
     } else {
-        if (qtz == 0.0) {
-            qtz = (max - min) / 100.0;
+        if (qtz == 0.f) {
+            qtz = (max - min) / 100.f;
         }
-        newValue = min + ival * qtz;
+        newValue = min + float(ival) * qtz;
     }
 
 //    SVDEBUG << "PluginParameterBox::dialChanged: newValue = " << newValue << endl;
@@ -327,19 +329,19 @@
     if (params.isQuantized) qtz = params.quantizeStep;
     
     if (qtz > 0.0) {
-        int step = lrintf((value - min) / qtz);
-        value = min + step * qtz;
+        int step = int(lrintf(float(value - min) / qtz));
+        value = min + float(step) * qtz;
     }
 
-    int imax = 100;
+//    int imax = 100;
     
     if (qtz > 0.0) {
-        imax = lrintf((max - min) / qtz);
+//        imax = lrintf((max - min) / qtz);
     } else {
-        qtz = (max - min) / 100.0;
+        qtz = (max - min) / 100.f;
     }
 
-    int ival = lrintf((value - min) / qtz);
+    int ival = int(lrintf(float(value - min) / qtz));
 
     AudioDial *dial = m_params[identifier].dial;
     if (dial) {
@@ -354,7 +356,7 @@
 
     SVDEBUG << "setting plugin parameter \"" << identifier << "\" to value " << value << endl;
 
-    m_plugin->setParameter(identifier.toStdString(), value);
+    m_plugin->setParameter(identifier.toStdString(), float(value));
 
     updateProgramCombo();
 
@@ -387,17 +389,17 @@
             if (param.isQuantized) qtz = param.quantizeStep;
 
             if (qtz == 0.0) {
-                qtz = (max - min) / 100.0;
+                qtz = (max - min) / 100.f;
             }
 
             i->second.dial->blockSignals(true);
-            i->second.dial->setValue(lrintf((value - min) / qtz));
+            i->second.dial->setValue(int(lrintf(float(value - min) / qtz)));
             i->second.dial->blockSignals(false);
         }
 
         if (i->second.combo) {
             i->second.combo->blockSignals(true);
-            i->second.combo->setCurrentIndex(lrintf(value));
+            i->second.combo->setCurrentIndex(int(lrintf(value)));
             i->second.combo->blockSignals(false);
         }
 
@@ -418,7 +420,7 @@
 
     std::string currentProgram = m_plugin->getCurrentProgram();
 
-    for (size_t i = 0; i < m_programs.size(); ++i) {
+    for (int i = 0; in_range_for(m_programs, i); ++i) {
         if (m_programs[i] == currentProgram) {
             m_programCombo->setCurrentIndex(i);
         }
--- a/widgets/PluginParameterDialog.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/PluginParameterDialog.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -51,7 +51,7 @@
     m_blockSize(0),
     m_windowType(HanningWindow),
     m_parameterBox(0),
-    m_selectionOnly(false)
+    m_currentSelectionOnly(false)
 {
     setWindowTitle(tr("Plugin Parameters"));
 
@@ -386,10 +386,10 @@
         int size = 1024;
         int increment = 1024;
         if (fePlugin) {
-            size = fePlugin->getPreferredBlockSize();
+            size = int(fePlugin->getPreferredBlockSize());
             cerr << "Feature extraction plugin \"" << fePlugin->getName() << "\" reports preferred block size as " << size << endl;
             if (size == 0) size = 1024;
-            increment = fePlugin->getPreferredStepSize();
+            increment = int(fePlugin->getPreferredStepSize());
             if (increment == 0) {
                 if (fePlugin->getInputDomain() == Vamp::Plugin::TimeDomain) {
                     increment = size;
@@ -540,15 +540,15 @@
 }
 
 void
-PluginParameterDialog::getProcessingParameters(size_t &blockSize) const
+PluginParameterDialog::getProcessingParameters(int &blockSize) const
 {
     blockSize = m_blockSize;
     return;
 }
 
 void
-PluginParameterDialog::getProcessingParameters(size_t &stepSize,
-                                               size_t &blockSize,
+PluginParameterDialog::getProcessingParameters(int &stepSize,
+                                               int &blockSize,
                                                WindowType &windowType) const
 {
     stepSize = m_stepSize;
--- a/widgets/PluginParameterDialog.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/PluginParameterDialog.h	Wed Apr 20 12:06:28 2016 +0100
@@ -70,8 +70,8 @@
 
     //!!! merge with PluginTransform::ExecutionContext
 
-    void getProcessingParameters(size_t &blockSize) const;
-    void getProcessingParameters(size_t &stepSize, size_t &blockSize,
+    void getProcessingParameters(int &blockSize) const;
+    void getProcessingParameters(int &stepSize, int &blockSize,
                                  WindowType &windowType) const;
 
     int exec();
@@ -96,8 +96,8 @@
     Vamp::PluginBase *m_plugin;
 
     int m_channel;
-    size_t m_stepSize;
-    size_t m_blockSize;
+    int m_stepSize;
+    int m_blockSize;
 
     WindowType m_windowType;
     PluginParameterBox *m_parameterBox;
--- a/widgets/PropertyBox.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/PropertyBox.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -24,10 +24,6 @@
 #include "base/UnitDatabase.h"
 #include "base/RangeMapper.h"
 
-#include "plugin/RealTimePluginFactory.h"
-#include "plugin/RealTimePluginInstance.h"
-#include "plugin/PluginXml.h"
-
 #include "AudioDial.h"
 #include "LEDButton.h"
 #include "IconLoader.h"
@@ -46,6 +42,7 @@
 #include <QApplication>
 #include <QColorDialog>
 #include <QInputDialog>
+#include <QDir>
 
 #include <cassert>
 #include <iostream>
@@ -71,6 +68,12 @@
     m_mainBox = new QVBoxLayout;
     setLayout(m_mainBox);
 
+#ifdef Q_OS_MAC
+    QMargins mm = m_mainBox->contentsMargins();
+    QMargins mmhalf(mm.left()/2, mm.top()/3, mm.right()/2, mm.bottom()/3);
+    m_mainBox->setContentsMargins(mmhalf);
+#endif
+
 //    m_nameWidget = new QLabel;
 //    m_mainBox->addWidget(m_nameWidget);
 //    m_nameWidget->setText(container->objectName());
@@ -84,6 +87,8 @@
 
     m_layout = new QGridLayout;
     m_layout->setMargin(0);
+    m_layout->setHorizontalSpacing(2);
+    m_layout->setVerticalSpacing(1);
     m_mainWidget->setLayout(m_layout);
 
     PropertyContainer::PropertyList properties = m_container->getProperties();
@@ -123,7 +128,7 @@
 PropertyBox::populateViewPlayFrame()
 {
 #ifdef DEBUG_PROPERTY_BOX
-    cerr << "PropertyBox(" << m_container << ")::populateViewPlayFrame" << endl;
+    cerr << "PropertyBox[" << this << ":" << m_container << "]::populateViewPlayFrame" << endl;
 #endif
 
     if (m_viewPlayFrame) {
@@ -194,13 +199,14 @@
 
 	layout->insertStretch(-1, 10);
 
-        if (params->getPlayPluginId() != "") {
-            QPushButton *pluginButton = new QPushButton(QIcon(":icons/faders.png"), "");
-            pluginButton->setFixedWidth(24);
-            pluginButton->setFixedHeight(24);
-            layout->addWidget(pluginButton);
-            connect(pluginButton, SIGNAL(clicked()),
-                    this, SLOT(editPlugin()));
+        if (params->getPlayClipId() != "") {
+            QPushButton *playParamButton =
+                new QPushButton(QIcon(":icons/faders.png"), "");
+            playParamButton->setFixedWidth(24);
+            playParamButton->setFixedHeight(24);
+            layout->addWidget(playParamButton);
+            connect(playParamButton, SIGNAL(clicked()),
+                    this, SLOT(editPlayParameters()));
         }
 
 	AudioDial *gainDial = new AudioDial;
@@ -506,7 +512,16 @@
             if (type == PropertyContainer::ValueProperty) {
 
                 for (int i = min; i <= max; ++i) {
-                    cb->addItem(m_container->getPropertyValueLabel(name, i));
+
+                    QString label = m_container->getPropertyValueLabel(name, i);
+                    QString iname = m_container->getPropertyValueIconName(name, i);
+
+                    if (iname != "") {
+                        QIcon icon(IconLoader().load(iname));
+                        cb->addItem(icon, label);
+                    } else {
+                        cb->addItem(label);
+                    }
                 }
 
             } else if (type == PropertyContainer::UnitsProperty) {
@@ -524,7 +539,7 @@
                 // manages its own Add New Colour entry...
                 
                 ColourDatabase *db = ColourDatabase::getInstance();
-                for (size_t i = 0; i < db->getColourCount(); ++i) {
+                for (int i = 0; i < db->getColourCount(); ++i) {
                     QString name = db->getColourName(i);
                     cb->addItem(db->getExamplePixmap(i, QSize(12, 12)), name);
                 }
@@ -575,13 +590,16 @@
         cb->blockSignals(false);
 
 #ifdef Q_OS_MAC
-	// Crashes on startup without this, for some reason
-	cb->setMinimumSize(QSize(10, 10));
+	// Crashes on startup without this, for some reason; also
+	// prevents combo boxes from getting weirdly squished
+	// vertically
+	cb->setMinimumSize(QSize(10, cb->font().pixelSize() * 2));
 #endif
 
 	break;
     }
 
+    case PropertyContainer::InvalidProperty:
     default:
 	break;
     }
@@ -624,7 +642,9 @@
 void
 PropertyBox::unitDatabaseChanged()
 {
+#ifdef DEBUG_PROPERTY_BOX
     cerr << "PropertyBox[" << this << "]: unitDatabaseChanged" << endl;
+#endif
     blockSignals(true);
 
 //    cerr << "my container is " << m_container << endl;
@@ -749,7 +769,7 @@
 void
 PropertyBox::playGainChanged(float gain)
 {
-    int dialValue = lrint(log10(gain) * 20.0);
+    int dialValue = int(lrint(log10(gain) * 20.0));
     if (dialValue < -50) dialValue = -50;
     if (dialValue >  50) dialValue =  50;
     emit changePlayGainDial(dialValue);
@@ -763,7 +783,7 @@
     PlayParameters *params = m_container->getPlayParameters();
     if (!params) return;
 
-    float gain = pow(10, float(dialValue) / 20.0);
+    float gain = float(pow(10, float(dialValue) / 20.0));
 
     if (params->getPlayGain() != gain) {
         PlayParameterRepository::EditCommand *command =
@@ -778,7 +798,7 @@
 void
 PropertyBox::playPanChanged(float pan)
 {
-    int dialValue = lrint(pan * 50.0);
+    int dialValue = int(lrint(pan * 50.0));
     if (dialValue < -50) dialValue = -50;
     if (dialValue >  50) dialValue =  50;
     emit changePlayPanDial(dialValue);
@@ -792,9 +812,9 @@
     PlayParameters *params = m_container->getPlayParameters();
     if (!params) return;
 
-    float pan = float(dialValue) / 50.0;
-    if (pan < -1.0) pan = -1.0;
-    if (pan >  1.0) pan =  1.0;
+    float pan = float(dialValue) / 50.f;
+    if (pan < -1.f) pan = -1.f;
+    if (pan >  1.f) pan =  1.f;
 
     if (params->getPlayPan() != pan) {
         PlayParameterRepository::EditCommand *command =
@@ -807,55 +827,62 @@
 }
 
 void
-PropertyBox::editPlugin()
+PropertyBox::editPlayParameters()
 {
-    //!!! should probably just emit and let something else do this
-
     PlayParameters *params = m_container->getPlayParameters();
     if (!params) return;
 
-    QString pluginId = params->getPlayPluginId();
-    QString configurationXml = params->getPlayPluginConfiguration();
+    QString clip = params->getPlayClipId();
 
     PlayParameterRepository::EditCommand *command = 
         new PlayParameterRepository::EditCommand(params);
     
-    RealTimePluginFactory *factory =
-	RealTimePluginFactory::instanceFor(pluginId);
-    if (!factory) return;
+    QInputDialog *dialog = new QInputDialog(this);
 
-    RealTimePluginInstance *instance =
-        factory->instantiatePlugin(pluginId, 0, 0, 48000, 1024, 1);
-    if (!instance) return;
+    QDir dir(":/samples");
+    QStringList clipFiles = dir.entryList(QStringList() << "*.wav", QDir::Files);
 
-    PluginXml(instance).setParametersFromXml(configurationXml);
+    QStringList clips;
+    foreach (QString str, clipFiles) {
+        clips.push_back(str.replace(".wav", ""));
+    }
+    dialog->setComboBoxItems(clips);
 
-    PluginParameterDialog *dialog = new PluginParameterDialog(instance);
-    connect(dialog, SIGNAL(pluginConfigurationChanged(QString)),
-            this, SLOT(pluginConfigurationChanged(QString)));
+    dialog->setLabelText(tr("Set playback clip:"));
+
+    QComboBox *cb = dialog->findChild<QComboBox *>();
+    if (cb) {
+        for (int i = 0; i < cb->count(); ++i) {
+            if (cb->itemText(i) == clip) {
+                cb->setCurrentIndex(i);
+            }
+        }
+    }
+
+    connect(dialog, SIGNAL(textValueChanged(QString)), 
+            this, SLOT(playClipChanged(QString)));
 
     if (dialog->exec() == QDialog::Accepted) {
-        QString newConfiguration = PluginXml(instance).toXmlString();
-        command->setPlayPluginConfiguration(newConfiguration);
+        QString newClip = dialog->textValue();
+        command->setPlayClipId(newClip);
         CommandHistory::getInstance()->addCommand(command, true);
     } else {
         delete command;
         // restore in case we mucked about with the configuration
         // as a consequence of signals from the dialog
-        params->setPlayPluginConfiguration(configurationXml);
+        params->setPlayClipId(clip);
     }
 
     delete dialog;
-    delete instance;
 }
 
 void
-PropertyBox::pluginConfigurationChanged(QString configurationXml)
+PropertyBox::playClipChanged(QString id)
 {
     PlayParameters *params = m_container->getPlayParameters();
     if (!params) return;
 
-    params->setPlayPluginConfiguration(configurationXml);
+    params->setPlayClipId(id);
 }    
 
 void
@@ -885,7 +912,7 @@
     QString extraText;
     AudioDial *dial = dynamic_cast<AudioDial *>(w);
     if (dial) {
-        float mv = dial->mappedValue();
+        double mv = dial->mappedValue();
         QString unit = "";
         if (dial->rangeMapper()) unit = dial->rangeMapper()->getUnit();
         if (unit != "") {
--- a/widgets/PropertyBox.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/PropertyBox.h	Wed Apr 20 12:06:28 2016 +0100
@@ -47,7 +47,7 @@
 public slots:
     void propertyContainerPropertyChanged(PropertyContainer *);
     void propertyContainerPropertyRangeChanged(PropertyContainer *);
-    void pluginConfigurationChanged(QString);
+    void playClipChanged(QString);
     void layerVisibilityChanged(bool);
 
 protected slots:
@@ -66,7 +66,7 @@
     void unitDatabaseChanged();
     void colourDatabaseChanged();
 
-    void editPlugin();
+    void editPlayParameters();
 
     void mouseEnteredWidget();
     void mouseLeftWidget();
--- a/widgets/PropertyStack.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/PropertyStack.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -23,6 +23,7 @@
 #include "widgets/IconLoader.h"
 #include "base/Command.h"
 #include "widgets/CommandHistory.h"
+#include "layer/ShowLayerCommand.h"
 
 #include <QIcon>
 #include <QTabWidget>
@@ -49,7 +50,7 @@
     tabBar()->setUsesScrollButtons(true); 
     tabBar()->setIconSize(QSize(16, 16));
 #endif
-    
+
     repopulate();
 
     connect(this, SIGNAL(currentChanged(int)),
@@ -74,13 +75,17 @@
 	    m_client, SLOT(propertyContainerSelected(View *, PropertyContainer *)));
 }
 
+PropertyStack::~PropertyStack()
+{
+}
+
 void
 PropertyStack::repopulate()
 {
     blockSignals(true);
 
 #ifdef DEBUG_PROPERTY_STACK
-    SVDEBUG << "PropertyStack::repopulate" << endl;
+    cerr << "PropertyStack[" << this << "]::repopulate" << endl;
 #endif
     
     while (count() > 0) {
@@ -91,11 +96,17 @@
     }
     m_boxes.clear();
     
-    for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) {
+    for (int i = 0; i < m_client->getPropertyContainerCount(); ++i) {
 
 	PropertyContainer *container = m_client->getPropertyContainer(i);
 	QString name = container->getPropertyContainerName();
 	
+#ifdef DEBUG_PROPERTY_STACK
+        cerr << "PropertyStack[" << this << "]::repopulate: client " << m_client
+             << " returns container " << container << " (name " << name
+             << ") at position " << i << endl;
+#endif
+
 	PropertyBox *box = new PropertyBox(container);
 
 	connect(box, SIGNAL(showLayer(bool)), this, SLOT(showLayer(bool)));
@@ -142,7 +153,7 @@
 bool
 PropertyStack::containsContainer(PropertyContainer *pc) const
 {
-    for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) {
+    for (int i = 0; i < m_client->getPropertyContainerCount(); ++i) {
 	PropertyContainer *container = m_client->getPropertyContainer(i);
 	if (pc == container) return true;
     }
@@ -153,9 +164,19 @@
 int
 PropertyStack::getContainerIndex(PropertyContainer *pc) const
 {
-    for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) {
-	PropertyContainer *container = m_client->getPropertyContainer(i);
-	if (pc == container) return i;
+    // This is used to obtain an index to be passed to setCurrentIndex
+    // -- which is the index of the property container's box in our
+    // stack of boxes. That is not the same thing as the index of the
+    // container (i.e. the layer) in the view: the view reorders its
+    // containers whenever one is raised to the top, while our boxes
+    // remain in the same order. So we must find this container in the
+    // box list, not in the view.
+
+    for (int i = 0; in_range_for(m_boxes, i); ++i) {
+	PropertyContainer *container = m_boxes[i]->getContainer();
+	if (pc == container) {
+            return i;
+        }
     }
 
     return false;
@@ -207,27 +228,6 @@
     repopulate();
 }
 
-class ShowLayerCommand : public QObject, public Command
-{
-public:
-    ShowLayerCommand(View *view, Layer *layer, bool show, QString name) :
-        m_view(view), m_layer(layer), m_show(show), m_name(name) { }
-    void execute() {
-        m_layer->showLayer(m_view, m_show);
-    }
-    void unexecute() {
-        m_layer->showLayer(m_view, !m_show);
-    }
-    QString getName() const {
-        return m_name;
-    }
-protected:
-    View *m_view;
-    Layer *m_layer;
-    bool m_show;
-    QString m_name;
-};
-
 void
 PropertyStack::showLayer(bool show)
 {
--- a/widgets/PropertyStack.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/PropertyStack.h	Wed Apr 20 12:06:28 2016 +0100
@@ -31,6 +31,7 @@
 
 public:
     PropertyStack(QWidget *parent, View *client);
+    virtual ~PropertyStack();
 
     View *getClient() { return m_client; }
     bool containsContainer(PropertyContainer *container) const;
--- a/widgets/SubdividingMenu.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/SubdividingMenu.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -22,7 +22,7 @@
 using std::set;
 using std::map;
 
-SubdividingMenu::SubdividingMenu(size_t lowerLimit, size_t upperLimit,
+SubdividingMenu::SubdividingMenu(int lowerLimit, int upperLimit,
                                  QWidget *parent) :
     QMenu(parent),
     m_lowerLimit(lowerLimit ? lowerLimit : 14),
@@ -31,8 +31,8 @@
 {
 }
 
-SubdividingMenu::SubdividingMenu(const QString &title, size_t lowerLimit,
-                                 size_t upperLimit, QWidget *parent) :
+SubdividingMenu::SubdividingMenu(const QString &title, int lowerLimit,
+                                 int upperLimit, QWidget *parent) :
     QMenu(title, parent),
     m_lowerLimit(lowerLimit ? lowerLimit : 14),
     m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2),
@@ -53,11 +53,11 @@
 {
     m_entriesSet = true;
 
-    size_t total = entries.size();
+    int total = int(entries.size());
         
     if (total < m_upperLimit) return;
 
-    size_t count = 0;
+    int count = 0;
     QMenu *chunkMenu = new QMenu();
     chunkMenu->setTearOffEnabled(isTearOffEnabled());
 
--- a/widgets/SubdividingMenu.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/SubdividingMenu.h	Wed Apr 20 12:06:28 2016 +0100
@@ -39,10 +39,10 @@
     Q_OBJECT
 
 public:
-    SubdividingMenu(size_t lowerLimit = 0, size_t upperLimit = 0,
+    SubdividingMenu(int lowerLimit = 0, int upperLimit = 0,
                     QWidget *parent = 0);
-    SubdividingMenu(const QString &title, size_t lowerLimit = 0,
-                    size_t upperLimit = 0, QWidget *parent = 0);
+    SubdividingMenu(const QString &title, int lowerLimit = 0,
+                    int upperLimit = 0, QWidget *parent = 0);
     virtual ~SubdividingMenu();
 
     void setEntries(const std::set<QString> &entries);
@@ -65,8 +65,8 @@
 protected:
     std::map<QString, QMenu *> m_nameToChunkMenuMap;
 
-    size_t m_lowerLimit;
-    size_t m_upperLimit;
+    int m_lowerLimit;
+    int m_upperLimit;
 
     bool m_entriesSet;
     std::map<QString, QObject *> m_pendingEntries;
--- a/widgets/Thumbwheel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/Thumbwheel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -135,7 +135,7 @@
 }
 
 void
-Thumbwheel::setMappedValue(float mappedValue)
+Thumbwheel::setMappedValue(double mappedValue)
 {
     if (m_rangeMapper) {
         int newValue = m_rangeMapper->getPositionForValue(mappedValue);
@@ -202,7 +202,7 @@
     return m_value;
 }
 
-float
+double
 Thumbwheel::getMappedValue() const
 {
     if (m_rangeMapper) {
@@ -240,7 +240,7 @@
 void
 Thumbwheel::scroll(bool up)
 {
-    int step = lrintf(m_speed);
+    int step = int(lrintf(m_speed));
     if (step == 0) step = 1;
 
     if (up) {
@@ -327,11 +327,11 @@
 
     if (m_rangeMapper) {
         
-        float min = m_rangeMapper->getValueForPosition(m_min);
-        float max = m_rangeMapper->getValueForPosition(m_max);
+        double min = m_rangeMapper->getValueForPosition(m_min);
+        double max = m_rangeMapper->getValueForPosition(m_max);
                 
         if (min > max) { 
-            float tmp = min;
+            double tmp = min;
             min = max;
             max = tmp;
         }
@@ -357,7 +357,7 @@
             }
         }
         
-        float newValue = QInputDialog::getDouble
+        double newValue = QInputDialog::getDouble
             (this,
              tr("Enter new value"),
              text,
@@ -398,10 +398,10 @@
         dist = e->y() - m_clickPos.y();
     }
 
-    float rotation = m_clickRotation + (m_speed * dist) / 100;
+    float rotation = m_clickRotation + (m_speed * float(dist)) / 100;
     if (rotation < 0.f) rotation = 0.f;
     if (rotation > 1.f) rotation = 1.f;
-    int value = lrintf(m_min + (m_max - m_min) * m_rotation);
+    int value = int(lrintf(float(m_min) + float(m_max - m_min) * m_rotation));
     if (value != m_value) {
         setValue(value);
         if (m_tracking) emit valueChanged(getValue());
@@ -426,7 +426,7 @@
 void
 Thumbwheel::wheelEvent(QWheelEvent *e)
 {
-    int step = lrintf(m_speed);
+    int step = int(lrintf(m_speed));
     if (step == 0) step = 1;
 
     if (e->delta() > 0) {
@@ -469,13 +469,13 @@
 
     paint.setRenderHint(QPainter::Antialiasing, true);
 
-    float w  = width();
-    float w0 = 0.5;
-    float w1 = w - 0.5;
+    double w  = width();
+    double w0 = 0.5;
+    double w1 = w - 0.5;
 
-    float h  = height();
-    float h0 = 0.5;
-    float h1 = h - 0.5;
+    double h  = height();
+    double h0 = 0.5;
+    double h1 = h - 0.5;
 
     for (int i = bw-1; i >= 0; --i) {
 
@@ -504,7 +504,7 @@
 
     paint.setClipRect(subclip);
 
-    float radians = m_rotation * 1.5f * M_PI;
+    double radians = m_rotation * 1.5f * M_PI;
 
 //    cerr << "value = " << m_value << ", min = " << m_min << ", max = " << m_max << ", rotation = " << rotation << endl;
 
@@ -518,16 +518,16 @@
 
     for (int i = 0; i < notches; ++i) {
 
-        float a0 = (2.f * M_PI * i) / notches + radians;
-        float a1 = a0 + M_PI / (notches * 2);
-        float a2 = (2.f * M_PI * (i + 1)) / notches + radians;
+        double a0 = (2.0 * M_PI * i) / notches + radians;
+        double a1 = a0 + M_PI / (notches * 2);
+        double a2 = (2.0 * M_PI * (i + 1)) / notches + radians;
 
-        float depth = cosf((a0 + a2) / 2);
+        double depth = cos((a0 + a2) / 2);
         if (depth < 0) continue;
 
-        float x0 = radius * sinf(a0) + w/2;
-        float x1 = radius * sinf(a1) + w/2;
-        float x2 = radius * sinf(a2) + w/2;
+        double x0 = radius * sin(a0) + w/2;
+        double x1 = radius * sin(a1) + w/2;
+        double x2 = radius * sin(a2) + w/2;
         if (x2 < 0 || x0 > w) continue;
 
         if (x0 < 0) x0 = 0;
@@ -537,7 +537,7 @@
         x1 += bw;
         x2 += bw;
 
-        int grey = lrintf(120 * depth);
+        int grey = int(lrint(120 * depth));
 
         QColor fc = QColor(grey, grey, grey);
         QColor oc = palette().highlight().color();
@@ -548,9 +548,9 @@
 
             paint.setBrush(oc);
 
-            float prop;
+            double prop;
             if (i >= notches / 4) {
-                prop = float(notches - (((i - float(notches) / 4.f) * 4.f) / 3.f))
+                prop = double(notches - (((i - double(notches) / 4.f) * 4.f) / 3.f))
                     / notches;
             } else {
                 prop = 0.f;
--- a/widgets/Thumbwheel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/Thumbwheel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -41,7 +41,7 @@
 
     void setRangeMapper(RangeMapper *mapper); // I take ownership, will delete
     const RangeMapper *getRangeMapper() const { return m_rangeMapper; }
-    float getMappedValue() const;
+    double getMappedValue() const;
 
     void setShowToolTip(bool show);
 
@@ -61,7 +61,7 @@
     void setTracking(bool tracking);
     void setShowScale(bool show);
     void setValue(int value);
-    void setMappedValue(float mappedValue);
+    void setMappedValue(double mappedValue);
     void scroll(bool up);
     void resetToDefault();
 
@@ -82,7 +82,7 @@
     int m_max;
     int m_default;
     int m_value;
-    float m_mappedValue;
+    double m_mappedValue;
     bool m_noMappedUpdate;
     float m_rotation;
     Qt::Orientation m_orientation;
--- a/widgets/TipDialog.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/TipDialog.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -105,7 +105,7 @@
 TipDialog::previous()
 {
     if (--m_tipNumber < 0) {
-        m_tipNumber = m_tips.size() - 1;
+        m_tipNumber = int(m_tips.size()) - 1;
     }
 
     showTip();
--- a/widgets/TransformFinder.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/TransformFinder.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -201,7 +201,7 @@
              j != sorted.begin(); ) {
             --j;
             m_sortedResults.push_back(*j);
-            if (m_sortedResults.size() == maxResults) break;
+            if ((int)m_sortedResults.size() == maxResults) break;
         }
 
         if (m_sortedResults.empty()) m_selectedTransform = "";
@@ -209,7 +209,7 @@
 
         m_upToDateCount = 0;
 
-        for (int j = m_labels.size(); j > m_sortedResults.size(); ) {
+        for (int j = (int)m_labels.size(); j > (int)m_sortedResults.size(); ) {
             m_labels[--j]->hide();
         }
 
@@ -225,19 +225,19 @@
         if (m_sortedResults.size() < sorted.size()) {
             m_infoLabel->setText
                 (tr("Found %n description(s) containing <b>%1</b>, showing the first %2 only",
-                    0, sorted.size()).arg(text).arg(m_sortedResults.size()));
+                    0, int(sorted.size())).arg(text).arg(m_sortedResults.size()));
         } else {
             m_infoLabel->setText
                 (tr("Found %n description(s) containing <b>%1</b>",
-                    0, sorted.size()).arg(text));
+                    0, int(sorted.size())).arg(text));
         }
 
         return;
     }
 
-    if (m_upToDateCount >= m_sortedResults.size()) return;
+    if (m_upToDateCount >= (int)m_sortedResults.size()) return;
 
-    while (m_upToDateCount < m_sortedResults.size()) {
+    while (m_upToDateCount < (int)m_sortedResults.size()) {
 
         int i = m_upToDateCount;
 
@@ -302,7 +302,7 @@
         }
         selectedText += tr("</small>");
 
-        if (i >= m_labels.size()) {
+        if (i >= (int)m_labels.size()) {
             SelectableLabel *label = new SelectableLabel(m_resultsFrame);
             m_resultsLayout->addWidget(label);
             connect(label, SIGNAL(selectionChanged()), this,
@@ -338,7 +338,7 @@
 {
     QObject *s = sender();
     m_selectedTransform = "";
-    for (int i = 0; i < m_labels.size(); ++i) {
+    for (int i = 0; i < (int)m_labels.size(); ++i) {
         if (!m_labels[i]->isVisible()) continue;
         if (m_labels[i] == s) {
             if (m_labels[i]->isSelected()) {
@@ -374,7 +374,7 @@
 void
 TransformFinder::up()
 {
-    for (int i = 0; i < m_labels.size(); ++i) {
+    for (int i = 0; i < (int)m_labels.size(); ++i) {
         if (!m_labels[i]->isVisible()) continue;
         if (m_labels[i]->objectName() == m_selectedTransform) {
             if (i > 0) {
@@ -390,10 +390,10 @@
 void
 TransformFinder::down()
 {
-    for (int i = 0; i < m_labels.size(); ++i) {
+    for (int i = 0; i < (int)m_labels.size(); ++i) {
         if (!m_labels[i]->isVisible()) continue;
         if (m_labels[i]->objectName() == m_selectedTransform) {
-            if (i+1 < m_labels.size() &&
+            if (i+1 < (int)m_labels.size() &&
                 m_labels[i+1]->isVisible()) {
                 m_labels[i]->setSelected(false);
                 m_labels[i+1]->setSelected(true);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/UnitConverter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,401 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "UnitConverter.h"
+
+#include <QSpinBox>
+#include <QComboBox>
+#include <QDoubleSpinBox>
+#include <QLabel>
+#include <QDialogButtonBox>
+#include <QGridLayout>
+#include <QTabWidget>
+
+#include "base/Debug.h"
+#include "base/Pitch.h"
+#include "base/Preferences.h"
+
+using namespace std;
+
+static QString pianoNotes[] = {
+    "C", "C# / Db", "D", "D# / Eb", "E",
+    "F", "F# / Gb", "G", "G# / Ab", "A", "A# / Bb", "B"
+};
+
+UnitConverter::UnitConverter(QWidget *parent) :
+    QDialog(parent)
+{
+    QGridLayout *maingrid = new QGridLayout;
+    setLayout(maingrid);
+    
+    QTabWidget *tabs = new QTabWidget;
+    maingrid->addWidget(tabs, 0, 0);
+    
+    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close);
+    maingrid->addWidget(bb, 1, 0);
+    connect(bb, SIGNAL(rejected()), this, SLOT(close()));
+    
+    QFrame *frame = new QFrame;
+    tabs->addTab(frame, tr("Pitch"));
+    
+    QGridLayout *grid = new QGridLayout;
+    frame->setLayout(grid);
+
+    m_freq = new QDoubleSpinBox;
+    m_freq->setSuffix(QString(" Hz"));
+    m_freq->setDecimals(6);
+    m_freq->setMinimum(1e-3);
+    m_freq->setMaximum(1e6);
+    m_freq->setValue(440);
+    connect(m_freq, SIGNAL(valueChanged(double)),
+	    this, SLOT(freqChanged()));
+
+    // The min and max range values for all the remaining controls are
+    // determined by the min and max Hz above
+    
+    m_midi = new QSpinBox;
+    m_midi->setMinimum(-156);
+    m_midi->setMaximum(203);
+    connect(m_midi, SIGNAL(valueChanged(int)),
+	    this, SLOT(midiChanged()));
+
+    m_note = new QComboBox;
+    for (int i = 0; i < 12; ++i) {
+	m_note->addItem(pianoNotes[i]);
+    }
+    connect(m_note, SIGNAL(currentIndexChanged(int)),
+	    this, SLOT(noteChanged()));
+
+    m_octave = new QSpinBox;
+    m_octave->setMinimum(-14);
+    m_octave->setMaximum(15);
+    connect(m_octave, SIGNAL(valueChanged(int)),
+	    this, SLOT(octaveChanged()));
+
+    m_cents = new QDoubleSpinBox;
+    m_cents->setSuffix(tr(" cents"));
+    m_cents->setDecimals(4);
+    m_cents->setMinimum(-50);
+    m_cents->setMaximum(50);
+    connect(m_cents, SIGNAL(valueChanged(double)),
+	    this, SLOT(centsChanged()));
+    
+    int row = 0;
+
+    grid->addWidget(new QLabel(tr("In 12-tone Equal Temperament:")), row, 0, 1, 9);
+
+    ++row;
+    
+    grid->setRowMinimumHeight(row, 8);
+
+    ++row;
+    
+    grid->addWidget(m_freq, row, 0, 2, 1, Qt::AlignRight | Qt::AlignVCenter);
+    grid->addWidget(new QLabel(tr("=")), row, 1, 2, 1, Qt::AlignHCenter | Qt::AlignVCenter);
+
+    grid->addWidget(new QLabel(tr("+")), row, 7, 2, 1, Qt::AlignHCenter | Qt::AlignVCenter);
+    grid->addWidget(m_cents, row, 8, 2, 1, Qt::AlignLeft | Qt::AlignVCenter);
+
+    grid->addWidget(new QLabel(tr("Piano note")), row, 2, 1, 2);
+    grid->addWidget(m_note, row, 4);
+    grid->addWidget(new QLabel(tr("in octave")), row, 5);
+    grid->addWidget(m_octave, row, 6);
+
+    ++row;
+    
+    grid->addWidget(new QLabel(tr("MIDI pitch")), row, 2, 1, 2);
+    grid->addWidget(m_midi, row, 4);
+    
+    ++row;
+
+    grid->setRowStretch(row, 20);
+    grid->setRowMinimumHeight(row, 8);
+
+    ++row;
+
+    m_pitchPrefsLabel = new QLabel;
+    grid->addWidget(m_pitchPrefsLabel, row, 0, 1, 9);
+
+    ++row;
+    
+    grid->addWidget
+	(new QLabel(tr("Note that only pitches in the range 0 to 127 are valid "
+		       "in the MIDI protocol.")),
+	 row, 0, 1, 9);
+
+    ++row;
+    
+    frame = new QFrame;
+    tabs->addTab(frame, tr("Tempo"));
+    
+    grid = new QGridLayout;
+    frame->setLayout(grid);
+
+    m_samples = new QDoubleSpinBox;
+    m_samples->setSuffix(QString(" samples"));
+    m_samples->setDecimals(2);
+    m_samples->setMinimum(1);
+    m_samples->setMaximum(1e8);
+    m_samples->setValue(22050);
+    connect(m_samples, SIGNAL(valueChanged(double)),
+	    this, SLOT(samplesChanged()));
+    
+    m_period = new QDoubleSpinBox;
+    m_period->setSuffix(QString(" ms"));
+    m_period->setDecimals(4);
+    m_period->setMinimum(1e-3);
+    m_period->setMaximum(100000);
+    m_period->setValue(500);
+    connect(m_period, SIGNAL(valueChanged(double)),
+	    this, SLOT(periodChanged()));
+
+    m_bpm = new QDoubleSpinBox;
+    m_bpm->setSuffix(QString(" bpm"));
+    m_bpm->setDecimals(4);
+    m_bpm->setMinimum(0.1);
+    m_bpm->setMaximum(1e6);
+    m_bpm->setValue(120);
+    connect(m_bpm, SIGNAL(valueChanged(double)),
+	    this, SLOT(bpmChanged()));
+
+    m_tempofreq = new QDoubleSpinBox;
+    m_tempofreq->setSuffix(QString(" beats/sec"));
+    m_tempofreq->setDecimals(4);
+    m_tempofreq->setMinimum(1e-3);
+    m_tempofreq->setMaximum(1e5);
+    m_tempofreq->setValue(0.5);
+
+    connect(m_tempofreq, SIGNAL(valueChanged(double)),
+	    this, SLOT(tempofreqChanged()));
+	
+    m_samplerate = new QComboBox;
+    QList<int> rates;
+    rates << 8000;
+    for (int i = 1; i <= 16; i *= 2) {
+	rates << 11025 * i << 12000 * i;
+    }
+    foreach (int r, rates) {
+	m_samplerate->addItem(QString("%1 Hz").arg(r));
+    }
+    connect(m_samplerate, SIGNAL(currentIndexChanged(int)),
+	    this, SLOT(samplerateChanged()));
+    m_samplerate->setCurrentText("44100 Hz");
+    
+    connect(Preferences::getInstance(),
+	    SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
+	    this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
+
+    row = 0;
+
+    grid->setRowStretch(row, 20);
+    grid->setRowMinimumHeight(row, 8);
+
+    ++row;
+
+    grid->addWidget(new QLabel(tr("Beat period")), row, 0, 2, 1, Qt::AlignVCenter);
+    grid->addWidget(m_period, row, 1);
+    grid->addWidget(new QLabel(tr("=")), row, 2, 2, 1, Qt::AlignVCenter);
+
+    grid->addWidget(m_tempofreq, row, 3);
+
+    grid->addWidget(new QLabel(tr("at")), row, 4, 2, 1, Qt::AlignVCenter);
+    grid->addWidget(m_samplerate, row, 5, 2, 1, Qt::AlignVCenter);
+
+    ++row;
+    
+    grid->addWidget(m_samples, row, 1);
+    grid->addWidget(m_bpm, row, 3);
+
+    ++row;
+    
+    grid->setRowStretch(row, 20);
+    grid->setRowMinimumHeight(row, 8);
+    
+    updatePitchesFromFreq();
+    updatePitchPrefsLabel();
+    updateTempiFromSamples();
+}
+
+UnitConverter::~UnitConverter()
+{
+}
+
+void
+UnitConverter::setTo(QSpinBox *box, int value)
+{
+    box->blockSignals(true);
+    if (value < box->minimum() || value > box->maximum()) {
+	QPalette p;
+	p.setColor(QPalette::Text, Qt::red);
+	box->setPalette(p);
+    } else {
+	box->setPalette(QPalette());
+    }
+    box->setValue(value);
+    box->blockSignals(false);
+}
+
+void
+UnitConverter::setTo(QDoubleSpinBox *box, double value)
+{
+    box->blockSignals(true);
+    if (value < box->minimum() || value > box->maximum()) {
+	QPalette p;
+	p.setColor(QPalette::Text, Qt::red);
+	box->setPalette(p);
+    } else {
+	box->setPalette(QPalette());
+    }
+    box->setValue(value);
+    box->blockSignals(false);
+}
+
+void
+UnitConverter::preferenceChanged(PropertyContainer::PropertyName)
+{
+    updatePitchesFromFreq();
+    updatePitchPrefsLabel();
+}
+
+void
+UnitConverter::updatePitchPrefsLabel()
+{
+    m_pitchPrefsLabel->setText
+	(tr("With concert-A tuning frequency at %1 Hz, and "
+	    "middle C residing in octave %2.\n"
+	    "(These can be changed in the application preferences.)")
+	 .arg(Preferences::getInstance()->getTuningFrequency())
+	 .arg(Preferences::getInstance()->getOctaveOfMiddleC()));
+}
+
+void
+UnitConverter::freqChanged()
+{
+    updatePitchesFromFreq();
+}
+
+void
+UnitConverter::midiChanged()
+{
+    double freq = Pitch::getFrequencyForPitch(m_midi->value(), m_cents->value());
+    m_freq->setValue(freq);
+}
+
+void
+UnitConverter::noteChanged()
+{
+    int pitch = Pitch::getPitchForNoteAndOctave(m_note->currentIndex(),
+						m_octave->value());
+    double freq = Pitch::getFrequencyForPitch(pitch, m_cents->value());
+    m_freq->setValue(freq);
+}
+
+void
+UnitConverter::octaveChanged()
+{
+    int pitch = Pitch::getPitchForNoteAndOctave(m_note->currentIndex(),
+						m_octave->value());
+    double freq = Pitch::getFrequencyForPitch(pitch, m_cents->value());
+    m_freq->setValue(freq);
+}
+
+void
+UnitConverter::centsChanged()
+{
+    double freq = Pitch::getFrequencyForPitch(m_midi->value(), m_cents->value());
+    m_freq->setValue(freq);
+}
+
+void
+UnitConverter::updatePitchesFromFreq()
+{
+    double cents = 0;
+    int pitch = Pitch::getPitchForFrequency(m_freq->value(), &cents);
+    int note, octave;
+    Pitch::getNoteAndOctaveForPitch(pitch, note, octave);
+
+    cerr << "pitch " << pitch << " note " << note << " octave " << octave << " cents " << cents << endl;
+
+    setTo(m_midi, pitch);
+    setTo(m_cents, cents);
+    setTo(m_octave, octave);
+
+    m_note->blockSignals(true);
+    m_note->setCurrentIndex(note);
+    m_note->blockSignals(false);
+}
+
+void
+UnitConverter::samplesChanged()
+{
+    updateTempiFromSamples();
+}
+
+void
+UnitConverter::periodChanged()
+{
+    double rate = getSampleRate();
+    double sec = m_period->value() / 1000.0;
+    double samples = rate * sec;
+    m_samples->setValue(samples);
+}
+
+void
+UnitConverter::bpmChanged()
+{
+    double rate = getSampleRate();
+    double sec = 60.0 / m_bpm->value();
+    double samples = rate * sec;
+    m_samples->setValue(samples);
+}
+
+void
+UnitConverter::tempofreqChanged()
+{
+    double rate = getSampleRate();
+    double samples = rate / m_tempofreq->value();
+    m_samples->setValue(samples);
+}
+
+void
+UnitConverter::samplerateChanged()
+{
+    // Preserve the beat period in seconds, here, not in samples
+    periodChanged();
+}
+
+double
+UnitConverter::getSampleRate()
+{
+    return double(atoi(m_samplerate->currentText().toLocal8Bit().data()));
+}
+
+void
+UnitConverter::updateTempiFromSamples()
+{
+    double samples = m_samples->value();
+    double rate = getSampleRate();
+
+    cerr << samples << " samples at rate " << rate << endl;
+
+    double sec = samples / rate;
+    double hz = rate / samples;
+    double bpm = 60.0 / sec;
+
+    setTo(m_bpm, bpm);
+    setTo(m_period, sec * 1000.0);
+    setTo(m_tempofreq, hz);
+}
+
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/UnitConverter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,73 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef UNIT_CONVERTER_H
+#define UNIT_CONVERTER_H
+
+#include <QDialog>
+
+#include "base/PropertyContainer.h"
+
+class QSpinBox;
+class QDoubleSpinBox;
+class QComboBox;
+class QLabel;
+
+class UnitConverter : public QDialog
+{
+    Q_OBJECT
+
+public:
+    UnitConverter(QWidget *parent = 0);
+    virtual ~UnitConverter();
+
+private slots:
+    void freqChanged();
+    void midiChanged();
+    void noteChanged();
+    void octaveChanged();
+    void centsChanged();
+
+    void samplesChanged();
+    void periodChanged();
+    void bpmChanged();
+    void tempofreqChanged();
+    void samplerateChanged();
+    
+    void preferenceChanged(PropertyContainer::PropertyName);
+    
+private:
+    QDoubleSpinBox *m_freq;
+    QSpinBox *m_midi;
+    QComboBox *m_note;
+    QSpinBox *m_octave;
+    QDoubleSpinBox *m_cents;
+    QLabel *m_pitchPrefsLabel;
+    void updatePitchesFromFreq();
+    void updatePitchPrefsLabel();
+
+    QDoubleSpinBox *m_samples;
+    QDoubleSpinBox *m_period;
+    QDoubleSpinBox *m_bpm;
+    QDoubleSpinBox *m_tempofreq;
+    QComboBox *m_samplerate;
+    void updateTempiFromSamples();
+
+    double getSampleRate();
+
+    void setTo(QSpinBox *, int);
+    void setTo(QDoubleSpinBox *, double);
+};
+
+#endif
--- a/widgets/WindowShapePreview.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/WindowShapePreview.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -32,7 +32,7 @@
 
 WindowShapePreview::WindowShapePreview(QWidget *parent) :
     QFrame(parent),
-    m_windowType(WindowType(999))
+    m_windowType(HanningWindow)
 {
     QHBoxLayout *layout = new QHBoxLayout;
     layout->setMargin(0);
@@ -51,7 +51,7 @@
 WindowShapePreview::updateLabels()
 {
     int step = 24;
-    int peak = 48;
+    float peak = 48;
     int w = step * 4, h = 64;
     WindowType type = m_windowType;
     Window<float> windower = Window<float>(type, step * 2);
@@ -62,8 +62,8 @@
 
     QPainterPath path;
 
-    path.moveTo(0, h - peak + 1);
-    path.lineTo(w, h - peak + 1);
+    path.moveTo(0, float(h) - peak + 1);
+    path.lineTo(w, float(h) - peak + 1);
 
     timePainter.setPen(Qt::gray);
     timePainter.setRenderHint(QPainter::Antialiasing, true);
@@ -84,7 +84,7 @@
         }
     }
     for (int i = 0; i < w; ++i) {
-        int y = h - int(peak * acc[i] + 0.001) + 1;
+        int y = h - int(peak * acc[i] + 0.001f) + 1;
         if (i == 0) path.moveTo(i, y);
         else path.lineTo(i, y);
     }
@@ -150,7 +150,7 @@
         float power = output[i][0] * output[i][0] + output[i][1] * output[i][1];
         float db = mindb;
         if (power > 0) {
-            db = 20 * log10(power);
+            db = 20.f * log10f(power);
             if (first || db > maxdb) maxdb = db;
             if (first || db < mindb) mindb = db;
             first = false;
@@ -167,8 +167,8 @@
 
 //    float ly = h - ((-80.f + -mindb) / maxval) * peak + 1;
 
-    path.moveTo(0, h - peak + 1);
-    path.lineTo(fw, h - peak + 1);
+    path.moveTo(0, float(h) - peak + 1);
+    path.lineTo(fw, float(h) - peak + 1);
 
     freqPainter.setPen(Qt::gray);
     freqPainter.setRenderHint(QPainter::Antialiasing, true);
@@ -181,12 +181,12 @@
 
     for (int i = 0; i < fftsize/2; ++i) {
         float power = output[i][0] * output[i][0] + output[i][1] * output[i][1];
-        float db = 20 * log10(power);
+        float db = 20.f * log10f(power);
         float val = db + -mindb;
         if (val < 0) val = 0;
         float norm = val / maxval;
-        float x = (fw / float(fftsize/2)) * i;
-        float y = h - norm * peak + 1;
+        float x = (float(fw) / float(fftsize/2)) * float(i);
+        float y = float(h) - norm * peak + 1;
         if (i == 0) path.moveTo(x, y);
         else path.lineTo(x, y);
     }
@@ -209,7 +209,6 @@
 void
 WindowShapePreview::setWindowType(WindowType type)
 {
-    if (m_windowType == type) return;
     m_windowType = type;
     updateLabels();
 }
--- a/widgets/WindowTypeSelector.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/WindowTypeSelector.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -22,9 +22,23 @@
 
 #include "base/Preferences.h"
 
-WindowTypeSelector::WindowTypeSelector(WindowType defaultType, QWidget *parent) :
-    QFrame(parent),
-    m_windowType(WindowType(999))
+WindowTypeSelector::WindowTypeSelector(WindowType defaultType)
+{
+    init(defaultType);
+}
+
+WindowTypeSelector::WindowTypeSelector()
+{
+    Preferences *prefs = Preferences::getInstance();
+    int min = 0, max = 0, deflt = 0;
+    WindowType type =
+        WindowType(prefs->getPropertyRangeAndValue("Window Type", &min, &max,
+                                                   &deflt));
+    init(type);
+}
+
+void
+WindowTypeSelector::init(WindowType defaultType)
 {
     QVBoxLayout *layout = new QVBoxLayout;
     layout->setMargin(0);
@@ -48,15 +62,10 @@
     m_windowShape = new WindowShapePreview;
 
     m_windowCombo = new QComboBox;
-    int min = 0, max = 0, deflt = 0, i = 0;
     int window = int(defaultType);
-    if (window == 999) {
-        window = prefs->getPropertyRangeAndValue("Window Type", &min, &max,
-                                                 &deflt);
-    }
     int index = 0;
     
-    for (i = 0; i <= 8; ++i) {
+    for (int i = 0; i <= 8; ++i) {
         m_windowCombo->addItem(prefs->getPropertyValueLabel("Window Type",
                                                             m_windows[i]));
         if (m_windows[i] == window) index = i;
@@ -69,7 +78,9 @@
 
     connect(m_windowCombo, SIGNAL(currentIndexChanged(int)),
             this, SLOT(windowIndexChanged(int)));
-    windowIndexChanged(index);
+
+    m_windowType = defaultType;
+    m_windowShape->setWindowType(m_windowType);
 }
 
 WindowTypeSelector::~WindowTypeSelector()
--- a/widgets/WindowTypeSelector.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/widgets/WindowTypeSelector.h	Wed Apr 20 12:06:28 2016 +0100
@@ -28,8 +28,8 @@
     Q_OBJECT
 
 public:
-    WindowTypeSelector(WindowType defaultType = WindowType(999), // 999 -> get from preferences
-                       QWidget *parent = 0);
+    WindowTypeSelector(WindowType defaultType);
+    WindowTypeSelector(); // get window type from preferences
     virtual ~WindowTypeSelector();
 
     WindowType getWindowType() const;
@@ -48,6 +48,8 @@
     WindowShapePreview *m_windowShape;
     WindowType *m_windows;
     WindowType m_windowType;
+
+    void init(WindowType type);
 };
 
 #endif
--- a/widgets/widgets.pro	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-TEMPLATE = lib
-
-SV_UNIT_PACKAGES = vamp-hostsdk fftw3f
-load(../prf/sv.prf)
-
-CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
-QT += xml
-
-TARGET = svwidgets
-
-DEPENDPATH += . ..
-INCLUDEPATH += . ..
-OBJECTS_DIR = tmp_obj
-MOC_DIR = tmp_moc
-
-# Input
-HEADERS += ActivityLog.h \
-           AudioDial.h \
-           ClickableLabel.h \
-           ColourNameDialog.h \
-           CommandHistory.h \
-           CSVFormatDialog.h \
-           Fader.h \
-           InteractiveFileFinder.h \
-           IconLoader.h \
-           ImageDialog.h \
-           ItemEditDialog.h \
-           KeyReference.h \
-           LabelCounterInputDialog.h \
-           LayerTree.h \
-           LayerTreeDialog.h \
-           LEDButton.h \
-           ListInputDialog.h \
-           MIDIFileImportDialog.h \
-           ModelDataTableDialog.h \
-           NotifyingCheckBox.h \
-           NotifyingComboBox.h \
-           NotifyingPushButton.h \
-           NotifyingTabBar.h \
-           Panner.h \
-           PluginParameterBox.h \
-           PluginParameterDialog.h \
-           ProgressDialog.h \
-           PropertyBox.h \
-           PropertyStack.h \
-           RangeInputDialog.h \
-           SelectableLabel.h \
-           SubdividingMenu.h \
-           TextAbbrev.h \
-           Thumbwheel.h \
-           TipDialog.h \
-           TransformFinder.h \
-           WindowShapePreview.h \
-           WindowTypeSelector.h
-SOURCES += ActivityLog.cpp \
-           AudioDial.cpp \
-           ColourNameDialog.cpp \
-           CommandHistory.cpp \
-           CSVFormatDialog.cpp \
-           Fader.cpp \
-           InteractiveFileFinder.cpp \
-           IconLoader.cpp \
-           ImageDialog.cpp \
-           ItemEditDialog.cpp \
-           KeyReference.cpp \
-           LabelCounterInputDialog.cpp \
-           LayerTree.cpp \
-           LayerTreeDialog.cpp \
-           LEDButton.cpp \
-           ListInputDialog.cpp \
-           MIDIFileImportDialog.cpp \
-           ModelDataTableDialog.cpp \
-           NotifyingCheckBox.cpp \
-           NotifyingComboBox.cpp \
-           NotifyingPushButton.cpp \
-           NotifyingTabBar.cpp \
-           Panner.cpp \
-           PluginParameterBox.cpp \
-           PluginParameterDialog.cpp \
-           ProgressDialog.cpp \
-           PropertyBox.cpp \
-           PropertyStack.cpp \
-           RangeInputDialog.cpp \
-           SelectableLabel.cpp \
-           SubdividingMenu.cpp \
-           TextAbbrev.cpp \
-           Thumbwheel.cpp \
-           TipDialog.cpp \
-           TransformFinder.cpp \
-           WindowShapePreview.cpp \
-           WindowTypeSelector.cpp