# HG changeset patch # User Chris Cannam # Date 1471618737 -3600 # Node ID c0d841cb8ab957816c44ea1a881bb778eea22f49 # Parent 96cf499fad621fb15e4d9d4103da1dfd7f73d9e9# Parent 1badacff7ab28e03904c71fe7609407e53bd2fbb Merge latest SV 3.0 branch code diff -r 96cf499fad62 -r c0d841cb8ab9 acinclude.m4 --- a/acinclude.m4 Tue Oct 20 12:55:09 2015 +0100 +++ b/acinclude.m4 Fri Aug 19 15:58:57 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 diff -r 96cf499fad62 -r c0d841cb8ab9 configure --- a/configure Tue Oct 20 12:55:09 2015 +0100 +++ b/configure Fri Aug 19 15:58:57 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 @@ -756,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 @@ -1423,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 @@ -1436,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 @@ -4137,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 @@ -4254,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 @@ -5148,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 @@ -5168,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 @@ -5180,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 @@ -5188,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 @@ -5197,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 @@ -5218,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;} @@ -5233,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 @@ -5299,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 @@ -5319,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 @@ -5331,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 @@ -5339,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 @@ -5348,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 @@ -5369,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 : @@ -5443,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 @@ -5470,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 @@ -5482,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 @@ -5490,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 @@ -5499,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 @@ -5520,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;} @@ -5687,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 diff -r 96cf499fad62 -r c0d841cb8ab9 configure.ac --- a/configure.ac Tue Oct 20 12:55:09 2015 +0100 +++ b/configure.ac Fri Aug 19 15:58:57 2016 +0100 @@ -83,8 +83,6 @@ 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]) diff -r 96cf499fad62 -r c0d841cb8ab9 layer/Colour3DPlotLayer.cpp --- a/layer/Colour3DPlotLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/Colour3DPlotLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -15,16 +15,23 @@ #include "Colour3DPlotLayer.h" -#include "view/View.h" #include "base/Profiler.h" #include "base/LogRange.h" #include "base/RangeMapper.h" + #include "ColourMapper.h" +#include "LayerGeometryProvider.h" +#include "PaintAssistant.h" + +#include "data/model/Dense3DModelPeakCache.h" + +#include "view/ViewManager.h" #include #include #include #include +#include #include @@ -41,33 +48,92 @@ Colour3DPlotLayer::Colour3DPlotLayer() : m_model(0), - m_cache(0), - m_peaksCache(0), - m_cacheValidStart(0), - m_cacheValidEnd(0), - m_colourScale(LinearScale), + m_colourScale(ColourScaleType::Linear), m_colourScaleSet(false), m_colourMap(0), m_gain(1.0), - m_binScale(LinearBinScale), - m_normalizeColumns(false), + m_binScale(BinScale::Linear), + m_normalization(ColumnNormalization::None), m_normalizeVisibleArea(false), - m_normalizeHybrid(false), m_invertVertical(false), - m_rectified(false), m_opaque(false), m_smooth(false), m_peakResolution(256), m_miny(0), - m_maxy(0) + m_maxy(0), + m_synchronous(false), + m_peakCache(0), + m_peakCacheDivisor(8) { - + QSettings settings; + settings.beginGroup("Preferences"); + setColourMap(settings.value("colour-3d-plot-colour", ColourMapper::Green).toInt()); + settings.endGroup(); } Colour3DPlotLayer::~Colour3DPlotLayer() { - delete m_cache; - delete m_peaksCache; + invalidateRenderers(); + delete m_peakCache; +} + +ColourScaleType +Colour3DPlotLayer::convertToColourScale(int value) +{ + switch (value) { + default: + case 0: return ColourScaleType::Linear; + case 1: return ColourScaleType::Log; + case 2: return ColourScaleType::PlusMinusOne; + case 3: return ColourScaleType::Absolute; + } +} + +int +Colour3DPlotLayer::convertFromColourScale(ColourScaleType scale) +{ + switch (scale) { + case ColourScaleType::Linear: return 0; + case ColourScaleType::Log: return 1; + case ColourScaleType::PlusMinusOne: return 2; + case ColourScaleType::Absolute: return 3; + + case ColourScaleType::Meter: + case ColourScaleType::Phase: + default: return 0; + } +} + +std::pair +Colour3DPlotLayer::convertToColumnNorm(int value) +{ + switch (value) { + default: + case 0: return { ColumnNormalization::None, false }; + case 1: return { ColumnNormalization::Max1, false }; + case 2: return { ColumnNormalization::None, true }; // visible area + case 3: return { ColumnNormalization::Hybrid, false }; + } +} + +int +Colour3DPlotLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible) +{ + if (visible) return 2; + switch (norm) { + case ColumnNormalization::None: return 0; + case ColumnNormalization::Max1: return 1; + case ColumnNormalization::Hybrid: return 3; + + case ColumnNormalization::Sum1: + default: return 0; + } +} + +void +Colour3DPlotLayer::setSynchronousPainting(bool synchronous) +{ + m_synchronous = synchronous; } void @@ -92,7 +158,11 @@ } else if (model->getResolution() > 2) { m_peakResolution = 128; } - cacheInvalid(); + + delete m_peakCache; + m_peakCache = 0; + + invalidateRenderers(); emit modelReplaced(); emit sliceableModelReplaced(oldModel, model); @@ -101,34 +171,46 @@ void Colour3DPlotLayer::cacheInvalid() { - delete m_cache; - delete m_peaksCache; - m_cache = 0; - m_peaksCache = 0; - m_cacheValidStart = 0; - m_cacheValidEnd = 0; + invalidateRenderers(); } void -Colour3DPlotLayer::cacheInvalid(sv_frame_t startFrame, sv_frame_t endFrame) +Colour3DPlotLayer::cacheInvalid(sv_frame_t /* startFrame */, + sv_frame_t /* endFrame */) { - if (!m_cache || !m_model) return; + //!!! should do this only if the range is visible + delete m_peakCache; + m_peakCache = 0; + + invalidateRenderers(); +} - 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; +void +Colour3DPlotLayer::invalidateRenderers() +{ + for (ViewRendererMap::iterator i = m_renderers.begin(); + i != m_renderers.end(); ++i) { + delete i->second; + } + m_renderers.clear(); +} + +Dense3DModelPeakCache * +Colour3DPlotLayer::getPeakCache() const +{ + if (!m_peakCache) { + m_peakCache = new Dense3DModelPeakCache(m_model, m_peakCacheDivisor); + } + return m_peakCache; } void Colour3DPlotLayer::modelChanged() { - if (!m_colourScaleSet && m_colourScale == LinearScale) { + if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) { if (m_model) { if (m_model->shouldUseLogValueScale()) { - setColourScale(LogScale); + setColourScale(ColourScaleType::Log); } else { m_colourScaleSet = true; } @@ -140,10 +222,10 @@ void Colour3DPlotLayer::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame) { - if (!m_colourScaleSet && m_colourScale == LinearScale) { + if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) { if (m_model && m_model->getWidth() > 50) { if (m_model->shouldUseLogValueScale()) { - setColourScale(LogScale); + setColourScale(ColourScaleType::Log); } else { m_colourScaleSet = true; } @@ -158,12 +240,10 @@ PropertyList list; list.push_back("Colour"); list.push_back("Colour Scale"); - list.push_back("Normalize Columns"); - list.push_back("Normalize Visible Area"); + list.push_back("Normalization"); list.push_back("Gain"); list.push_back("Bin Scale"); list.push_back("Invert Vertical Scale"); - list.push_back("Show Rectified"); list.push_back("Opaque"); list.push_back("Smooth"); return list; @@ -174,10 +254,8 @@ { if (name == "Colour") return tr("Colour"); if (name == "Colour Scale") return tr("Scale"); - 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 == "Invert Vertical Scale") return tr("Invert Vertical Scale"); - if (name == "Show Rectified") return tr("Half-Wave Rectify"); if (name == "Gain") return tr("Gain"); if (name == "Opaque") return tr("Always Opaque"); if (name == "Smooth") return tr("Smooth"); @@ -188,10 +266,7 @@ QString Colour3DPlotLayer::getPropertyIconName(const PropertyName &name) const { - if (name == "Normalize Columns") return "normalise-columns"; - if (name == "Normalize Visible Area") return "normalise"; if (name == "Invert Vertical Scale") return "invert-vertical"; - if (name == "Show Rectified") return "derivative"; if (name == "Opaque") return "opaque"; if (name == "Smooth") return "smooth"; return ""; @@ -201,10 +276,7 @@ Colour3DPlotLayer::getPropertyType(const PropertyName &name) const { if (name == "Gain") return RangeProperty; - if (name == "Normalize Columns") return ToggleProperty; - if (name == "Normalize Visible Area") return ToggleProperty; if (name == "Invert Vertical Scale") return ToggleProperty; - if (name == "Show Rectified") return ToggleProperty; if (name == "Opaque") return ToggleProperty; if (name == "Smooth") return ToggleProperty; return ValueProperty; @@ -213,10 +285,8 @@ QString Colour3DPlotLayer::getPropertyGroupName(const PropertyName &name) const { - if (name == "Normalize Columns" || - name == "Normalize Visible Area" || - name == "Colour Scale" || - name == "Show Rectified" || + if (name == "Normalization" || + name == "Colour Scale" || name == "Gain") return tr("Scale"); if (name == "Bin Scale" || name == "Invert Vertical Scale") return tr("Bins"); @@ -252,11 +322,12 @@ } else if (name == "Colour Scale") { + // linear, log, +/-1, abs *min = 0; *max = 3; - *deflt = (int)LinearScale; + *deflt = 0; - val = (int)m_colourScale; + val = convertFromColourScale(m_colourScale); } else if (name == "Colour") { @@ -266,33 +337,24 @@ val = m_colourMap; - } else if (name == "Normalize Columns") { + } else if (name == "Normalization") { + *min = 0; + *max = 3; *deflt = 0; - val = (m_normalizeColumns ? 1 : 0); - } else if (name == "Normalize Visible Area") { - - *deflt = 0; - val = (m_normalizeVisibleArea ? 1 : 0); + val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea); } else if (name == "Invert Vertical Scale") { *deflt = 0; val = (m_invertVertical ? 1 : 0); - } else if (name == "Show Rectified") { - - if (min) *min = 0; - if (max) *max = 0; - if (deflt) *deflt = 0; - val = (m_rectified ? 1.0 : 0.0); - } else if (name == "Bin Scale") { *min = 0; *max = 1; - *deflt = int(LinearBinScale); + *deflt = int(BinScale::Linear); val = (int)m_binScale; } else if (name == "Opaque") { @@ -328,6 +390,9 @@ case 3: return tr("Absolute"); } } + if (name == "Normalization") { + return ""; // icon only + } if (name == "Bin Scale") { switch (value) { default: @@ -338,6 +403,22 @@ return tr(""); } +QString +Colour3DPlotLayer::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 * Colour3DPlotLayer::getNewPropertyRangeMapper(const PropertyName &name) const { @@ -353,23 +434,11 @@ if (name == "Gain") { setGain(float(pow(10, value/20.0))); } else if (name == "Colour Scale") { - switch (value) { - default: - case 0: setColourScale(LinearScale); break; - case 1: setColourScale(LogScale); break; - case 2: setColourScale(PlusMinusOneScale); break; - case 3: setColourScale(AbsoluteScale); break; - } + setColourScale(convertToColourScale(value)); } else if (name == "Colour") { setColourMap(value); - } else if (name == "Normalize Columns") { - setNormalizeColumns(value ? true : false); - } else if (name == "Normalize Visible Area") { - setNormalizeVisibleArea(value ? true : false); } else if (name == "Invert Vertical Scale") { setInvertVertical(value ? true : false); - } else if (name == "Show Rectified") { - setShowRectified(value > 0.5); } else if (name == "Opaque") { setOpaque(value ? true : false); } else if (name == "Smooth") { @@ -377,19 +446,23 @@ } else if (name == "Bin Scale") { switch (value) { default: - case 0: setBinScale(LinearBinScale); break; - case 1: setBinScale(LogBinScale); break; + case 0: setBinScale(BinScale::Linear); break; + case 1: setBinScale(BinScale::Log); break; } + } else if (name == "Normalization") { + auto n = convertToColumnNorm(value); + setNormalization(n.first); + setNormalizeVisibleArea(n.second); } } void -Colour3DPlotLayer::setColourScale(ColourScale scale) +Colour3DPlotLayer::setColourScale(ColourScaleType scale) { if (m_colourScale == scale) return; m_colourScale = scale; m_colourScaleSet = true; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -398,7 +471,7 @@ { if (m_colourMap == map) return; m_colourMap = map; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -407,7 +480,7 @@ { if (m_gain == gain) return; m_gain = gain; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -422,52 +495,41 @@ { if (m_binScale == binScale) return; m_binScale = binScale; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } -Colour3DPlotLayer::BinScale +BinScale Colour3DPlotLayer::getBinScale() const { return m_binScale; } void -Colour3DPlotLayer::setNormalizeColumns(bool n) +Colour3DPlotLayer::setNormalization(ColumnNormalization n) { - if (m_normalizeColumns == n) return; - m_normalizeColumns = n; - cacheInvalid(); + if (m_normalization == n) return; + + m_normalization = n; + invalidateRenderers(); + emit layerParametersChanged(); } -bool -Colour3DPlotLayer::getNormalizeColumns() const +ColumnNormalization +Colour3DPlotLayer::getNormalization() const { - return m_normalizeColumns; -} - -void -Colour3DPlotLayer::setNormalizeHybrid(bool n) -{ - if (m_normalizeHybrid == n) return; - m_normalizeHybrid = n; - cacheInvalid(); - emit layerParametersChanged(); -} - -bool -Colour3DPlotLayer::getNormalizeHybrid() const -{ - return m_normalizeHybrid; + return m_normalization; } void Colour3DPlotLayer::setNormalizeVisibleArea(bool n) { if (m_normalizeVisibleArea == n) return; + m_normalizeVisibleArea = n; - cacheInvalid(); + invalidateRenderers(); + emit layerParametersChanged(); } @@ -482,16 +544,7 @@ { if (m_invertVertical == n) return; m_invertVertical = n; - cacheInvalid(); - emit layerParametersChanged(); -} - -void -Colour3DPlotLayer::setShowRectified(bool show) -{ - if (m_rectified == show) return; - m_rectified = show; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -500,6 +553,7 @@ { if (m_opaque == n) return; m_opaque = n; + invalidateRenderers(); emit layerParametersChanged(); } @@ -508,6 +562,7 @@ { if (m_smooth == n) return; m_smooth = n; + invalidateRenderers(); emit layerParametersChanged(); } @@ -554,16 +609,19 @@ } bool -Colour3DPlotLayer::isLayerScrollable(const LayerGeometryProvider *v) const +Colour3DPlotLayer::isLayerScrollable(const LayerGeometryProvider */* v */) const { if (m_normalizeVisibleArea) { return false; } - if (shouldPaintDenseIn(v)) { - return true; - } - QPoint discard; - return !v->shouldIlluminateLocalFeatures(this, discard); + //!!! ah hang on, if we're potentially rendering incrementally + //!!! they we can't be scrollable + return false; +// if (getRenderer(v)->willRenderOpaque(v)) { +// return true; +// } +// QPoint discard; +// return !v->shouldIlluminateLocalFeatures(this, discard); } bool @@ -608,6 +666,8 @@ m_miny = int(lrint(min)); m_maxy = int(lrint(max)); + invalidateRenderers(); + emit layerParametersChanged(); return true; } @@ -654,6 +714,8 @@ m_maxy = m_miny + dist; if (m_maxy > m_model->getHeight()) m_maxy = m_model->getHeight(); + invalidateRenderers(); + // SVDEBUG << "Colour3DPlotLayer::setVerticalZoomStep(" <getColour(value)); + paint.drawLine(5, 11 + y, cw - 5, 11 + y); } QString minstr = QString("%1").arg(min); @@ -869,8 +890,12 @@ paint.save(); QFont font = paint.font(); - font.setPixelSize(int(font.pixelSize() * 0.65)); - paint.setFont(font); + if (font.pixelSize() > 0) { + int newSize = int(font.pixelSize() * 0.65); + if (newSize < 6) newSize = 6; + font.setPixelSize(newSize); + paint.setFont(font); + } int msw = paint.fontMetrics().width(maxstr); @@ -880,12 +905,12 @@ paint.setWorldMatrix(m); - v->drawVisibleText(paint, 2, 0, minstr, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, 2, 0, minstr, PaintAssistant::OutlinedText); m.translate(ch - msw - 2, 0); paint.setWorldMatrix(m); - v->drawVisibleText(paint, 0, 0, maxstr, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, 0, 0, maxstr, PaintAssistant::OutlinedText); paint.restore(); } @@ -957,36 +982,28 @@ { Profiler profiler("Colour3DPlotLayer::getColumn"); - DenseThreeDimensionalModel::Column prev; - if (m_rectified && (col > m_model->getStartFrame())) { - prev = m_model->getColumn(col - 1); + DenseThreeDimensionalModel::Column values = m_model->getColumn(col); + values.resize(m_model->getHeight(), 0.f); + if (m_normalization != ColumnNormalization::Max1 && + m_normalization != ColumnNormalization::Hybrid) { + return values; } - - DenseThreeDimensionalModel::Column values = m_model->getColumn(col); - - if (m_rectified && !prev.empty()) { - for (int y = 0; y < values.size(); ++y) { - if (values[y] < prev[y]) values[y] = 0; - else values[y] -= prev[y]; - } - } - - while (values.size() < m_model->getHeight()) values.push_back(0.f); - if (!m_normalizeColumns && !m_normalizeHybrid) return values; 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 (int 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 (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { double value = values.at(y); double norm = (value - colMin) / (colMax - colMin); @@ -995,311 +1012,102 @@ if (value != newvalue) values[y] = float(newvalue); } - if (m_normalizeHybrid && (colMax > 0.0)) { + if (m_normalization == ColumnNormalization::Hybrid + && (colMax > 0.0)) { double logmax = log10(colMax); - for (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { values[y] = float(values[y] * logmax); } } return values; } - -void -Colour3DPlotLayer::fillCache(int firstBin, int lastBin) const + +Colour3DPlotRenderer * +Colour3DPlotLayer::getRenderer(const LayerGeometryProvider *v) const { - Profiler profiler("Colour3DPlotLayer::fillCache", true); + if (m_renderers.find(v->getId()) == m_renderers.end()) { - sv_frame_t modelStart = m_model->getStartFrame(); - sv_frame_t modelEnd = m_model->getEndFrame(); - int modelResolution = m_model->getResolution(); + Colour3DPlotRenderer::Sources sources; + sources.verticalBinLayer = this; + sources.fft = 0; + sources.source = m_model; + sources.peaks = getPeakCache(); - int modelStartBin = int(modelStart / modelResolution); - int modelEndBin = int(modelEnd / modelResolution); + ColourScale::Parameters cparams; + cparams.colourMap = m_colourMap; + cparams.scaleType = m_colourScale; + cparams.gain = m_gain; -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: range " << firstBin << " -> " << lastBin << " of model range " << modelStartBin << " -> " << modelEndBin << " (model resolution " << modelResolution << ")" << endl; -#endif + if (m_normalization == ColumnNormalization::None) { + cparams.minValue = m_model->getMinimumLevel(); + cparams.maxValue = m_model->getMaximumLevel(); + } else if (m_normalization == ColumnNormalization::Hybrid) { + cparams.minValue = 0; + cparams.maxValue = log10(m_model->getMaximumLevel() + 1.0); + } - int cacheWidth = modelEndBin - modelStartBin + 1; - if (lastBin > modelEndBin) cacheWidth = lastBin - modelStartBin + 1; - int cacheHeight = m_model->getHeight(); + if (cparams.maxValue <= cparams.minValue) { + cparams.maxValue = cparams.minValue + 0.1; + } + + Colour3DPlotRenderer::Parameters params; + params.colourScale = ColourScale(cparams); + params.normalization = m_normalization; + params.binScale = m_binScale; + params.alwaysOpaque = m_opaque; + params.invertVertical = m_invertVertical; + params.interpolate = m_smooth; - 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() != 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; - m_cache = newCache; - if (m_peaksCache) { - QImage *newPeaksCache = - new QImage(m_peaksCache->copy - (0, 0, cacheWidth / m_peakResolution + 1, cacheHeight)); - delete m_peaksCache; - m_peaksCache = newPeaksCache; - } + m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params); } - if (!m_cache) { -#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); - m_peaksCache->setColorCount(256); - m_peaksCache->fill(0); - } else if (m_peaksCache) { - delete m_peaksCache; - m_peaksCache = 0; - } - m_cacheValidStart = 0; - m_cacheValidEnd = 0; - } + return m_renderers[v->getId()]; +} -#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 +void +Colour3DPlotLayer::paintWithRenderer(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + Colour3DPlotRenderer *renderer = getRenderer(v); - if (m_cacheValidStart <= firstBin && m_cacheValidEnd >= lastBin) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Cache is valid in this region already" << endl; -#endif - return; + Colour3DPlotRenderer::RenderResult result; + MagnitudeRange magRange; + int viewId = v->getId(); + + if (!renderer->geometryChanged(v)) { + magRange = m_viewMags[viewId]; } - int fillStart = firstBin; - int fillEnd = lastBin; + if (m_synchronous) { - if (fillStart < modelStartBin) fillStart = modelStartBin; - if (fillStart > modelEndBin) fillStart = modelEndBin; - if (fillEnd < modelStartBin) fillEnd = modelStartBin; - if (fillEnd > modelEndBin) fillEnd = modelEndBin; - - bool normalizeVisible = (m_normalizeVisibleArea && !m_normalizeColumns); - - if (!normalizeVisible && (m_cacheValidStart < m_cacheValidEnd)) { - - if (m_cacheValidEnd < fillStart) { - fillStart = m_cacheValidEnd + 1; - } - if (m_cacheValidStart > fillEnd) { - fillEnd = m_cacheValidStart - 1; - } - - m_cacheValidStart = std::min(fillStart, m_cacheValidStart); - m_cacheValidEnd = std::max(fillEnd, m_cacheValidEnd); + result = renderer->render(v, paint, rect); } else { - // when normalising the visible area, the only valid area, - // ever, is the currently visible one + result = renderer->renderTimeConstrained(v, paint, rect); - 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 << " (fillStart = " << fillStart << ", fillEnd = " << fillEnd << ")" << endl; -#endif - - DenseThreeDimensionalModel::Column values; - - double min = m_model->getMinimumLevel(); - double max = m_model->getMaximumLevel(); - - if (m_colourScale == LogScale) { - LogRange::mapRange(min, max); - } else if (m_colourScale == PlusMinusOneScale) { - min = -1.f; - max = 1.f; - } else if (m_colourScale == AbsoluteScale) { - if (min < 0) { - if (fabs(min) > fabs(max)) max = fabs(min); - else max = fabs(max); - min = 0; - } else { - min = fabs(min); - max = fabs(max); + QRect uncached = renderer->getLargestUncachedRect(v); + if (uncached.width() > 0) { + v->updatePaintRect(uncached); } } - if (max == min) max = min + 1.f; - - ColourMapper mapper(m_colourMap, 0.f, 255.f); - - for (int index = 0; index < 256; ++index) { - QColor colour = mapper.map(index); - m_cache->setColor - (index, qRgb(colour.red(), colour.green(), colour.blue())); - if (m_peaksCache) { - m_peaksCache->setColor - (index, qRgb(colour.red(), colour.green(), colour.blue())); + magRange.sample(result.range); + + if (magRange.isSet()) { + if (!(m_viewMags[viewId] == magRange)) { + m_viewMags[viewId] = magRange; + //!!! now need to do the normalise-visible thing } } - double visibleMax = 0.f, visibleMin = 0.f; - - if (normalizeVisible) { + cerr << "mag range in this view: " + << m_viewMags[v->getId()].getMin() + << " -> " + << m_viewMags[v->getId()].getMax() + << endl; - for (int c = fillStart; c <= fillEnd; ++c) { - - values = getColumn(c); - - double colMax = 0.f, colMin = 0.f; - - for (int y = 0; y < cacheHeight; ++y) { - if (y >= values.size()) break; - if (y == 0 || values[y] > colMax) colMax = values[y]; - if (y == 0 || values[y] < colMin) colMin = values[y]; - } - - if (c == fillStart || colMax > visibleMax) visibleMax = colMax; - if (c == fillStart || colMin < visibleMin) visibleMin = colMin; - } - - if (m_colourScale == LogScale) { - visibleMin = LogRange::map(visibleMin); - visibleMax = LogRange::map(visibleMax); - if (visibleMin > visibleMax) std::swap(visibleMin, visibleMax); - } else if (m_colourScale == AbsoluteScale) { - if (visibleMin < 0) { - if (fabs(visibleMin) > fabs(visibleMax)) visibleMax = fabs(visibleMin); - else visibleMax = fabs(visibleMax); - visibleMin = 0; - } else { - visibleMin = fabs(visibleMin); - visibleMax = fabs(visibleMax); - } - } - } - - if (visibleMin == visibleMax) visibleMax = visibleMin + 1; - - int *peaks = 0; - if (m_peaksCache) { - peaks = new int[cacheHeight]; - for (int y = 0; y < cacheHeight; ++y) { - peaks[y] = 0; - } - } - - Profiler profiler2("Colour3DPlotLayer::fillCache: filling", true); - - for (int c = fillStart; c <= fillEnd; ++c) { - - values = getColumn(c); - - if (c >= m_cache->width()) { - cerr << "ERROR: column " << c << " >= cache width " - << m_cache->width() << endl; - continue; - } - - for (int y = 0; y < cacheHeight; ++y) { - - double value = min; - if (y < values.size()) { - value = values.at(y); - } - - value = value * m_gain; - - if (m_colourScale == LogScale) { - value = LogRange::map(value); - } else if (m_colourScale == AbsoluteScale) { - value = fabs(value); - } - - if (normalizeVisible) { - double norm = (value - visibleMin) / (visibleMax - visibleMin); - value = min + (max - min) * norm; - } - - int pixel = int(((value - min) * 256) / (max - min)); - if (pixel < 0) pixel = 0; - if (pixel > 255) pixel = 255; - if (peaks && (pixel > peaks[y])) peaks[y] = pixel; - - if (m_invertVertical) { - m_cache->setPixel(c, cacheHeight - y - 1, pixel); - } else { - if (y >= m_cache->height()) { - cerr << "ERROR: row " << y << " >= cache height " << m_cache->height() << endl; - } else { - m_cache->setPixel(c, y, pixel); - } - } - } - - if (peaks) { - int notch = (c % m_peakResolution); - if (notch == m_peakResolution-1 || c == fillEnd) { - 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 { - 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) { - peaks[y] = 0; - } - } - } - } - - 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 @@ -1324,374 +1132,25 @@ return; } - if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->getPaintRect(); - - 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 - // want to draw from our start frame + x0 * zoomLevel to our start - // frame + x1 * zoomLevel at zoomLevel frames per pixel. - - // We have quite different paint mechanisms for rendering "large" - // bins (more than one bin per pixel in both directions) and - // "small". This is "large"; see paintDense below for "small". - - int x0 = rect.left(); - int x1 = rect.right() + 1; - - int h = v->getPaintHeight(); - - double srRatio = - v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate(); - - 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; - int symax = m_maxy; - if (symax <= symin) { - symin = 0; - symax = sh; - } - if (symin < 0) symin = 0; - if (symax > sh) symax = sh; - - if (sx0 > 0) --sx0; - fillCache(sx0 < 0 ? 0 : sx0, - sx1 < 0 ? 0 : sx1); - + if (m_model->getWidth() == 0) { #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "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(): model width == 0, " + << "nothing to paint (yet)" << endl; #endif - - if (shouldPaintDenseIn(v)) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "calling paintDense" << endl; -#endif - paintDense(v, paint, rect); return; } -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - 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); + //!!!??? - const int buflen = 40; - char labelbuf[buflen]; - - for (int sx = sx0; sx <= sx1; ++sx) { - - sv_frame_t fx = sx * modelResolution; - - if (fx + modelResolution <= modelStart || fx > modelEnd) continue; - - int rx0 = v->getXForFrame(int(double(fx + modelStart) * srRatio)); - int rx1 = v->getXForFrame(int(double(fx + modelStart + modelResolution + 1) * srRatio)); - - int rw = rx1 - rx0; - if (rw < 1) rw = 1; - - bool showLabel = (rw > 10 && - paint.fontMetrics().width("0.000000") < rw - 3 && - paint.fontMetrics().height() < (h / sh)); - - for (int sy = symin; sy < symax; ++sy) { - - int ry0 = getIYForBin(v, sy); - int ry1 = getIYForBin(v, sy + 1); - QRect r(rx0, ry1, rw, ry0 - ry1); - - QRgb pixel = qRgb(255, 255, 255); - if (sx >= 0 && sx < m_cache->width() && - sy >= 0 && sy < m_cache->height()) { - pixel = m_cache->pixel(sx, sy); - } - - if (rw == 1) { - paint.setPen(pixel); - paint.setBrush(Qt::NoBrush); - paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1); - continue; - } - - QColor pen(255, 255, 255, 80); - QColor brush(pixel); - - if (rw > 3 && r.height() > 3) { - brush.setAlpha(160); - } - - paint.setPen(Qt::NoPen); - paint.setBrush(brush); - - if (illuminate) { - if (r.contains(illuminatePos)) { - paint.setPen(v->getForeground()); - } - } - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT -// cerr << "rect " << r.x() << "," << r.y() << " " -// << r.width() << "x" << r.height() << endl; -#endif - - paint.drawRect(r); - - if (showLabel) { - if (sx >= 0 && sx < m_cache->width() && - sy >= 0 && sy < m_cache->height()) { - 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); - } - } - } - } -} - -void -Colour3DPlotLayer::paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const -{ - Profiler profiler("Colour3DPlotLayer::paintDense", true); - if (!m_cache) return; - - double modelStart = double(m_model->getStartFrame()); - double modelResolution = double(m_model->getResolution()); - - 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->getPaintHeight(); // we always paint full height - int sh = m_model->getHeight(); - - int symin = m_miny; - int symax = m_maxy; - if (symax <= symin) { - symin = 0; - symax = sh; - } - if (symin < 0) symin = 0; - if (symax > sh) symax = sh; - - QImage img(w, h, QImage::Format_Indexed8); - img.setColorTable(m_cache->colorTable()); - - uchar *peaks = new uchar[w]; - memset(peaks, 0, w); - - int zoomLevel = v->getZoomLevel(); - - QImage *source = m_cache; - -#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) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "using peaks cache" << endl; -#endif - source = m_peaksCache; - modelResolution *= m_peakResolution; - } else { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "not using peaks cache" << endl; -#endif - } - } else { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "have no peaks cache" << endl; -#endif + if (m_normalizeVisibleArea) { + rect = v->getPaintRect(); } - int sw = source->width(); - - sv_frame_t xf = -1; - sv_frame_t nxf = v->getFrameForX(x0); + //!!! why is the setLayerDormant(false) found here in + //!!! SpectrogramLayer not present in Colour3DPlotLayer? + //!!! unnecessary? vestigial? forgotten? - double epsilon = 0.000001; - - vector sxa(w*2); - - for (int x = 0; x < w; ++x) { - - xf = nxf; - nxf = xf + zoomLevel; - - double sx0 = (double(xf) / srRatio - modelStart) / modelResolution; - double sx1 = (double(nxf) / srRatio - modelStart) / modelResolution; - - sxa[x*2] = sx0; - sxa[x*2 + 1] = sx1; - } - - 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) { - - double sy = getBinForY(v, y) - 0.5; - int syi = int(sy + epsilon); - if (syi < 0 || syi >= source->height()) continue; - - uchar *targetLine = img.scanLine(y); - uchar *sourceLine = source->scanLine(syi); - uchar *nextSource; - if (syi + 1 < source->height()) { - nextSource = source->scanLine(syi + 1); - } else { - nextSource = sourceLine; - } - - for (int x = 0; x < w; ++x) { - - targetLine[x] = 0; - - double sx0 = sxa[x*2]; - if (sx0 < 0) continue; - int sx0i = int(sx0 + epsilon); - if (sx0i >= sw) break; - - double a = sourceLine[sx0i]; - double b = a; - double value; - - 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 = sourceLine[sx]; - b = nextSource[sx]; - have = true; - } else { - a = std::max(a, double(sourceLine[sx])); - b = std::max(b, double(nextSource[sx])); - } - } - double yprop = sy - syi; - value = (a * (1.f - yprop) + b * yprop); - } else { - a = sourceLine[sx0i]; - b = nextSource[sx0i]; - double yprop = sy - syi; - value = (a * (1.f - yprop) + b * yprop); - int oi = sx0i + 1; - double xprop = sx0 - sx0i; - xprop -= 0.5; - if (xprop < 0) { - oi = sx0i - 1; - xprop = -xprop; - } - if (oi < 0 || oi >= sw) oi = sx0i; - a = sourceLine[oi]; - b = nextSource[oi]; - value = (value * (1.f - xprop) + - (a * (1.f - yprop) + b * yprop) * xprop); - } - - int vi = int(lrint(value)); - if (vi > 255) vi = 255; - if (vi < 0) vi = 0; - targetLine[x] = uchar(vi); - } - } - } else { - - double sy0 = getBinForY(v, 0); - - int psy0i = -1, psy1i = -1; - - for (int y = 0; y < h; ++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 == 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; - } - - for (int sy = sy0i; sy <= sy1i; ++sy) { - - if (sy < 0 || sy >= source->height()) continue; - - uchar *sourceLine = source->scanLine(sy); - - for (int x = 0; x < w; ++x) { - - double sx1 = sxa[x*2 + 1]; - if (sx1 < 0) continue; - int sx1i = int(sx1); - - double sx0 = sxa[x*2]; - if (sx0 < 0) continue; - int sx0i = int(sx0 + epsilon); - if (sx0i >= sw) break; - - uchar peak = 0; - for (int sx = sx0i; sx <= sx1i; ++sx) { - if (sx < 0 || sx >= sw) continue; - if (sourceLine[sx] > peak) peak = sourceLine[sx]; - } - peaks[x] = peak; - } - } - - copy: - for (int x = 0; x < w; ++x) { - targetLine[x] = peaks[x]; - } - } - } - - delete[] peaks; - - paint.drawImage(x0, 0, img); + paintWithRenderer(v, paint, rect); } bool @@ -1726,25 +1185,40 @@ { QString s = QString("scale=\"%1\" " "colourScheme=\"%2\" " - "normalizeColumns=\"%3\" " - "normalizeVisibleArea=\"%4\" " - "minY=\"%5\" " - "maxY=\"%6\" " - "invertVertical=\"%7\" " - "opaque=\"%8\" %9") - .arg((int)m_colourScale) + "minY=\"%3\" " + "maxY=\"%4\" " + "invertVertical=\"%5\" " + "opaque=\"%6\" %7") + .arg(convertFromColourScale(m_colourScale)) .arg(m_colourMap) - .arg(m_normalizeColumns ? "true" : "false") - .arg(m_normalizeVisibleArea ? "true" : "false") .arg(m_miny) .arg(m_maxy) .arg(m_invertVertical ? "true" : "false") .arg(m_opaque ? "true" : "false") .arg(QString("binScale=\"%1\" smooth=\"%2\" gain=\"%3\" ") - .arg((int)m_binScale) + .arg(int(m_binScale)) .arg(m_smooth ? "true" : "false") .arg(m_gain)); + // 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 == ColumnNormalization::Max1 ? "peak" : + m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none"); + + // Old-style normalization attribute, for backward compatibility + + s += QString("normalizeColumns=\"%1\" ") + .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false"); + + // And this applies to both old- and new-style attributes + + s += QString("normalizeVisibleArea=\"%1\" ") + .arg(m_normalizeVisibleArea ? "true" : "false"); + Layer::toXml(stream, indent, extraAttributes + " " + s); } @@ -1753,22 +1227,16 @@ { bool ok = false, alsoOk = false; - ColourScale scale = (ColourScale)attributes.value("scale").toInt(&ok); - if (ok) setColourScale(scale); + ColourScaleType colourScale = convertToColourScale + (attributes.value("colourScale").toInt(&ok)); + if (ok) setColourScale(colourScale); int colourMap = attributes.value("colourScheme").toInt(&ok); if (ok) setColourMap(colourMap); - BinScale binscale = (BinScale)attributes.value("binScale").toInt(&ok); - if (ok) setBinScale(binscale); - - bool normalizeColumns = - (attributes.value("normalizeColumns").trimmed() == "true"); - setNormalizeColumns(normalizeColumns); - - bool normalizeVisibleArea = - (attributes.value("normalizeVisibleArea").trimmed() == "true"); - setNormalizeVisibleArea(normalizeVisibleArea); + BinScale binScale = (BinScale) + attributes.value("binScale").toInt(&ok); + if (ok) setBinScale(binScale); bool invertVertical = (attributes.value("invertVertical").trimmed() == "true"); @@ -1788,5 +1256,50 @@ float min = attributes.value("minY").toFloat(&ok); float max = attributes.value("maxY").toFloat(&alsoOk); if (ok && alsoOk) setDisplayExtents(min, max); + + bool haveNewStyleNormalization = false; + + QString columnNormalization = attributes.value("columnNormalization"); + + if (columnNormalization != "") { + + haveNewStyleNormalization = true; + + if (columnNormalization == "peak") { + setNormalization(ColumnNormalization::Max1); + } else if (columnNormalization == "hybrid") { + setNormalization(ColumnNormalization::Hybrid); + } else if (columnNormalization == "none") { + setNormalization(ColumnNormalization::None); + } else { + cerr << "NOTE: Unknown or unsupported columnNormalization attribute \"" + << columnNormalization << "\"" << endl; + } + } + + if (!haveNewStyleNormalization) { + + setNormalization(ColumnNormalization::None); + + bool normalizeColumns = + (attributes.value("normalizeColumns").trimmed() == "true"); + if (normalizeColumns) { + setNormalization(ColumnNormalization::Max1); + } + + bool normalizeHybrid = + (attributes.value("normalizeHybrid").trimmed() == "true"); + if (normalizeHybrid) { + setNormalization(ColumnNormalization::Hybrid); + } + } + + bool normalizeVisibleArea = + (attributes.value("normalizeVisibleArea").trimmed() == "true"); + setNormalizeVisibleArea(normalizeVisibleArea); + + //!!! todo: check save/reload scaling, compare with + //!!! SpectrogramLayer, compare with prior SV versions, compare + //!!! with Tony v1 and v2 and their save files } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/Colour3DPlotLayer.h --- a/layer/Colour3DPlotLayer.h Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/Colour3DPlotLayer.h Fri Aug 19 15:58:57 2016 +0100 @@ -13,10 +13,14 @@ COPYING included with this distribution for more information. */ -#ifndef _COLOUR_3D_PLOT_H_ -#define _COLOUR_3D_PLOT_H_ +#ifndef COLOUR_3D_PLOT_LAYER_H +#define COLOUR_3D_PLOT_LAYER_H #include "SliceableLayer.h" +#include "VerticalBinLayer.h" + +#include "ColourScale.h" +#include "Colour3DPlotRenderer.h" #include "data/model/DenseThreeDimensionalModel.h" @@ -30,14 +34,11 @@ * colour range. Its source is a DenseThreeDimensionalModel. * * This was the original implementation for the spectrogram view, but - * it was replaced with a more efficient implementation that derived - * the spectrogram itself from a DenseTimeValueModel instead of using - * a three-dimensional model. This class is retained in case it - * becomes useful, but it will probably need some cleaning up if it's - * ever actually used. + * it was replaced for that purpose with a more efficient + * implementation that derived the spectrogram itself from a + * DenseTimeValueModel instead of using a three-dimensional model. */ - -class Colour3DPlotLayer : public SliceableLayer +class Colour3DPlotLayer : public VerticalBinLayer { Q_OBJECT @@ -50,6 +51,7 @@ } virtual const Model *getModel() const { return m_model; } virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; + virtual void setSynchronousPainting(bool synchronous); virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; @@ -81,19 +83,14 @@ 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); virtual void setProperties(const QXmlAttributes &); - enum ColourScale { - LinearScale, - LogScale, - PlusMinusOneScale, - AbsoluteScale - }; - - void setColourScale(ColourScale); - ColourScale getColourScale() const { return m_colourScale; } + void setColourScale(ColourScaleType); + ColourScaleType getColourScale() const { return m_colourScale; } void setColourMap(int map); int getColourMap() const; @@ -104,11 +101,6 @@ */ void setGain(float gain); float getGain() const; - - enum BinScale { - LinearBinScale, - LogBinScale - }; /** * Specify the scale for the y axis. @@ -117,31 +109,20 @@ BinScale getBinScale() const; /** - * Normalize each column to its maximum value, independent of its - * neighbours. + * Specify the normalization mode for individual columns. */ - void setNormalizeColumns(bool n); - bool getNormalizeColumns() const; + void setNormalization(ColumnNormalization); + ColumnNormalization getNormalization() const; /** - * Normalize each value against the maximum in the visible region. + * Specify whether to normalize the visible area. */ - void setNormalizeVisibleArea(bool n); + void setNormalizeVisibleArea(bool); 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; - void setShowRectified(bool); - bool getShowRectified() const { return m_rectified; } - void setOpaque(bool i); bool getOpaque() const; @@ -176,42 +157,50 @@ protected: const DenseThreeDimensionalModel *m_model; // I do not own this - mutable QImage *m_cache; - mutable QImage *m_peaksCache; - mutable int m_cacheValidStart; - mutable int m_cacheValidEnd; - - ColourScale m_colourScale; - bool m_colourScaleSet; - int m_colourMap; - float m_gain; - BinScale m_binScale; - bool m_normalizeColumns; - bool m_normalizeVisibleArea; - bool m_normalizeHybrid; - bool m_invertVertical; - bool m_rectified; - bool m_opaque; - bool m_smooth; - int m_peakResolution; + ColourScaleType m_colourScale; + bool m_colourScaleSet; + int m_colourMap; + float m_gain; + BinScale m_binScale; + ColumnNormalization m_normalization; // of individual columns + bool m_normalizeVisibleArea; + bool m_invertVertical; + bool m_opaque; + bool m_smooth; + 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; + int m_miny; + int m_maxy; + bool m_synchronous; + + static ColourScaleType convertToColourScale(int value); + static int convertFromColourScale(ColourScaleType); + static std::pair convertToColumnNorm(int value); + static int convertFromColumnNorm(ColumnNormalization norm, bool visible); + + mutable Dense3DModelPeakCache *m_peakCache; + const int m_peakCacheDivisor; + Dense3DModelPeakCache *getPeakCache() const; + + typedef std::map ViewMagMap; // key is view id + mutable ViewMagMap m_viewMags; + + typedef std::map ViewRendererMap; // key is view id + mutable ViewRendererMap m_renderers; + + Colour3DPlotRenderer *getRenderer(const LayerGeometryProvider *) const; + void invalidateRenderers(); + /** * 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; + double getYForBin(const LayerGeometryProvider *, double bin) const; /** * Return the bin number, possibly fractional, at the given y @@ -219,25 +208,13 @@ * 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; + double getBinForY(const LayerGeometryProvider *, double 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; - int getColourScaleWidth(QPainter &) const; - void fillCache(int firstBin, int lastBin) const; - void paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; + void paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; }; #endif diff -r 96cf499fad62 -r c0d841cb8ab9 layer/Colour3DPlotRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotRenderer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,1051 @@ +/* -*- 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-2016 Chris Cannam and QMUL. + + 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 "Colour3DPlotRenderer.h" +#include "RenderTimer.h" + +#include "base/Profiler.h" + +#include "data/model/DenseThreeDimensionalModel.h" +#include "data/model/Dense3DModelPeakCache.h" +#include "data/model/FFTModel.h" + +#include "LayerGeometryProvider.h" +#include "VerticalBinLayer.h" +#include "PaintAssistant.h" +#include "ImageRegionFinder.h" + +#include "view/ViewManager.h" // for main model sample rate. Pity + +#include + +//#define DEBUG_COLOUR_PLOT_REPAINT 1 + +using namespace std; + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::render(const LayerGeometryProvider *v, QPainter &paint, QRect rect) +{ + return render(v, paint, rect, false); +} + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::renderTimeConstrained(const LayerGeometryProvider *v, + QPainter &paint, QRect rect) +{ + return render(v, paint, rect, true); +} + +QRect +Colour3DPlotRenderer::getLargestUncachedRect(const LayerGeometryProvider *v) +{ + RenderType renderType = decideRenderType(v); + + if (renderType == DirectTranslucent) { + return QRect(); // never cached + } + + int h = m_cache.getSize().height(); + + QRect areaLeft(0, 0, m_cache.getValidLeft(), h); + QRect areaRight(m_cache.getValidRight(), 0, + m_cache.getSize().width() - m_cache.getValidRight(), h); + + if (areaRight.width() > areaLeft.width()) { + return areaRight; + } else { + return areaLeft; + } +} + +bool +Colour3DPlotRenderer::geometryChanged(const LayerGeometryProvider *v) +{ + RenderType renderType = decideRenderType(v); + + if (renderType == DirectTranslucent) { + return true; // never cached + } + + if (m_cache.getSize() == v->getPaintSize() && + m_cache.getZoomLevel() == v->getZoomLevel() && + m_cache.getStartFrame() == v->getStartFrame()) { + return false; + } else { + return true; + } +} + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect, bool timeConstrained) +{ + RenderType renderType = decideRenderType(v); + + if (renderType != DrawBufferPixelResolution) { + // Rendering should be fast in bin-resolution and direct draw + // cases because we are quite well zoomed-in, and the sums are + // easier this way. Calculating boundaries later will be + // fiddly for partial paints otherwise. + timeConstrained = false; + } + + 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(); + + sv_frame_t startFrame = v->getStartFrame(); + + m_cache.resize(v->getPaintSize()); + m_cache.setZoomLevel(v->getZoomLevel()); + + m_magCache.resize(v->getPaintSize().width()); + m_magCache.setZoomLevel(v->getZoomLevel()); + + if (renderType == DirectTranslucent) { + MagnitudeRange range = renderDirectTranslucent(v, paint, rect); + return { rect, range }; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "cache start " << m_cache.getStartFrame() + << " valid left " << m_cache.getValidLeft() + << " valid right " << m_cache.getValidRight() + << endl; + cerr << " view start " << startFrame + << " x0 " << x0 + << " x1 " << x1 + << endl; +#endif + + if (m_cache.isValid()) { // some part of the cache is valid + + if (v->getXForFrame(m_cache.getStartFrame()) == + v->getXForFrame(startFrame) && + m_cache.getValidLeft() <= x0 && + m_cache.getValidRight() >= x1) { + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "cache hit" << endl; +#endif + + // cache is valid for the complete requested area + paint.drawImage(rect, m_cache.getImage(), rect); + + MagnitudeRange range = m_magCache.getRange(x0, x1 - x0); + + return { rect, range }; + + } else { +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "cache partial hit" << endl; +#endif + + // cache doesn't begin at the right frame or doesn't + // contain the complete view, but might be scrollable or + // partially usable + m_cache.scrollTo(v, startFrame); + m_magCache.scrollTo(v, startFrame); + + // if we are not time-constrained, then we want to paint + // the whole area in one go; we don't return a partial + // paint. To avoid providing the more complex logic to + // handle painting discontiguous areas, if the only valid + // part of cache is in the middle, just make the whole + // thing invalid and start again. + if (!timeConstrained) { + if (m_cache.getValidLeft() > x0 && + m_cache.getValidRight() < x1) { + m_cache.invalidate(); + } + } + } + } else { + // cache is completely invalid + m_cache.setStartFrame(startFrame); + m_magCache.setStartFrame(startFrame); + } + + bool rightToLeft = false; + + int reqx0 = x0; + int reqx1 = x1; + + if (!m_cache.isValid() && timeConstrained) { + // When rendering the whole area, in a context where we might + // not be able to complete the work, start from somewhere near + // the middle so that the region of interest appears first + + //!!! (perhaps we should avoid doing this if past repaints + //!!! have been fast enough to do the whole in one shot) + if (x0 == 0 && x1 == v->getPaintWidth()) { + x0 = int(x1 * 0.3); + } + } + + if (m_cache.isValid()) { + + // 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; + m_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; + } + + // Note, we always paint the full height to cache. We want to + // ensure the cache is coherent without having to worry about + // vertical matching of required and valid areas as well as + // horizontal. + + if (renderType == DrawBufferBinResolution) { + + renderToCacheBinResolution(v, x0, x1 - x0); + + } else { // must be DrawBufferPixelResolution, handled DirectTranslucent earlier + + renderToCachePixelResolution(v, x0, x1 - x0, rightToLeft, timeConstrained); + } + + QRect pr = rect & m_cache.getValidArea(); + paint.drawImage(pr.x(), pr.y(), m_cache.getImage(), + pr.x(), pr.y(), pr.width(), pr.height()); + + if (!timeConstrained && (pr != rect)) { + cerr << "WARNING: failed to render entire requested rect " + << "even when not time-constrained" << endl; + } + + MagnitudeRange range = m_magCache.getRange(reqx0, reqx1 - reqx0); + + return { pr, range }; +} + +Colour3DPlotRenderer::RenderType +Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const +{ + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !v || !(v->getViewManager())) { + return DrawBufferPixelResolution; // or anything + } + + int binResolution = model->getResolution(); + int zoomLevel = v->getZoomLevel(); + sv_samplerate_t modelRate = model->getSampleRate(); + + double rateRatio = v->getViewManager()->getMainModelSampleRate() / modelRate; + double relativeBinResolution = binResolution * rateRatio; + + if (m_params.binDisplay == BinDisplay::PeakFrequencies) { + // no alternative works here + return DrawBufferPixelResolution; + } + + if (!m_params.alwaysOpaque && !m_params.interpolate) { + + // consider translucent option -- only if not smoothing & not + // explicitly requested opaque & sufficiently zoomed-in + + if (model->getHeight() * 3 < v->getPaintHeight() && + relativeBinResolution >= 3 * zoomLevel) { + return DirectTranslucent; + } + } + + if (relativeBinResolution > zoomLevel) { + return DrawBufferBinResolution; + } else { + return DrawBufferPixelResolution; + } +} + +ColumnOp::Column +Colour3DPlotRenderer::getColumn(int sx, int minbin, int nbins) const +{ + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // we do the first bit here: + // get column -> scale -> normalise + + ColumnOp::Column column; + + if (m_params.colourScale.getScale() == ColourScaleType::Phase && + m_sources.fft) { + + ColumnOp::Column fullColumn = m_sources.fft->getPhases(sx); + + column = vector(fullColumn.data() + minbin, + fullColumn.data() + minbin + nbins); + + } else { + + ColumnOp::Column fullColumn = m_sources.source->getColumn(sx); + + column = vector(fullColumn.data() + minbin, + fullColumn.data() + minbin + nbins); + + column = ColumnOp::applyGain(column, m_params.scaleFactor); + + column = ColumnOp::normalize(column, m_params.normalization); + } + + return column; +} + +MagnitudeRange +Colour3DPlotRenderer::renderDirectTranslucent(const LayerGeometryProvider *v, + QPainter &paint, + QRect rect) +{ + Profiler profiler("Colour3DPlotRenderer::renderDirectTranslucent"); + + MagnitudeRange magRange; + + QPoint illuminatePos; + bool illuminate = v->shouldIlluminateLocalFeatures + (m_sources.verticalBinLayer, illuminatePos); + + const DenseThreeDimensionalModel *model = m_sources.source; + + int x0 = rect.left(); + int x1 = rect.right() + 1; + + int h = v->getPaintHeight(); + + sv_frame_t modelStart = model->getStartFrame(); + sv_frame_t modelEnd = model->getEndFrame(); + int modelResolution = model->getResolution(); + + double rateRatio = + v->getViewManager()->getMainModelSampleRate() / model->getSampleRate(); + + // the s-prefix values are source, i.e. model, column and bin numbers + int sx0 = int((double(v->getFrameForX(x0)) / rateRatio - double(modelStart)) + / modelResolution); + int sx1 = int((double(v->getFrameForX(x1)) / rateRatio - double(modelStart)) + / modelResolution); + + int sh = model->getHeight(); + + const int buflen = 40; + char labelbuf[buflen]; + + int minbin = m_sources.verticalBinLayer->getIBinForY(v, h); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = m_sources.verticalBinLayer->getIBinForY(v, 0) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + int psx = -1; + + vector preparedColumn; + + int modelWidth = model->getWidth(); + + for (int sx = sx0; sx <= sx1; ++sx) { + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // this does the first three: + preparedColumn = getColumn(sx, minbin, nbins); + + magRange.sample(preparedColumn); + + if (m_params.binDisplay == BinDisplay::PeakBins) { + preparedColumn = ColumnOp::peakPick(preparedColumn); + } + + // Display gain belongs to the colour scale and is + // applied by the colour scale object when mapping it + + psx = sx; + } + + sv_frame_t fx = sx * modelResolution + modelStart; + + if (fx + modelResolution <= modelStart || fx > modelEnd) continue; + + int rx0 = v->getXForFrame(int(double(fx) * rateRatio)); + int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * rateRatio)); + + int rw = rx1 - rx0; + if (rw < 1) rw = 1; + + bool showLabel = (rw > 10 && + paint.fontMetrics().width("0.000000") < rw - 3 && + paint.fontMetrics().height() < (h / sh)); + + for (int sy = minbin; sy < minbin + nbins; ++sy) { + + int ry0 = m_sources.verticalBinLayer->getIYForBin(v, sy); + int ry1 = m_sources.verticalBinLayer->getIYForBin(v, sy + 1); + + if (m_params.invertVertical) { + ry0 = h - ry0 - 1; + ry1 = h - ry1 - 1; + } + + QRect r(rx0, ry1, rw, ry0 - ry1); + + float value = preparedColumn[sy - minbin]; + QColor colour = m_params.colourScale.getColour(value, + m_params.colourRotation); + + if (rw == 1) { + paint.setPen(colour); + paint.setBrush(Qt::NoBrush); + paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1); + continue; + } + + QColor pen(255, 255, 255, 80); + QColor brush(colour); + + if (rw > 3 && r.height() > 3) { + brush.setAlpha(160); + } + + paint.setPen(Qt::NoPen); + paint.setBrush(brush); + + if (illuminate) { + if (r.contains(illuminatePos)) { + paint.setPen(v->getForeground()); + } + } + +#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT +// cerr << "rect " << r.x() << "," << r.y() << " " +// << r.width() << "x" << r.height() << endl; +#endif + + paint.drawRect(r); + + if (showLabel) { + double value = model->getValueAt(sx, sy); + snprintf(labelbuf, buflen, "%06f", value); + QString text(labelbuf); + PaintAssistant::drawVisibleText + (v, + paint, + rx0 + 2, + ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(), + text, + PaintAssistant::OutlinedText); + } + } + } + + return magRange; +} + +void +Colour3DPlotRenderer::renderToCachePixelResolution(const LayerGeometryProvider *v, + int x0, int repaintWidth, + bool rightToLeft, + bool timeConstrained) +{ + Profiler profiler("Colour3DPlotRenderer::renderToCachePixelResolution"); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "renderToCachePixelResolution" << endl; +#endif + + // Draw to the draw buffer, and then copy from there. The draw + // buffer is at the same resolution as the target in the cache, so + // no extra scaling needed. + + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !model->isOK() || !model->isReady()) { + throw std::logic_error("no source model provided, or model not ready"); + } + + int h = v->getPaintHeight(); + + clearDrawBuffer(repaintWidth, h); + + vector binforx(repaintWidth); + vector binfory(h); + + bool usePeaksCache = false; + int binsPerPeak = 1; + int zoomLevel = v->getZoomLevel(); + int binResolution = model->getResolution(); + + for (int x = 0; x < repaintWidth; ++x) { + sv_frame_t f0 = v->getFrameForX(x0 + x); + double s0 = double(f0 - model->getStartFrame()) / binResolution; + binforx[x] = int(s0 + 0.0001); + } + + if (m_sources.peaks) { // peaks cache exists + + binsPerPeak = m_sources.peaks->getColumnsPerPeak(); + usePeaksCache = (binResolution * binsPerPeak) < zoomLevel; + + if (m_params.colourScale.getScale() == + ColourScaleType::Phase) { + usePeaksCache = false; + } + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "[PIX] zoomLevel = " << zoomLevel + << ", binResolution " << binResolution + << ", binsPerPeak " << binsPerPeak + << ", peak cache " << m_sources.peaks + << ", usePeaksCache = " << usePeaksCache + << endl; +#endif + + for (int y = 0; y < h; ++y) { + binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); + } + + int attainedWidth; + + if (m_params.binDisplay == BinDisplay::PeakFrequencies) { + attainedWidth = renderDrawBufferPeakFrequencies(v, + repaintWidth, + h, + binforx, + binfory, + rightToLeft, + timeConstrained); + + } else { + attainedWidth = renderDrawBuffer(repaintWidth, + h, + binforx, + binfory, + usePeaksCache, + rightToLeft, + timeConstrained); + } + + if (attainedWidth == 0) return; + + // draw buffer is pixel resolution, no scaling factors or padding involved + + int paintedLeft = x0; + if (rightToLeft) { + paintedLeft += (repaintWidth - attainedWidth); + } + + m_cache.drawImage(paintedLeft, attainedWidth, + m_drawBuffer, + paintedLeft - x0, attainedWidth); + + for (int i = 0; in_range_for(m_magRanges, i); ++i) { + m_magCache.sampleColumn(i, m_magRanges.at(i)); + } +} + +void +Colour3DPlotRenderer::renderToCacheBinResolution(const LayerGeometryProvider *v, + int x0, int repaintWidth) +{ + Profiler profiler("Colour3DPlotRenderer::renderToCacheBinResolution"); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "renderToCacheBinResolution" << endl; +#endif + + // Draw to the draw buffer, and then scale-copy from there. Draw + // buffer is at bin resolution, i.e. buffer x == source column + // number. We use toolkit smooth scaling for interpolation. + + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !model->isOK() || !model->isReady()) { + throw std::logic_error("no source model provided, or model not ready"); + } + + // The draw buffer will contain a fragment at bin resolution. We + // need to ensure that it starts and ends at points where a + // time-bin boundary occurs at an exact pixel boundary, and with a + // certain amount of overlap across existing pixels so that we can + // scale and draw from it without smoothing errors at the edges. + + // If (getFrameForX(x) / increment) * increment == + // getFrameForX(x), then x is a time-bin boundary. We want two + // such boundaries at either side of the draw buffer -- one which + // we draw up to, and one which we subsequently crop at. + + sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; + sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; + + int drawBufferWidth; + int binResolution = model->getResolution(); + + for (int x = x0; ; --x) { + sv_frame_t f = v->getFrameForX(x); + if ((f / binResolution) * binResolution == f) { + if (leftCropFrame == -1) leftCropFrame = f; + else if (x < x0 - 2) { + leftBoundaryFrame = f; + break; + } + } + } + for (int x = x0 + repaintWidth; ; ++x) { + sv_frame_t f = v->getFrameForX(x); + if ((f / binResolution) * binResolution == f) { + if (rightCropFrame == -1) rightCropFrame = f; + else if (x > x0 + repaintWidth + 2) { + rightBoundaryFrame = f; + break; + } + } + } + drawBufferWidth = int + ((rightBoundaryFrame - leftBoundaryFrame) / binResolution); + + int h = v->getPaintHeight(); + + // For our purposes here, the draw buffer needs to be exactly our + // target size (so we recreate always rather than just clear it) + + recreateDrawBuffer(drawBufferWidth, h); + + vector binforx(drawBufferWidth); + vector binfory(h); + + for (int x = 0; x < drawBufferWidth; ++x) { + binforx[x] = int(leftBoundaryFrame / binResolution) + x; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "[BIN] binResolution " << binResolution + << endl; +#endif + + for (int y = 0; y < h; ++y) { + binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); + } + + int attainedWidth = renderDrawBuffer(drawBufferWidth, + h, + binforx, + binfory, + false, + false, + false); + + if (attainedWidth == 0) return; + + int scaledLeft = v->getXForFrame(leftBoundaryFrame); + int scaledRight = v->getXForFrame(rightBoundaryFrame); + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "scaling draw buffer from width " << m_drawBuffer.width() + << " to " << (scaledRight - scaledLeft) << " (nb drawBufferWidth = " + << drawBufferWidth << ")" << endl; +#endif + + QImage scaled = m_drawBuffer.scaled + (scaledRight - scaledLeft, h, + Qt::IgnoreAspectRatio, (m_params.interpolate ? + Qt::SmoothTransformation : + Qt::FastTransformation)); + + int scaledLeftCrop = v->getXForFrame(leftCropFrame); + int scaledRightCrop = v->getXForFrame(rightCropFrame); + + int targetLeft = scaledLeftCrop; + if (targetLeft < 0) { + targetLeft = 0; + } + + int targetWidth = scaledRightCrop - targetLeft; + if (targetLeft + targetWidth > m_cache.getSize().width()) { + targetWidth = m_cache.getSize().width() - targetLeft; + } + + int sourceLeft = targetLeft - scaledLeft; + if (sourceLeft < 0) { + sourceLeft = 0; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "repaintWidth = " << repaintWidth + << ", targetWidth = " << targetWidth << endl; +#endif + + if (targetWidth > 0) { + // we are copying from an image that has already been scaled, + // hence using the same width in both geometries + m_cache.drawImage(targetLeft, targetWidth, + scaled, + sourceLeft, targetWidth); + } + + for (int i = 0; i < targetWidth; ++i) { + // but the mag range vector has not been scaled + int sourceIx = int((double(i + sourceLeft) / scaled.width()) + * int(m_magRanges.size())); + if (in_range_for(m_magRanges, sourceIx)) { + m_magCache.sampleColumn(i, m_magRanges.at(sourceIx)); + } + } +} + +int +Colour3DPlotRenderer::renderDrawBuffer(int w, int h, + const vector &binforx, + const vector &binfory, + bool usePeaksCache, + bool rightToLeft, + bool timeConstrained) +{ + // Callers must have checked that the appropriate subset of + // Sources data members are set for the supplied flags (e.g. that + // peaks model exists if usePeaksCache) + + RenderTimer timer(timeConstrained ? + RenderTimer::FastRender : + RenderTimer::NoTimeout); + + int divisor = 1; + const DenseThreeDimensionalModel *sourceModel = m_sources.source; + if (usePeaksCache) { + divisor = m_sources.peaks->getColumnsPerPeak(); + sourceModel = m_sources.peaks; + } + + int sh = sourceModel->getHeight(); + + int minbin = int(binfory[0] + 0.0001); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = int(binfory[h-1]) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + int psx = -1; + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + vector preparedColumn; + + int modelWidth = sourceModel->getWidth(); + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "modelWidth " << modelWidth << ", divisor " << divisor << endl; +#endif + + for (int x = start; x != finish; x += step) { + + // x is the on-canvas pixel coord; sx (later) will be the + // source column index + + ++columnCount; + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "x = " << x << ", binforx[x] = " << binforx[x] << endl; +#endif + + if (binforx[x] < 0) continue; + + int sx0 = binforx[x] / divisor; + int sx1 = sx0; + if (x+1 < w) sx1 = binforx[x+1] / divisor; + if (sx0 < 0) sx0 = sx1 - 1; + if (sx0 < 0) continue; + if (sx1 <= sx0) sx1 = sx0 + 1; + + vector pixelPeakColumn; + MagnitudeRange magRange; + + for (int sx = sx0; sx < sx1; ++sx) { + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "sx = " << sx << endl; +#endif + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // this does the first three: + ColumnOp::Column column = getColumn(sx, minbin, nbins); + + magRange.sample(column); + + if (m_params.binDisplay == BinDisplay::PeakBins) { + column = ColumnOp::peakPick(column); + } + + preparedColumn = + ColumnOp::distribute(column, + h, + binfory, + minbin, + m_params.interpolate); + + // Display gain belongs to the colour scale and is + // applied by the colour scale object when mapping it + + psx = sx; + } + + if (sx == sx0) { + pixelPeakColumn = preparedColumn; + } else { + for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) { + pixelPeakColumn[i] = std::max(pixelPeakColumn[i], + preparedColumn[i]); + } + } + } + + if (!pixelPeakColumn.empty()) { + + for (int y = 0; y < h; ++y) { + int py; + if (m_params.invertVertical) { + py = y; + } else { + py = h - y - 1; + } + m_drawBuffer.setPixel + (x, + py, + m_params.colourScale.getPixel(pixelPeakColumn[y])); + } + + m_magRanges.push_back(magRange); + } + + double fractionComplete = double(columnCount) / double(w); + if (timer.outOfTime(fractionComplete)) { + cerr << "out of time" << endl; + return columnCount; + } + } + + return columnCount; +} + +int +Colour3DPlotRenderer::renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v, + int w, int h, + const vector &binforx, + const vector &binfory, + bool rightToLeft, + bool timeConstrained) +{ + // Callers must have checked that the appropriate subset of + // Sources data members are set for the supplied flags (e.g. that + // fft model exists) + + RenderTimer timer(timeConstrained ? + RenderTimer::FastRender : + RenderTimer::NoTimeout); + + const FFTModel *fft = m_sources.fft; + + int sh = fft->getHeight(); + + int minbin = int(binfory[0] + 0.0001); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = int(binfory[h-1]) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + FFTModel::PeakSet peakfreqs; + + int psx = -1; + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + vector preparedColumn; + + int modelWidth = fft->getWidth(); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "modelWidth " << modelWidth << endl; +#endif + + double minFreq = + (double(minbin) * fft->getSampleRate()) / fft->getFFTSize(); + double maxFreq = + (double(minbin + nbins - 1) * fft->getSampleRate()) / fft->getFFTSize(); + + bool logarithmic = (m_params.binScale == BinScale::Log); + + for (int x = start; x != finish; x += step) { + + // x is the on-canvas pixel coord; sx (later) will be the + // source column index + + ++columnCount; + + if (binforx[x] < 0) continue; + + int sx0 = binforx[x]; + int sx1 = sx0; + if (x+1 < w) sx1 = binforx[x+1]; + if (sx0 < 0) sx0 = sx1 - 1; + if (sx0 < 0) continue; + if (sx1 <= sx0) sx1 = sx0 + 1; + + vector pixelPeakColumn; + MagnitudeRange magRange; + + for (int sx = sx0; sx < sx1; ++sx) { + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + preparedColumn = getColumn(sx, minbin, nbins); + magRange.sample(preparedColumn); + psx = sx; + } + + if (sx == sx0) { + pixelPeakColumn = preparedColumn; + peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, + minbin, minbin + nbins - 1); + } else { + for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) { + pixelPeakColumn[i] = std::max(pixelPeakColumn[i], + preparedColumn[i]); + } + } + } + + if (!pixelPeakColumn.empty()) { + + for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); + pi != peakfreqs.end(); ++pi) { + + int bin = pi->first; + double freq = pi->second; + + if (bin < minbin) continue; + if (bin >= minbin + nbins) break; + + double value = pixelPeakColumn[bin - minbin]; + + double y = v->getYForFrequency + (freq, minFreq, maxFreq, logarithmic); + + int iy = int(y + 0.5); + if (iy < 0 || iy >= h) continue; + + m_drawBuffer.setPixel + (x, + iy, + m_params.colourScale.getPixel(value)); + } + + m_magRanges.push_back(magRange); + } + + double fractionComplete = double(columnCount) / double(w); + if (timer.outOfTime(fractionComplete)) { + return columnCount; + } + } + + return columnCount; +} + +void +Colour3DPlotRenderer::recreateDrawBuffer(int w, int h) +{ + m_drawBuffer = QImage(w, h, QImage::Format_Indexed8); + + for (int pixel = 0; pixel < 256; ++pixel) { + m_drawBuffer.setColor + ((unsigned char)pixel, + m_params.colourScale.getColourForPixel + (pixel, m_params.colourRotation).rgb()); + } + + m_drawBuffer.fill(0); + m_magRanges.clear(); +} + +void +Colour3DPlotRenderer::clearDrawBuffer(int w, int h) +{ + if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) { + recreateDrawBuffer(w, h); + } else { + m_drawBuffer.fill(0); + m_magRanges.clear(); + } +} + +QRect +Colour3DPlotRenderer::findSimilarRegionExtents(QPoint p) const +{ + QImage image = m_cache.getImage(); + ImageRegionFinder finder; + QRect rect = finder.findRegionExtents(&image, p); + return rect; +} diff -r 96cf499fad62 -r c0d841cb8ab9 layer/Colour3DPlotRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotRenderer.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,309 @@ +/* -*- 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-2016 Chris Cannam and QMUL. + + 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 COLOUR_3D_PLOT_RENDERER_H +#define COLOUR_3D_PLOT_RENDERER_H + +#include "ColourScale.h" +#include "ScrollableImageCache.h" +#include "ScrollableMagRangeCache.h" + +#include "base/ColumnOp.h" +#include "base/MagnitudeRange.h" + +#include +#include +#include + +class LayerGeometryProvider; +class VerticalBinLayer; +class DenseThreeDimensionalModel; +class Dense3DModelPeakCache; +class FFTModel; + +enum class BinDisplay { + AllBins, + PeakBins, + PeakFrequencies +}; + +enum class BinScale { + Linear, + Log +}; + +class Colour3DPlotRenderer +{ +public: + struct Sources { + Sources() : verticalBinLayer(0), source(0), peaks(0), fft(0) { } + + // These must all outlive this class + const VerticalBinLayer *verticalBinLayer; // always + const DenseThreeDimensionalModel *source; // always + const Dense3DModelPeakCache *peaks; // optionally + const FFTModel *fft; // optionally + }; + + struct Parameters { + Parameters() : + colourScale(ColourScale::Parameters()), + normalization(ColumnNormalization::None), + binDisplay(BinDisplay::AllBins), + binScale(BinScale::Linear), + alwaysOpaque(false), + interpolate(false), + invertVertical(false), + scaleFactor(1.0), + colourRotation(0) { } + + /** A complete ColourScale object by value, used for colour + * map conversion. Note that the final display gain setting is + * also encapsulated here. */ + ColourScale colourScale; + + /** Type of column normalization. */ + ColumnNormalization normalization; + + /** Selection of bins to display. */ + BinDisplay binDisplay; + + /** Scale for vertical bin spacing (linear or logarithmic). */ + BinScale binScale; + + /** Whether cells should always be opaque. If false, then + * large cells (when zoomed in a long way) will be rendered + * translucent in order not to obscure anything in a layer + * beneath. */ + bool alwaysOpaque; + + /** Whether to apply smoothing when rendering cells at more + * than one pixel per cell. !!! todo: decide about separating + * out x-interpolate and y-interpolate as the spectrogram + * actually does (or used to) + */ + bool interpolate; + + /** Whether to render the whole caboodle upside-down. */ + bool invertVertical; + + /** Initial scale factor (e.g. for FFT scaling). This factor + * is applied to all values read from the underlying model + * *before* magnitude ranges are calculated, in contrast to + * the display gain found in the ColourScale parameter. */ + double scaleFactor; + + /** Colourmap rotation, in the range 0-255. */ + int colourRotation; + }; + + Colour3DPlotRenderer(Sources sources, Parameters parameters) : + m_sources(sources), + m_params(parameters) + { } + + struct RenderResult { + /** + * The rect that was actually rendered. May be equal to the + * rect that was requested to render, or may be smaller if + * time ran out and the complete flag was not set. + */ + QRect rendered; + + /** + * The magnitude range of the data in the rendered area. + */ + MagnitudeRange range; + }; + + /** + * Render the requested area using the given painter, obtaining + * geometry (e.g. start frame) from the given + * LayerGeometryProvider. + * + * The whole of the supplied rect will be rendered and the + * returned QRect will be equal to the supplied QRect. (See + * renderTimeConstrained for an alternative that may render only + * part of the rect in cases where obtaining source data is slow + * and retaining responsiveness is important.) + * + * Note that Colour3DPlotRenderer retains internal cache state + * related to the size and position of the supplied + * LayerGeometryProvider. Although it is valid to call render() + * successively on the same Colour3DPlotRenderer with different + * LayerGeometryProviders, it will be much faster to use a + * dedicated Colour3DPlotRenderer for each LayerGeometryProvider. + * + * If the model to render from is not ready, this will throw a + * std::logic_error exception. The model must be ready and the + * layer requesting the render must not be dormant in its view, so + * that the LayerGeometryProvider returns valid results; it is the + * caller's responsibility to ensure these. + */ + RenderResult render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + /** + * Render the requested area using the given painter, obtaining + * geometry (e.g. start frame) from the stored + * LayerGeometryProvider. + * + * As much of the rect will be rendered as can be managed given + * internal time constraints (using a RenderTimer object + * internally). The returned QRect (the rendered field in the + * RenderResult struct) will contain the area that was + * rendered. Note that we always render the full requested height, + * it's only width that is time-constrained. + * + * Note that Colour3DPlotRenderer retains internal cache state + * related to the size and position of the supplied + * LayerGeometryProvider. Although it is valid to call render() + * successively on the same Colour3DPlotRenderer with different + * LayerGeometryProviders, it will be much faster to use a + * dedicated Colour3DPlotRenderer for each LayerGeometryProvider. + * + * If the model to render from is not ready, this will throw a + * std::logic_error exception. The model must be ready and the + * layer requesting the render must not be dormant in its view, so + * that the LayerGeometryProvider returns valid results; it is the + * caller's responsibility to ensure these. + */ + RenderResult renderTimeConstrained(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + /** + * Return the area of the largest rectangle within the entire area + * of the cache that is unavailable in the cache. This is only + * valid in relation to a preceding render() call which is + * presumed to have set the area, start frame, and zoom level for + * the cache. It could be used to establish a suitable region for + * a subsequent paint request (because if an area is not in the + * cache, it cannot have been rendered since the cache was + * cleared). + * + * Returns an empty QRect if the cache is entirely valid. + */ + QRect getLargestUncachedRect(const LayerGeometryProvider *v); + + /** + * Return true if the provider's geometry differs from the cache, + * or if we are not using a cache. i.e. if the cache will be + * regenerated for the next render, or the next render performed + * from scratch. + */ + bool geometryChanged(const LayerGeometryProvider *v); + + /** + * Return true if the rendering will be opaque. This may be used + * by the calling layer to determine whether it can scroll + * directly without regard to any other layers beneath. + */ + bool willRenderOpaque(const LayerGeometryProvider *v) { + return decideRenderType(v) != DirectTranslucent; + } + + /** + * Return the colour corresponding to the given value. + * \see ColourScale::getPixel + * \see ColourScale::getColour + */ + QColor getColour(double value) const { + return m_params.colourScale.getColour(value, m_params.colourRotation); + } + + /** + * Return the enclosing rectangle for the region of similar colour + * to the given point within the cache. Return an empty QRect if + * this is not possible. \see ImageRegionFinder + */ + QRect findSimilarRegionExtents(QPoint point) const; + +private: + Sources m_sources; + Parameters m_params; + + // Draw buffer is the target of each partial repaint. It is always + // at view height (not model height) and is cleared and repainted + // on each fragment render. The only reason it's stored as a data + // member is to avoid reallocation. + QImage m_drawBuffer; + + // A temporary store of magnitude ranges per-column, used when + // rendering to the draw buffer. This always has the same length + // as the width of the draw buffer, and the x coordinates of the + // two containers are equivalent. + std::vector m_magRanges; + + // The image cache is our persistent record of the visible + // area. It is always the same size as the view (i.e. the paint + // size reported by the LayerGeometryProvider) and is scrolled and + // partially repainted internally as appropriate. A render request + // is carried out by repainting to cache (via the draw buffer) any + // area that is being requested but is not valid in the cache, and + // then repainting from cache to the requested painter. + ScrollableImageCache m_cache; + + // The mag range cache is our record of the column magnitude + // ranges for each of the columns in the cache. It always has the + // same start frame and width as the image cache, and the column + // indices match up across both. Our cache update mechanism + // guarantees that every valid column in the image cache has a + // valid range in the magnitude cache, but not necessarily vice + // versa (as the image cache is limited to contiguous ranges). + ScrollableMagRangeCache m_magCache; + + RenderResult render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect, bool timeConstrained); + + MagnitudeRange renderDirectTranslucent(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + void renderToCachePixelResolution(const LayerGeometryProvider *v, int x0, + int repaintWidth, bool rightToLeft, + bool timeConstrained); + + void renderToCacheBinResolution(const LayerGeometryProvider *v, int x0, + int repaintWidth); + + int renderDrawBuffer(int w, int h, + const std::vector &binforx, + const std::vector &binfory, + bool usePeaksCache, + bool rightToLeft, + bool timeConstrained); + + int renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v, + int w, int h, + const std::vector &binforx, + const std::vector &binfory, + bool rightToLeft, + bool timeConstrained); + + void recreateDrawBuffer(int w, int h); + void clearDrawBuffer(int w, int h); + + enum RenderType { + DrawBufferPixelResolution, + DrawBufferBinResolution, + DirectTranslucent + }; + + RenderType decideRenderType(const LayerGeometryProvider *) const; + + ColumnOp::Column getColumn(int sx, int minbin, int nbins) const; +}; + +#endif + diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ColourMapper.cpp --- a/layer/ColourMapper.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/ColourMapper.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -21,8 +21,45 @@ #include "base/Debug.h" +#include + +using namespace std; + +static vector convertStrings(const vector &strs) +{ + vector converted; + for (const auto &s: strs) converted.push_back(QColor(s)); + reverse(converted.begin(), converted.end()); + return converted; +} + +static vector ice = convertStrings({ + // Based on ColorBrewer ylGnBu + "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5", + "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040" + }); + +static vector cherry = convertStrings({ + "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497", + "#ae017e", "#7a0177", "#49006a" + }); + +static void +mapDiscrete(double norm, vector &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), m_max(max) @@ -47,25 +84,25 @@ QString ColourMapper::getColourMapName(int n) { - if (n >= getColourMapCount()) return tr(""); + if (n >= getColourMapCount()) return QObject::tr(""); StandardMap map = (StandardMap)n; switch (map) { - case DefaultColours: return tr("Default"); - 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 Sunset: return tr("Sunset"); - case FruitSalad: return tr("Fruit Salad"); - case Banded: return tr("Banded"); - case Highlight: return tr("Highlight"); - case Printer: return tr("Printer"); - case HighGain: return tr("High Gain"); + case Green: return QObject::tr("Green"); + case WhiteOnBlack: return QObject::tr("White on Black"); + case BlackOnWhite: return QObject::tr("Black on White"); + case Cherry: return QObject::tr("Cherry"); + case Wasp: return QObject::tr("Wasp"); + case Ice: return QObject::tr("Ice"); + case Sunset: return QObject::tr("Sunset"); + case FruitSalad: return QObject::tr("Fruit Salad"); + case Banded: return QObject::tr("Banded"); + case Highlight: return QObject::tr("Highlight"); + case Printer: return QObject::tr("Printer"); + case HighGain: return QObject::tr("High Gain"); } - return tr(""); + return QObject::tr(""); } QColor @@ -85,7 +122,7 @@ switch (map) { - case DefaultColours: + case Green: h = blue - norm * 2.0 * pieslice; s = 0.5f + norm/2.0; v = norm; @@ -101,30 +138,17 @@ hsv = false; break; - case RedOnBlue: - h = blue - pieslice/4.0 + norm * (pieslice + pieslice/4.0); - s = 1.0; - v = norm; + case Cherry: + hsv = false; + mapDiscrete(norm, cherry, r, g, b); break; - case YellowOnBlack: + case Wasp: h = 0.15; s = 1.0; v = norm; break; - case BlueOnBlack: - h = blue; - s = 1.0; - v = norm * 2.0; - if (v > 1.0) { - v = 1.0; - s = 1.0 - (sqrt(norm) - 0.707) * 3.413; - if (s < 0.0) s = 0.0; - if (s > 1.0) s = 1.0; - } - break; - case Sunset: r = (norm - 0.24) * 2.38; if (r > 1.0) r = 1.0; @@ -207,6 +231,10 @@ hsv = false; */ break; + + case Ice: + hsv = false; + mapDiscrete(norm, ice, r, g, b); } if (hsv) { @@ -224,7 +252,7 @@ switch (map) { - case DefaultColours: + case Green: return QColor(255, 150, 50); case WhiteOnBlack: @@ -233,13 +261,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: @@ -277,12 +305,12 @@ case HighGain: return true; - case DefaultColours: + case Green: case Sunset: case WhiteOnBlack: - case RedOnBlue: - case YellowOnBlack: - case BlueOnBlack: + case Cherry: + case Wasp: + case Ice: case FruitSalad: case Banded: case Highlight: diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ColourMapper.h --- a/layer/ColourMapper.h Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/ColourMapper.h Fri Aug 19 15:58:57 2016 +0100 @@ -23,23 +23,23 @@ /** * A class for mapping intensity values onto various colour maps. */ - -class ColourMapper : public QObject +class ColourMapper { - Q_OBJECT - public: ColourMapper(int map, double minValue, double maxValue); - virtual ~ColourMapper(); + ~ColourMapper(); + + ColourMapper(const ColourMapper &) = default; + ColourMapper &operator=(const ColourMapper &) = default; enum StandardMap { - DefaultColours, + Green, Sunset, WhiteOnBlack, BlackOnWhite, - RedOnBlue, - YellowOnBlack, - BlueOnBlack, + Cherry, + Wasp, + Ice, FruitSalad, Banded, Highlight, diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ColourScale.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourScale.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,160 @@ +/* -*- 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-2016 Chris Cannam and QMUL. + + 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 "ColourScale.h" + +#include "base/AudioLevel.h" +#include "base/LogRange.h" + +#include +#include + +using namespace std; + +int ColourScale::m_maxPixel = 255; + +ColourScale::ColourScale(Parameters parameters) : + m_params(parameters), + m_mapper(m_params.colourMap, 1.f, double(m_maxPixel)) +{ + if (m_params.minValue >= m_params.maxValue) { + cerr << "ERROR: ColourScale::ColourScale: minValue = " + << m_params.minValue << ", maxValue = " << m_params.maxValue << endl; + throw std::logic_error("maxValue must be greater than minValue"); + } + + m_mappedMin = m_params.minValue; + m_mappedMax = m_params.maxValue; + + if (m_mappedMin < m_params.threshold) { + m_mappedMin = m_params.threshold; + } + + if (m_params.scaleType == ColourScaleType::Log) { + + LogRange::mapRange(m_mappedMin, m_mappedMax); + + } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { + + m_mappedMin = -1.0; + m_mappedMax = 1.0; + + } else if (m_params.scaleType == ColourScaleType::Absolute) { + + m_mappedMin = fabs(m_mappedMin); + m_mappedMax = fabs(m_mappedMax); + if (m_mappedMin >= m_mappedMax) { + std::swap(m_mappedMin, m_mappedMax); + } + } + + if (m_mappedMin >= m_mappedMax) { + cerr << "ERROR: ColourScale::ColourScale: minValue = " << m_params.minValue + << ", maxValue = " << m_params.maxValue + << ", threshold = " << m_params.threshold + << ", scale = " << int(m_params.scaleType) + << " resulting in mapped minValue = " << m_mappedMin + << ", mapped maxValue = " << m_mappedMax << endl; + throw std::logic_error("maxValue must be greater than minValue [after mapping]"); + } +} + +ColourScale::~ColourScale() +{ +} + +ColourScaleType +ColourScale::getScale() const +{ + return m_params.scaleType; +} + +int +ColourScale::getPixel(double value) const +{ + double maxPixF = m_maxPixel; + + if (m_params.scaleType == ColourScaleType::Phase) { + double half = (maxPixF - 1.f) / 2.f; + int pixel = 1 + int((value * half) / M_PI + half); +// cerr << "phase = " << value << " pixel = " << pixel << endl; + return pixel; + } + + value *= m_params.gain; + + if (value < m_params.threshold) return 0; + + double mapped = value; + + if (m_params.scaleType == ColourScaleType::Log) { + mapped = LogRange::map(value); + } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { + if (mapped < -1.f) mapped = -1.f; + if (mapped > 1.f) mapped = 1.f; + } else if (m_params.scaleType == ColourScaleType::Absolute) { + if (mapped < 0.f) mapped = -mapped; + } + + mapped *= m_params.multiple; + + if (mapped < m_mappedMin) { + mapped = m_mappedMin; + } + if (mapped > m_mappedMax) { + mapped = m_mappedMax; + } + + double proportion = (mapped - m_mappedMin) / (m_mappedMax - m_mappedMin); + + int pixel = 0; + + if (m_params.scaleType == ColourScaleType::Meter) { + pixel = AudioLevel::multiplier_to_preview(proportion, m_maxPixel-1) + 1; + } else { + pixel = int(proportion * maxPixF) + 1; + } + + if (pixel < 0) { + pixel = 0; + } + if (pixel > m_maxPixel) { + pixel = m_maxPixel; + } + return pixel; +} + +QColor +ColourScale::getColourForPixel(int pixel, int rotation) const +{ + if (pixel < 0) { + pixel = 0; + } + if (pixel > m_maxPixel) { + pixel = m_maxPixel; + } + if (pixel == 0) { + if (m_mapper.hasLightBackground()) { + return Qt::white; + } else { + return Qt::black; + } + } else { + int target = int(pixel) + rotation; + while (target < 1) target += m_maxPixel; + while (target > m_maxPixel) target -= m_maxPixel; + return m_mapper.map(double(target)); + } +} diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ColourScale.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourScale.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,120 @@ +/* -*- 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-2016 Chris Cannam and QMUL. + + 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 COLOUR_SCALE_H +#define COLOUR_SCALE_H + +#include "ColourMapper.h" + +enum class ColourScaleType { + Linear, + Meter, + Log, + Phase, + PlusMinusOne, + Absolute +}; + +/** + * Map values within a range onto a set of colours, with a given + * distribution (linear, log etc) and optional colourmap rotation. + */ +class ColourScale +{ +public: + struct Parameters { + Parameters() : colourMap(0), scaleType(ColourScaleType::Linear), + minValue(0.0), maxValue(1.0), + threshold(0.0), gain(1.0), multiple(1.0) { } + + /** A colour map index as used by ColourMapper */ + int colourMap; + + /** Distribution for the scale */ + ColourScaleType scaleType; + + /** Minimum value in source range */ + double minValue; + + /** Maximum value in source range. Must be > minValue */ + double maxValue; + + /** Threshold below which every value is mapped to background + pixel 0 */ + double threshold; + + /** Gain to apply before thresholding, mapping, and clamping */ + double gain; + + /** Multiple to apply after thresholding and mapping. In most + * cases the gain parameter is the one you want instead of + * this, but this can be used for example with Log scale to + * produce the log of some power of the original value, + * e.g. multiple = 2 gives log(x^2). */ + double multiple; + }; + + /** + * Create a ColourScale with the given parameters. + * + * Note that some parameters may be ignored for some scale + * distribution settings. For example, min and max are ignored for + * PlusMinusOneScale and PhaseColourScale and threshold and gain + * are ignored for PhaseColourScale. + */ + ColourScale(Parameters parameters); + ~ColourScale(); + + ColourScale(const ColourScale &) = default; + ColourScale &operator=(const ColourScale &) = default; + + /** + * Return the general type of scale this is. + */ + ColourScaleType getScale() const; + + /** + * Return a pixel number (in the range 0-255 inclusive) + * corresponding to the given value. The pixel 0 is used only for + * values below the threshold supplied in the constructor. All + * other values are mapped onto the range 1-255. + */ + int getPixel(double value) const; + + /** + * Return the colour for the given pixel number (which must be in + * the range 0-255). The pixel 0 is always the background + * colour. Other pixels are mapped taking into account the given + * colourmap rotation (which is also a value in the range 0-255). + */ + QColor getColourForPixel(int pixel, int rotation) const; + + /** + * Return the colour corresponding to the given value. This is + * equivalent to getColourForPixel(getPixel(value), rotation). + */ + QColor getColour(double value, int rotation) const { + return getColourForPixel(getPixel(value), rotation); + } + +private: + Parameters m_params; + ColourMapper m_mapper; + double m_mappedMin; + double m_mappedMax; + static int m_maxPixel; +}; + +#endif diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ColourScaleLayer.h --- a/layer/ColourScaleLayer.h Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/ColourScaleLayer.h Fri Aug 19 15:58:57 2016 +0100 @@ -21,6 +21,13 @@ class LayerGeometryProvider; +/** + * Interface for layers in which a colour scale represents (or can + * sometimes represent, depending on the display mode) the sample + * value. For example, TimeValueLayer uses colour scale when in + * segment mode and so provides this interface for use by the + * LogColourScale or LinearColourScale scale renderers. + */ class ColourScaleLayer { public: diff -r 96cf499fad62 -r c0d841cb8ab9 layer/FlexiNoteLayer.cpp --- a/layer/FlexiNoteLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/FlexiNoteLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -22,15 +22,18 @@ #include "base/Pitch.h" #include "base/LogRange.h" #include "base/RangeMapper.h" + #include "ColourDatabase.h" -#include "view/View.h" - +#include "LayerGeometryProvider.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" +#include "PaintAssistant.h" #include "data/model/FlexiNoteModel.h" +#include "view/View.h" + #include "widgets/ItemEditDialog.h" #include "widgets/TextAbbrev.h" @@ -867,34 +870,34 @@ // paint.setBrush(v->getForeground()); QString vlabel = QString("freq: %1%2").arg(p.value).arg(m_model->getScaleUnits()); - // v->drawVisibleText(paint, + // PaintAssistant::drawVisibleText(v, paint, // x - paint.fontMetrics().width(vlabel) - 2, // y + paint.fontMetrics().height()/2 // - paint.fontMetrics().descent(), - // vlabel, View::OutlinedText); - v->drawVisibleText(paint, + // vlabel, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - 2 - paint.fontMetrics().height() - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = "dur: " + QString(RealTime::frame2RealTime (p.duration, m_model->getSampleRate()).toText(true).c_str()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); QString llabel = QString("%1").arg(p.label); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y + h + 2 + paint.fontMetrics().descent(), - llabel, View::OutlinedText); + llabel, PaintAssistant::OutlinedText); QString nlabel = QString("%1").arg(noteNumber); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x + paint.fontMetrics().averageCharWidth() / 2, y + h/2 - paint.fontMetrics().descent(), - nlabel, View::OutlinedText); + nlabel, PaintAssistant::OutlinedText); } paint.drawRect(x, y - h/2, w, h); diff -r 96cf499fad62 -r c0d841cb8ab9 layer/LayerFactory.cpp --- a/layer/LayerFactory.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/LayerFactory.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -289,6 +289,7 @@ if (name == "timeruler") return TimeRuler; 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; diff -r 96cf499fad62 -r c0d841cb8ab9 layer/LayerGeometryProvider.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/LayerGeometryProvider.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,184 @@ +/* -*- 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 +#include +#include + +class ViewManager; +class View; +class Layer; + +/** + * Interface for classes that provide geometry information (such as + * size, start frame, and a large number of other properties) about + * the disposition of a layer. The main implementor of this interface + * is the View class, but other implementations may be used in + * different circumstances, e.g. as a proxy to handle hi-dpi + * coordinate mapping. + * + * Note it is expected that some implementations of this may be + * disposable, created on-the-fly for a single use. Code that receives + * a LayerGeometryProvider pointer as an argument to something should + * not, in general, store that pointer as it may be invalidated before + * the next use. Use getId() to instead obtain a persistent identifier + * for a LayerGeometryProvider, for example to establish whether the + * same one is being provided in two separate calls. + */ +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 (maybe fractional) 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 (maybe fractional) + * pixel y-coordinate, if the frequency range is as specified. + * + * Not thread-safe in logarithmic mode. Call only from GUI thread. + */ + virtual double getFrequencyForY(double 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; + + 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 diff -r 96cf499fad62 -r c0d841cb8ab9 layer/LinearColourScale.cpp --- a/layer/LinearColourScale.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/LinearColourScale.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -20,7 +20,7 @@ #include -#include "view/LayerGeometryProvider.h" +#include "LayerGeometryProvider.h" int LinearColourScale::getWidth(LayerGeometryProvider *, diff -r 96cf499fad62 -r c0d841cb8ab9 layer/LinearNumericalScale.cpp --- a/layer/LinearNumericalScale.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/LinearNumericalScale.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -20,7 +20,7 @@ #include -#include "view/View.h" +#include "LayerGeometryProvider.h" int LinearNumericalScale::getWidth(LayerGeometryProvider *, diff -r 96cf499fad62 -r c0d841cb8ab9 layer/LogColourScale.cpp --- a/layer/LogColourScale.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/LogColourScale.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -22,7 +22,7 @@ #include -#include "view/View.h" +#include "LayerGeometryProvider.h" int LogColourScale::getWidth(LayerGeometryProvider *, diff -r 96cf499fad62 -r c0d841cb8ab9 layer/LogNumericalScale.cpp --- a/layer/LogNumericalScale.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/LogNumericalScale.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -22,7 +22,7 @@ #include -#include "view/View.h" +#include "LayerGeometryProvider.h" //#define DEBUG_TIME_VALUE_LAYER 1 diff -r 96cf499fad62 -r c0d841cb8ab9 layer/NoteLayer.cpp --- a/layer/NoteLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/NoteLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -21,12 +21,13 @@ #include "base/Pitch.h" #include "base/LogRange.h" #include "base/RangeMapper.h" -#include "ColourDatabase.h" #include "view/View.h" +#include "ColourDatabase.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" +#include "PaintAssistant.h" #include "data/model/NoteModel.h" @@ -809,18 +810,18 @@ paint.setBrush(v->getForeground()); QString vlabel = QString("%1%2").arg(p.value).arg(getScaleUnits()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = RealTime::frame2RealTime (p.frame, m_model->getSampleRate()).toText(true).c_str(); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); } paint.drawRect(x, y - h/2, w, h); diff -r 96cf499fad62 -r c0d841cb8ab9 layer/PaintAssistant.cpp --- a/layer/PaintAssistant.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/PaintAssistant.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -15,7 +15,10 @@ #include "PaintAssistant.h" +#include "LayerGeometryProvider.h" + #include "base/AudioLevel.h" +#include "base/Strings.h" #include #include @@ -79,7 +82,7 @@ text = QString("%1").arg(meterdbs[i]); if (i == n) text = "0dB"; if (i == 0) { - text = "-Inf"; + text = Strings::minus_infinity; val = 0.0; } break; @@ -89,7 +92,7 @@ text = QString("%1").arg(-(10*n) + i * 10); if (i == n) text = "0dB"; if (i == 0) { - text = "-Inf"; + text = Strings::minus_infinity; val = 0.0; } break; @@ -207,3 +210,55 @@ return vy; } + +void +PaintAssistant::drawVisibleText(const LayerGeometryProvider *v, + QPainter &paint, int x, int y, + QString text, TextStyle style) +{ + if (style == OutlinedText || style == OutlinedItalicText) { + + paint.save(); + + if (style == OutlinedItalicText) { + QFont f(paint.font()); + f.setItalic(true); + paint.setFont(f); + } + + QColor penColour, surroundColour, boxColour; + + penColour = v->getForeground(); + surroundColour = v->getBackground(); + boxColour = surroundColour; + boxColour.setAlpha(127); + + paint.setPen(Qt::NoPen); + paint.setBrush(boxColour); + + QRect r = paint.fontMetrics().boundingRect(text); + r.translate(QPoint(x, y)); +// cerr << "drawVisibleText: r = " << r.x() << "," < #include class QPainter; +class Layer; +class LayerGeometryProvider; class PaintAssistant { @@ -34,6 +36,16 @@ static int getYForValue(Scale scale, double value, double minVal, double maxVal, int minY, int height); + + enum TextStyle { + BoxedText, + OutlinedText, + OutlinedItalicText + }; + + static void drawVisibleText(const LayerGeometryProvider *, + QPainter &p, int x, int y, + QString text, TextStyle style); }; #endif diff -r 96cf499fad62 -r c0d841cb8ab9 layer/PianoScale.cpp --- a/layer/PianoScale.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/PianoScale.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -21,7 +21,7 @@ #include "base/Pitch.h" -#include "view/LayerGeometryProvider.h" +#include "LayerGeometryProvider.h" void PianoScale::paintPianoVertical(LayerGeometryProvider *v, diff -r 96cf499fad62 -r c0d841cb8ab9 layer/RegionLayer.cpp --- a/layer/RegionLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/RegionLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -19,13 +19,14 @@ #include "base/RealTime.h" #include "base/Profiler.h" #include "base/LogRange.h" + #include "ColourDatabase.h" - #include "ColourMapper.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" #include "LinearColourScale.h" #include "LogColourScale.h" +#include "PaintAssistant.h" #include "view/View.h" @@ -979,18 +980,18 @@ paint.setBrush(v->getForeground()); QString vlabel = QString("%1%2").arg(p.value).arg(getScaleUnits()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = RealTime::frame2RealTime (p.frame, m_model->getSampleRate()).toText(true).c_str(); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); } paint.drawLine(x, y-1, x + w, y-1); @@ -1048,7 +1049,7 @@ nextLabelMinX = labelX + paint.fontMetrics().width(label); } - v->drawVisibleText(paint, labelX, labelY, label, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label, PaintAssistant::OutlinedText); } } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/RenderTimer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/RenderTimer.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,102 @@ +/* -*- 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 RENDER_TIMER_H +#define RENDER_TIMER_H + +#include + +class RenderTimer +{ +public: + enum Type { + /// A normal rendering operation with normal responsiveness demands + FastRender, + + /// An operation that the user might accept being slower + SlowRender, + + /// An operation that should always complete, i.e. as if there + /// were no RenderTimer in use, but without having to change + /// client code structurally + NoTimeout + }; + + /** + * Create a new RenderTimer and start timing. Make one of these + * before rendering, and then call outOfTime() regularly during + * rendering. If outOfTime() returns true, abandon rendering! and + * schedule the rest for after some user responsiveness has + * happened. + */ + RenderTimer(Type t) : + m_start(std::chrono::steady_clock::now()), + m_haveLimits(true), + m_minFraction(0.1), + m_softLimit(0.1), + m_hardLimit(0.2), + m_softLimitOverridden(false) { + + if (t == NoTimeout) { + m_haveLimits = false; + } else if (t == SlowRender) { + m_softLimit = 0.2; + m_hardLimit = 0.4; + } + } + + + /** + * Return true if we have run out of time and should suspend + * rendering and handle user events instead. Call this regularly + * during rendering work: fractionComplete should be an estimate + * of how much of the work has been done as of this call, as a + * number between 0.0 (none of it) and 1.0 (all of it). + */ + bool outOfTime(double fractionComplete) { + + if (!m_haveLimits || fractionComplete < m_minFraction) { + return false; + } + + auto t = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(t - m_start).count(); + + if (elapsed > m_hardLimit) { + return true; + } else if (!m_softLimitOverridden && elapsed > m_softLimit) { + if (fractionComplete > 0.6) { + // If we're significantly more than half way by the + // time we reach the soft limit, ignore it (though + // always respect the hard limit, above). Otherwise + // respect the soft limit and report out of time now. + m_softLimitOverridden = true; + } else { + return true; + } + } + + return false; + } + +private: + std::chrono::time_point m_start; + bool m_haveLimits; + double m_minFraction; + double m_softLimit; + double m_hardLimit; + bool m_softLimitOverridden; +}; + +#endif diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ScrollableImageCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableImageCache.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,210 @@ +/* -*- 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 +using namespace std; + +//#define DEBUG_SCROLLABLE_IMAGE_CACHE 1 + +void +ScrollableImageCache::scrollTo(const LayerGeometryProvider *v, + sv_frame_t newStartFrame) +{ + int dx = (v->getXForFrame(m_startFrame) - + v->getXForFrame(newStartFrame)); + +#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE + cerr << "ScrollableImageCache::scrollTo: start frame " << m_startFrame + << " -> " << newStartFrame << ", dx = " << dx << endl; +#endif + + if (m_startFrame == newStartFrame) { + // haven't moved + return; + } + + m_startFrame = newStartFrame; + + if (!isValid()) { + return; + } + + int w = m_image.width(); + + if (dx == 0) { + // haven't moved visibly (even though start frame may have changed) + 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_validLeft; + int pw = m_validWidth; + + 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_validLeft = px; + m_validWidth = 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_validLeft + << ", width " << m_validWidth << " so right " << (m_validLeft + m_validWidth) << endl; +#endif + if (left < m_validLeft) { + isLeftOfValidArea = true; + if (left + width <= m_validLeft + m_validWidth) { + width = m_validLeft - 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_validLeft + m_validWidth); + left = m_validLeft + m_validWidth; + 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_validLeft = left; + m_validWidth = width; + return; + } + + if (left < m_validLeft) { + if (left + width > m_validLeft + m_validWidth) { + // new image completely contains the old valid area -- + // use the new area as is + m_validLeft = left; + m_validWidth = width; + } else if (left + width < m_validLeft) { + // 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_validLeft = left; + m_validWidth = width; + } else { + // new image overlaps old valid area on left side -- + // use new left edge, and extend width to existing + // right edge + m_validWidth = (m_validLeft + m_validWidth) - left; + m_validLeft = left; + } + } else { + if (left > m_validLeft + m_validWidth) { + // 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_validLeft = left; + m_validWidth = width; + } else if (left + width > m_validLeft + m_validWidth) { + // new image overlaps old valid area on right side -- + // use existing left edge, and extend width to new + // right edge + m_validWidth = (left + width) - m_validLeft; + // (m_validLeft unchanged) + } else { + // new image completely contained within old valid + // area -- leave the old area unchanged + } + } +} + diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ScrollableImageCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableImageCache.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,163 @@ +/* -*- 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 "LayerGeometryProvider.h" + +#include +#include +#include + +/** + * A cached image for a view that scrolls horizontally, such as a + * 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() : + m_validLeft(0), + m_validWidth(0), + m_startFrame(0), + m_zoomLevel(0) + {} + + void invalidate() { + m_validWidth = 0; + } + + bool isValid() const { + return m_validWidth > 0; + } + + QSize getSize() const { + return m_image.size(); + } + + /** + * Set the size of the cache. If the new size differs from the + * current size, the cache is invalidated. + */ + void resize(QSize newSize) { + if (getSize() != newSize) { + m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied); + invalidate(); + } + } + + int getValidLeft() const { + return m_validLeft; + } + + int getValidWidth() const { + return m_validWidth; + } + + int getValidRight() const { + return m_validLeft + m_validWidth; + } + + QRect getValidArea() const { + return QRect(m_validLeft, 0, m_validWidth, m_image.height()); + } + + int getZoomLevel() const { + return m_zoomLevel; + } + + /** + * Set the zoom level. If the new zoom level differs from the + * current one, the cache is invalidated. (Determining whether to + * invalidate the cache here is the only thing the zoom level is + * used for.) + */ + void setZoomLevel(int zoom) { + if (m_zoomLevel != zoom) { + m_zoomLevel = zoom; + invalidate(); + } + } + + sv_frame_t getStartFrame() const { + return m_startFrame; + } + + /** + * Set the start frame. If the new start frame differs from the + * current one, the cache is invalidated. To scroll, i.e. to set + * the start frame while retaining cache validity where possible, + * use scrollTo() instead. + */ + void setStartFrame(sv_frame_t frame) { + if (m_startFrame != frame) { + m_startFrame = frame; + invalidate(); + } + } + + const QImage &getImage() const { + return m_image; + } + + /** + * Set the new start frame for the cache, according to the + * geometry of the supplied LayerGeometryProvider, 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(const LayerGeometryProvider *v, 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. Does not + * modify anything about the cache, only about the arguments. + */ + 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: + QImage m_image; + int m_validLeft; + int m_validWidth; + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ScrollableMagRangeCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableMagRangeCache.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,113 @@ +/* -*- 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 "ScrollableMagRangeCache.h" + +#include +using namespace std; + +//#define DEBUG_SCROLLABLE_MAG_RANGE_CACHE 1 + +void +ScrollableMagRangeCache::scrollTo(const LayerGeometryProvider *v, + sv_frame_t newStartFrame) +{ + int dx = (v->getXForFrame(m_startFrame) - + v->getXForFrame(newStartFrame)); + +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "ScrollableMagRangeCache::scrollTo: start frame " << m_startFrame + << " -> " << newStartFrame << ", dx = " << dx << endl; +#endif + + if (m_startFrame == newStartFrame) { + // haven't moved + return; + } + + m_startFrame = newStartFrame; + + if (dx == 0) { + // haven't moved visibly (even though start frame may have changed) + return; + } + + int w = int(m_ranges.size()); + + if (dx <= -w || dx >= w) { + // scrolled entirely off + invalidate(); + return; + } + + // dx is in range, cache is scrollable + + if (dx < 0) { + // The new start frame is to the left of the old start + // frame. We need to add some empty ranges at the left (start) + // end and clip the right end. Assemble -dx new values, then + // w+dx old values starting at index 0. + + auto newRanges = vector(-dx); + newRanges.insert(newRanges.end(), + m_ranges.begin(), m_ranges.begin() + (w + dx)); + m_ranges = newRanges; + + } else { + // The new start frame is to the right of the old start + // frame. We want to clip the left (start) end and add some + // empty ranges at the right end. Assemble w-dx old values + // starting at index dx, then dx new values. + + auto newRanges = vector(dx); + newRanges.insert(newRanges.begin(), + m_ranges.begin() + dx, m_ranges.end()); + m_ranges = newRanges; + } + +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "maxes (" << m_ranges.size() << ") now: "; + for (int i = 0; in_range_for(m_ranges, i); ++i) { + cerr << m_ranges[i].getMax() << " "; + } + cerr << endl; +#endif +} + +MagnitudeRange +ScrollableMagRangeCache::getRange(int x, int count) const +{ + MagnitudeRange r; +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "ScrollableMagRangeCache::getRange(" << x << ", " << count << ")" << endl; +#endif + for (int i = 0; i < count; ++i) { + r.sample(m_ranges.at(x + i)); + } + return r; +} + +void +ScrollableMagRangeCache::sampleColumn(int column, const MagnitudeRange &r) +{ + if (!in_range_for(m_ranges, column)) { + cerr << "ERROR: ScrollableMagRangeCache::sampleColumn: column " << column + << " is out of range for cache of width " << m_ranges.size() + << " (with start frame " << m_startFrame << ")" << endl; + throw logic_error("column out of range"); + } else { + m_ranges[column].sample(r); + } +} + diff -r 96cf499fad62 -r c0d841cb8ab9 layer/ScrollableMagRangeCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableMagRangeCache.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,139 @@ +/* -*- 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_MAG_RANGE_CACHE_H +#define SCROLLABLE_MAG_RANGE_CACHE_H + +#include "base/BaseTypes.h" +#include "base/MagnitudeRange.h" + +#include "LayerGeometryProvider.h" + +/** + * A cached set of magnitude range records for a view that scrolls + * horizontally, such as a spectrogram. The cache object holds a + * magnitude range per column of the view, can report width (likely + * the same as the underlying view, but it's the caller's + * responsibility to set the size appropriately), can scroll the set + * of ranges, and can report and update which columns have had a range + * specified. + * + * The only way to *update* the valid area in a cache is to update the + * magnitude range for a column using the sampleColumn call. + */ +class ScrollableMagRangeCache +{ +public: + ScrollableMagRangeCache() : + m_startFrame(0), + m_zoomLevel(0) + {} + + void invalidate() { + m_ranges = std::vector(m_ranges.size()); + } + + int getWidth() const { + return int(m_ranges.size()); + } + + /** + * Set the width of the cache in columns. If the new size differs + * from the current size, the cache is invalidated. + */ + void resize(int newWidth) { + if (getWidth() != newWidth) { + m_ranges = std::vector(newWidth); + } + } + + int getZoomLevel() const { + return m_zoomLevel; + } + + /** + * Set the zoom level. If the new zoom level differs from the + * current one, the cache is invalidated. (Determining whether to + * invalidate the cache here is the only thing the zoom level is + * used for.) + */ + void setZoomLevel(int zoom) { + if (m_zoomLevel != zoom) { + m_zoomLevel = zoom; + invalidate(); + } + } + + sv_frame_t getStartFrame() const { + return m_startFrame; + } + + /** + * Set the start frame. If the new start frame differs from the + * current one, the cache is invalidated. To scroll, i.e. to set + * the start frame while retaining cache validity where possible, + * use scrollTo() instead. + */ + void setStartFrame(sv_frame_t frame) { + if (m_startFrame != frame) { + m_startFrame = frame; + invalidate(); + } + } + + bool isColumnSet(int column) const { + return in_range_for(m_ranges, column) && m_ranges.at(column).isSet(); + } + + bool areColumnsSet(int x, int count) const { + for (int i = 0; i < count; ++i) { + if (!isColumnSet(x + i)) return false; + } + return true; + } + + /** + * Get the magnitude range for a single column. + */ + MagnitudeRange getRange(int column) const { + return m_ranges.at(column); + } + + /** + * Get the magnitude range for a range of columns. + */ + MagnitudeRange getRange(int x, int count) const; + + /** + * Set the new start frame for the cache, according to the + * geometry of the supplied LayerGeometryProvider, 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(const LayerGeometryProvider *v, sv_frame_t newStartFrame); + + /** + * Update a column in the cache, by column index. (Column zero is + * the first column in the cache, it has nothing to do with any + * underlying model that the cache may be used with.) + */ + void sampleColumn(int column, const MagnitudeRange &r); + +private: + std::vector m_ranges; + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif diff -r 96cf499fad62 -r c0d841cb8ab9 layer/SliceLayer.cpp --- a/layer/SliceLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/SliceLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -485,17 +485,17 @@ v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + 5, - startText, View::OutlinedText); + startText, PaintAssistant::OutlinedText); v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + paint.fontMetrics().height() + 10, - endText, View::OutlinedText); + endText, PaintAssistant::OutlinedText); v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + 2*paint.fontMetrics().height() + 15, - durationText, View::OutlinedText); + durationText, PaintAssistant::OutlinedText); } */ } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/SpectrogramLayer.cpp --- a/layer/SpectrogramLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/SpectrogramLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -23,21 +23,25 @@ #include "base/Preferences.h" #include "base/RangeMapper.h" #include "base/LogRange.h" +#include "base/ColumnOp.h" +#include "base/Strings.h" #include "widgets/CommandHistory.h" +#include "data/model/Dense3DModelPeakCache.h" + #include "ColourMapper.h" -#include "ImageRegionFinder.h" -#include "data/model/Dense3DModelPeakCache.h" #include "PianoScale.h" +#include "PaintAssistant.h" +#include "Colour3DPlotRenderer.h" #include #include #include #include -#include #include #include #include #include +#include #include @@ -48,9 +52,10 @@ #include #endif +//#define DEBUG_SPECTROGRAM 1 //#define DEBUG_SPECTROGRAM_REPAINT 1 -using std::vector; +using namespace std; SpectrogramLayer::SpectrogramLayer(Configuration config) : m_model(0), @@ -58,29 +63,33 @@ m_windowSize(1024), m_windowType(HanningWindow), m_windowHopLevel(2), - m_zeroPadLevel(0), - m_fftSize(1024), m_gain(1.0), m_initialGain(1.0), - m_threshold(0.0), - m_initialThreshold(0.0), + m_threshold(1.0e-8f), + m_initialThreshold(1.0e-8f), m_colourRotation(0), m_initialRotation(0), m_minFrequency(10), m_maxFrequency(8000), m_initialMaxFrequency(8000), - m_colourScale(dBColourScale), + m_colourScale(ColourScaleType::Log), + m_colourScaleMultiple(1.0), m_colourMap(0), - m_frequencyScale(LinearFrequencyScale), - m_binDisplay(AllBins), - m_normalization(NoNormalization), + m_binScale(BinScale::Linear), + m_binDisplay(BinDisplay::AllBins), + m_normalization(ColumnNormalization::None), + m_normalizeVisibleArea(false), m_lastEmittedZoomStep(-1), m_synchronous(false), m_haveDetailedScale(false), - m_lastPaintBlockWidth(0), m_exiting(false), - m_sliceableModel(0) + m_fftModel(0), + m_peakCache(0), + m_peakCacheDivisor(8) { + QString colourConfigName = "spectrogram-colour"; + int colourConfigDefault = int(ColourMapper::Green); + if (config == FullRangeDb) { m_initialMaxFrequency = 0; setMaxFrequency(0); @@ -90,9 +99,11 @@ m_initialMaxFrequency = 1500; setMaxFrequency(1500); setMinFrequency(40); - setColourScale(LinearColourScale); + setColourScale(ColourScaleType::Linear); setColourMap(ColourMapper::Sunset); - setFrequencyScale(LogFrequencyScale); + setBinScale(BinScale::Log); + colourConfigName = "spectrogram-melodic-colour"; + colourConfigDefault = int(ColourMapper::Sunset); // setGain(20); } else if (config == MelodicPeaks) { setWindowSize(4096); @@ -100,23 +111,82 @@ m_initialMaxFrequency = 2000; setMaxFrequency(2000); setMinFrequency(40); - setFrequencyScale(LogFrequencyScale); - setColourScale(LinearColourScale); - setBinDisplay(PeakFrequencies); - setNormalization(NormalizeColumns); + setBinScale(BinScale::Log); + setColourScale(ColourScaleType::Linear); + setBinDisplay(BinDisplay::PeakFrequencies); + setNormalization(ColumnNormalization::Max1); + 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))); setWindowType(prefs->getWindowType()); - - initialisePalette(); } SpectrogramLayer::~SpectrogramLayer() { - invalidateFFTModels(); + invalidateRenderers(); + invalidateFFTModel(); +} + +pair +SpectrogramLayer::convertToColourScale(int value) +{ + switch (value) { + case 0: return { ColourScaleType::Linear, 1.0 }; + case 1: return { ColourScaleType::Meter, 1.0 }; + case 2: return { ColourScaleType::Log, 2.0 }; // dB^2 (i.e. log of power) + case 3: return { ColourScaleType::Log, 1.0 }; // dB (of magnitude) + case 4: return { ColourScaleType::Phase, 1.0 }; + default: return { ColourScaleType::Linear, 1.0 }; + } +} + +int +SpectrogramLayer::convertFromColourScale(ColourScaleType scale, double multiple) +{ + switch (scale) { + case ColourScaleType::Linear: return 0; + case ColourScaleType::Meter: return 1; + case ColourScaleType::Log: return (multiple > 1.5 ? 2 : 3); + case ColourScaleType::Phase: return 4; + case ColourScaleType::PlusMinusOne: + case ColourScaleType::Absolute: + default: return 0; + } +} + +std::pair +SpectrogramLayer::convertToColumnNorm(int value) +{ + switch (value) { + default: + case 0: return { ColumnNormalization::None, false }; + case 1: return { ColumnNormalization::Max1, false }; + case 2: return { ColumnNormalization::None, true }; // visible area + case 3: return { ColumnNormalization::Hybrid, false }; + } +} + +int +SpectrogramLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible) +{ + if (visible) return 2; + switch (norm) { + case ColumnNormalization::None: return 0; + case ColumnNormalization::Max1: return 1; + case ColumnNormalization::Hybrid: return 3; + + case ColumnNormalization::Sum1: + default: return 0; + } } void @@ -127,7 +197,7 @@ if (model == m_model) return; m_model = model; - invalidateFFTModels(); + invalidateFFTModel(); if (!m_model || !m_model->isOK()) return; @@ -156,7 +226,6 @@ // list.push_back("Min Frequency"); // list.push_back("Max Frequency"); list.push_back("Frequency Scale"); -//// list.push_back("Zero Padding"); return list; } @@ -175,7 +244,6 @@ if (name == "Min Frequency") return tr("Min Frequency"); if (name == "Max Frequency") return tr("Max Frequency"); if (name == "Frequency Scale") return tr("Frequency Scale"); - if (name == "Zero Padding") return tr("Smoothing"); return ""; } @@ -191,7 +259,6 @@ if (name == "Gain") return RangeProperty; if (name == "Colour Rotation") return RangeProperty; if (name == "Threshold") return RangeProperty; - if (name == "Zero Padding") return ToggleProperty; return ValueProperty; } @@ -201,8 +268,7 @@ if (name == "Bin Display" || name == "Frequency Scale") return tr("Bins"); if (name == "Window Size" || - name == "Window Increment" || - name == "Zero Padding") return tr("Window"); + name == "Window Increment") return tr("Window"); if (name == "Colour" || name == "Threshold" || name == "Colour Rotation") return tr("Colour"); @@ -238,8 +304,8 @@ } else if (name == "Threshold") { - *min = -50; - *max = 0; + *min = -81; + *max = -1; *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold))); if (*deflt < *min) *deflt = *min; @@ -259,11 +325,12 @@ } else if (name == "Colour Scale") { + // linear, meter, db^2, db, phase *min = 0; *max = 4; - *deflt = int(dBColourScale); - - val = (int)m_colourScale; + *deflt = 2; + + val = convertFromColourScale(m_colourScale, m_colourScaleMultiple); } else if (name == "Colour") { @@ -291,14 +358,6 @@ val = m_windowHopLevel; - } else if (name == "Zero Padding") { - - *min = 0; - *max = 1; - *deflt = 0; - - val = m_zeroPadLevel > 0 ? 1 : 0; - } else if (name == "Min Frequency") { *min = 0; @@ -341,22 +400,23 @@ *min = 0; *max = 1; - *deflt = int(LinearFrequencyScale); - val = (int)m_frequencyScale; + *deflt = int(BinScale::Linear); + val = (int)m_binScale; } else if (name == "Bin Display") { *min = 0; *max = 2; - *deflt = int(AllBins); + *deflt = int(BinDisplay::AllBins); val = (int)m_binDisplay; } else if (name == "Normalization") { *min = 0; *max = 3; - *deflt = int(NoNormalization); - val = (int)m_normalization; + *deflt = 0; + + val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea); } else { val = Layer::getPropertyRangeAndValue(name, min, max, deflt); @@ -399,10 +459,6 @@ case 5: return tr("93.75 %"); } } - if (name == "Zero Padding") { - if (value == 0) return tr("None"); - return QString("%1x").arg(value + 1); - } if (name == "Min Frequency") { switch (value) { default: @@ -474,7 +530,8 @@ return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); } if (name == "Threshold") { - return new LinearRangeMapper(-50, 0, -50, 0, tr("dB")); + return new LinearRangeMapper(-81, -1, -81, -1, tr("dB"), false, + { { -81, Strings::minus_infinity } }); } return 0; } @@ -485,7 +542,7 @@ if (name == "Gain") { setGain(float(pow(10, float(value)/20.0))); } else if (name == "Threshold") { - if (value == -50) setThreshold(0.0); + if (value == -81) setThreshold(0.0); else setThreshold(float(AudioLevel::dB_to_multiplier(value))); } else if (name == "Colour Rotation") { setColourRotation(value); @@ -495,8 +552,6 @@ setWindowSize(32 << value); } else if (name == "Window Increment") { setWindowHopLevel(value); - } else if (name == "Zero Padding") { - setZeroPadLevel(value > 0.1 ? 3 : 0); } else if (name == "Min Frequency") { switch (value) { default: @@ -536,112 +591,50 @@ m_lastEmittedZoomStep = vs; } } else if (name == "Colour Scale") { + setColourScaleMultiple(1.0); switch (value) { default: - case 0: setColourScale(LinearColourScale); break; - case 1: setColourScale(MeterColourScale); break; - case 2: setColourScale(dBSquaredColourScale); break; - case 3: setColourScale(dBColourScale); break; - case 4: setColourScale(PhaseColourScale); break; + case 0: setColourScale(ColourScaleType::Linear); break; + case 1: setColourScale(ColourScaleType::Meter); break; + case 2: + setColourScale(ColourScaleType::Log); + setColourScaleMultiple(2.0); + break; + case 3: setColourScale(ColourScaleType::Log); break; + case 4: setColourScale(ColourScaleType::Phase); break; } } else if (name == "Frequency Scale") { switch (value) { default: - case 0: setFrequencyScale(LinearFrequencyScale); break; - case 1: setFrequencyScale(LogFrequencyScale); break; + case 0: setBinScale(BinScale::Linear); break; + case 1: setBinScale(BinScale::Log); break; } } else if (name == "Bin Display") { switch (value) { default: - case 0: setBinDisplay(AllBins); break; - case 1: setBinDisplay(PeakBins); break; - case 2: setBinDisplay(PeakFrequencies); break; + case 0: setBinDisplay(BinDisplay::AllBins); break; + case 1: setBinDisplay(BinDisplay::PeakBins); break; + case 2: setBinDisplay(BinDisplay::PeakFrequencies); break; } } 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; - } + auto n = convertToColumnNorm(value); + setNormalization(n.first); + setNormalizeVisibleArea(n.second); } } void -SpectrogramLayer::invalidateImageCaches() +SpectrogramLayer::invalidateRenderers() { - for (ViewImageCache::iterator i = m_imageCaches.begin(); - i != m_imageCaches.end(); ++i) { - i->second.validArea = QRect(); +#ifdef DEBUG_SPECTROGRAM + cerr << "SpectrogramLayer::invalidateRenderers called" << endl; +#endif + + for (ViewRendererMap::iterator i = m_renderers.begin(); + i != m_renderers.end(); ++i) { + delete i->second; } -} - -void -SpectrogramLayer::invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame) -{ - for (ViewImageCache::iterator i = m_imageCaches.begin(); - i != m_imageCaches.end(); ++i) { - - //!!! when are views removed from the map? on setLayerDormant? - const LayerGeometryProvider *v = i->first; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "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 (int(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 - cerr << "clipping from 0 to " << x-1 << endl; -#endif - if (x > 1) { - i->second.validArea &= - QRect(0, 0, x-1, v->getPaintHeight()); - } else { - i->second.validArea = QRect(); - } - } else { - if (int(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 - cerr << "clipping from " << x+1 << " to " << v->getPaintWidth() - << endl; -#endif - if (x < v->getPaintWidth()) { - i->second.validArea &= - QRect(x+1, 0, v->getPaintWidth()-(x+1), v->getPaintHeight()); - } 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 - } + m_renderers.clear(); } void @@ -654,12 +647,13 @@ return; } if (name == "Spectrogram Y Smoothing") { - invalidateImageCaches(); + setWindowSize(m_windowSize); + invalidateRenderers(); invalidateMagnitudes(); emit layerParametersChanged(); } if (name == "Spectrogram X Smoothing") { - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); emit layerParametersChanged(); } @@ -673,9 +667,9 @@ { if (m_channel == ch) return; - invalidateImageCaches(); + invalidateRenderers(); m_channel = ch; - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); } @@ -686,17 +680,40 @@ return m_channel; } +int +SpectrogramLayer::getFFTOversampling() const +{ + if (m_binDisplay != BinDisplay::AllBins) { + return 1; + } + + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + + if (smoothing == Preferences::NoSpectrogramSmoothing || + smoothing == Preferences::SpectrogramInterpolated) { + return 1; + } + + return 4; +} + +int +SpectrogramLayer::getFFTSize() const +{ + return m_windowSize * getFFTOversampling(); +} + void SpectrogramLayer::setWindowSize(int ws) { if (m_windowSize == ws) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowSize = ws; - m_fftSize = ws * (m_zeroPadLevel + 1); - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); } @@ -712,11 +729,11 @@ { if (m_windowHopLevel == v) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowHopLevel = v; - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); @@ -730,36 +747,15 @@ } void -SpectrogramLayer::setZeroPadLevel(int v) -{ - if (m_zeroPadLevel == v) return; - - invalidateImageCaches(); - - m_zeroPadLevel = v; - m_fftSize = m_windowSize * (v + 1); - - invalidateFFTModels(); - - emit layerParametersChanged(); -} - -int -SpectrogramLayer::getZeroPadLevel() const -{ - return m_zeroPadLevel; -} - -void SpectrogramLayer::setWindowType(WindowType w) { if (m_windowType == w) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowType = w; - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); } @@ -778,7 +774,7 @@ if (m_gain == gain) return; - invalidateImageCaches(); + invalidateRenderers(); m_gain = gain; @@ -796,7 +792,7 @@ { if (m_threshold == threshold) return; - invalidateImageCaches(); + invalidateRenderers(); m_threshold = threshold; @@ -816,7 +812,7 @@ // SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_minFrequency = mf; @@ -837,7 +833,7 @@ // SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_maxFrequency = mf; @@ -854,47 +850,67 @@ void SpectrogramLayer::setColourRotation(int r) { - invalidateImageCaches(); - if (r < 0) r = 0; if (r > 256) r = 256; int distance = r - m_colourRotation; if (distance != 0) { - rotatePalette(-distance); m_colourRotation = r; } + + // Initially the idea with colour rotation was that we would just + // rotate the palette of an already-generated cache. That's not + // really practical now that cacheing is handled in a separate + // class in which the main cache no longer has a palette. + invalidateRenderers(); emit layerParametersChanged(); } void -SpectrogramLayer::setColourScale(ColourScale colourScale) +SpectrogramLayer::setColourScale(ColourScaleType colourScale) { if (m_colourScale == colourScale) return; - invalidateImageCaches(); + invalidateRenderers(); m_colourScale = colourScale; emit layerParametersChanged(); } -SpectrogramLayer::ColourScale +ColourScaleType SpectrogramLayer::getColourScale() const { return m_colourScale; } void +SpectrogramLayer::setColourScaleMultiple(double multiple) +{ + if (m_colourScaleMultiple == multiple) return; + + invalidateRenderers(); + + m_colourScaleMultiple = multiple; + + emit layerParametersChanged(); +} + +double +SpectrogramLayer::getColourScaleMultiple() const +{ + return m_colourScaleMultiple; +} + +void SpectrogramLayer::setColourMap(int map) { if (m_colourMap == map) return; - invalidateImageCaches(); + invalidateRenderers(); m_colourMap = map; - initialisePalette(); emit layerParametersChanged(); } @@ -906,20 +922,20 @@ } void -SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) +SpectrogramLayer::setBinScale(BinScale binScale) { - if (m_frequencyScale == frequencyScale) return; - - invalidateImageCaches(); - m_frequencyScale = frequencyScale; + if (m_binScale == binScale) return; + + invalidateRenderers(); + m_binScale = binScale; emit layerParametersChanged(); } -SpectrogramLayer::FrequencyScale -SpectrogramLayer::getFrequencyScale() const +BinScale +SpectrogramLayer::getBinScale() const { - return m_frequencyScale; + return m_binScale; } void @@ -927,37 +943,55 @@ { if (m_binDisplay == binDisplay) return; - invalidateImageCaches(); + invalidateRenderers(); m_binDisplay = binDisplay; emit layerParametersChanged(); } -SpectrogramLayer::BinDisplay +BinDisplay SpectrogramLayer::getBinDisplay() const { return m_binDisplay; } void -SpectrogramLayer::setNormalization(Normalization n) +SpectrogramLayer::setNormalization(ColumnNormalization n) { if (m_normalization == n) return; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_normalization = n; emit layerParametersChanged(); } -SpectrogramLayer::Normalization +ColumnNormalization SpectrogramLayer::getNormalization() const { return m_normalization; } void +SpectrogramLayer::setNormalizeVisibleArea(bool n) +{ + if (m_normalizeVisibleArea == n) return; + + invalidateRenderers(); + invalidateMagnitudes(); + m_normalizeVisibleArea = n; + + emit layerParametersChanged(); +} + +bool +SpectrogramLayer::getNormalizeVisibleArea() const +{ + return m_normalizeVisibleArea; +} + +void SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) { if (dormant) { @@ -973,33 +1007,7 @@ Layer::setLayerDormant(v, true); - const View *view = v->getView(); - - invalidateImageCaches(); - - m_imageCaches.erase(view); - - if (m_fftModels.find(view) != m_fftModels.end()) { - - if (m_sliceableModel == m_fftModels[view]) { - bool replaced = false; - for (ViewFFTMap::iterator i = m_fftModels.begin(); - i != m_fftModels.end(); ++i) { - if (i->second != m_sliceableModel) { - emit sliceableModelReplaced(m_sliceableModel, i->second); - replaced = true; - break; - } - } - if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); - } - - delete m_fftModels[view]; - m_fftModels.erase(view); - - delete m_peakCaches[view]; - m_peakCaches.erase(view); - } + invalidateRenderers(); } else { @@ -1014,18 +1022,30 @@ cerr << "SpectrogramLayer::cacheInvalid()" << endl; #endif - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); } void -SpectrogramLayer::cacheInvalid(sv_frame_t from, sv_frame_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 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. + invalidateRenderers(); invalidateMagnitudes(); } @@ -1035,146 +1055,16 @@ return ColourMapper(m_colourMap, 1.f, 255.f).hasLightBackground(); } -void -SpectrogramLayer::initialisePalette() -{ - int formerRotation = m_colourRotation; - - if (m_colourMap == (int)ColourMapper::BlackOnWhite) { - m_palette.setColour(NO_VALUE, Qt::white); - } else { - m_palette.setColour(NO_VALUE, Qt::black); - } - - ColourMapper mapper(m_colourMap, 1.f, 255.f); - - for (int pixel = 1; pixel < 256; ++pixel) { - m_palette.setColour((unsigned char)pixel, mapper.map(pixel)); - } - - m_crosshairColour = mapper.getContrastingColour(); - - m_colourRotation = 0; - rotatePalette(m_colourRotation - formerRotation); - m_colourRotation = formerRotation; - - m_drawBuffer = QImage(); -} - -void -SpectrogramLayer::rotatePalette(int distance) -{ - QColor newPixels[256]; - - newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE); - - for (int pixel = 1; pixel < 256; ++pixel) { - int target = pixel + distance; - while (target < 1) target += 255; - while (target > 255) target -= 255; - newPixels[target] = m_palette.getColour((unsigned char)pixel); - } - - for (int pixel = 0; pixel < 256; ++pixel) { - m_palette.setColour((unsigned char)pixel, newPixels[pixel]); - } - - m_drawBuffer = QImage(); -} - -unsigned char -SpectrogramLayer::getDisplayValue(LayerGeometryProvider *v, double input) const -{ - int value; - - double min = 0.0; - double max = 1.0; - - if (m_normalization == NormalizeVisibleArea) { - min = m_viewMags[v].getMin(); - max = m_viewMags[v].getMax(); - } else if (m_normalization != NormalizeColumns) { - if (m_colourScale == LinearColourScale //|| -// m_colourScale == MeterColourScale) { - ) { - max = 0.1; - } - } - - 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.0) + 1; - break; - - case MeterColourScale: - value = AudioLevel::multiplier_to_preview - ((input - min) / (max - min), 254) + 1; - break; - - case dBSquaredColourScale: - input = ((input - min) * (input - min)) / ((max - min) * (max - min)); - if (input > 0.0) { - input = 10.0 * log10(input); - } else { - input = thresh; - } - if (min > 0.0) { - thresh = 10.0 * log10(min * min); - if (thresh < -80.0) thresh = -80.0; - } - input = (input - thresh) / (-thresh); - if (input < 0.0) input = 0.0; - if (input > 1.0) input = 1.0; - value = int(input * 255.0) + 1; - break; - - case dBColourScale: - //!!! experiment with normalizing the visible area this way. - //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.0) { - input = 10.0 * log10(input); - } else { - input = thresh; - } - if (min > 0.0) { - thresh = 10.0 * log10(min); - if (thresh < -80.0) thresh = -80.0; - } - input = (input - thresh) / (-thresh); - if (input < 0.0) input = 0.0; - if (input > 1.0) input = 1.0; - value = int(input * 255.0) + 1; - break; - - case PhaseColourScale: - value = int((input * 127.0 / M_PI) + 128); - break; - } - - if (value > UCHAR_MAX) value = UCHAR_MAX; - if (value < 0) value = 0; - return (unsigned char)value; -} - double SpectrogramLayer::getEffectiveMinFrequency() const { sv_samplerate_t sr = m_model->getSampleRate(); - double minf = double(sr) / m_fftSize; + double minf = double(sr) / getFFTSize(); if (m_minFrequency > 0.0) { - int minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.01); + int minbin = int((double(m_minFrequency) * getFFTSize()) / sr + 0.01); if (minbin < 1) minbin = 1; - minf = minbin * sr / m_fftSize; + minf = minbin * sr / getFFTSize(); } return minf; @@ -1187,9 +1077,9 @@ double maxf = double(sr) / 2; if (m_maxFrequency > 0.0) { - 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; + int maxbin = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1); + if (maxbin > getFFTSize() / 2) maxbin = getFFTSize() / 2; + maxf = maxbin * sr / getFFTSize(); } return maxf; @@ -1199,55 +1089,46 @@ SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const { Profiler profiler("SpectrogramLayer::getYBinRange"); - int h = v->getPaintHeight(); if (y < 0 || y >= h) return false; - + q0 = getBinForY(v, y); + q1 = getBinForY(v, y-1); + return true; +} + +double +SpectrogramLayer::getYForBin(const LayerGeometryProvider *v, double bin) const +{ + double minf = getEffectiveMinFrequency(); + double maxf = getEffectiveMaxFrequency(); + bool logarithmic = (m_binScale == BinScale::Log); + sv_samplerate_t sr = m_model->getSampleRate(); + + double freq = (bin * sr) / getFFTSize(); + + double y = v->getYForFrequency(freq, minf, maxf, logarithmic); + + return y; +} + +double +SpectrogramLayer::getBinForY(const LayerGeometryProvider *v, double y) const +{ sv_samplerate_t sr = m_model->getSampleRate(); double minf = getEffectiveMinFrequency(); double maxf = getEffectiveMaxFrequency(); - bool logarithmic = (m_frequencyScale == LogFrequencyScale); - - q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); - q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); - - // Now map these on to ("proportions of") actual bins, using raw - // FFT size (unsmoothed) - - q0 = (q0 * m_fftSize) / sr; - q1 = (q1 * m_fftSize) / sr; - - return true; + bool logarithmic = (m_binScale == BinScale::Log); + + double freq = v->getFrequencyForY(y, minf, maxf, logarithmic); + + // Now map on to ("proportion of") actual bins + double bin = (freq * getFFTSize()) / sr; + + return bin; } bool -SpectrogramLayer::getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const -{ - Profiler profiler("SpectrogramLayer::getSmoothedYBinRange"); - - int h = v->getPaintHeight(); - if (y < 0 || y >= h) return false; - - sv_samplerate_t sr = m_model->getSampleRate(); - double minf = getEffectiveMinFrequency(); - double maxf = getEffectiveMaxFrequency(); - - bool logarithmic = (m_frequencyScale == LogFrequencyScale); - - q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); - q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); - - // Now map these on to ("proportions of") actual bins, using raw - // FFT size (unsmoothed) - - q0 = (q0 * getFFTSize(v)) / sr; - q1 = (q1 * getFFTSize(v)) / sr; - - return true; -} - -bool SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const { sv_frame_t modelStart = m_model->getStartFrame(); @@ -1303,8 +1184,8 @@ sv_samplerate_t sr = m_model->getSampleRate(); for (int q = q0i; q <= q1i; ++q) { - if (q == q0i) freqMin = (sr * q) / m_fftSize; - if (q == q1i) freqMax = (sr * (q+1)) / m_fftSize; + if (q == q0i) freqMin = (sr * q) / getFFTSize(); + if (q == q1i) freqMax = (sr * (q+1)) / getFFTSize(); } return true; } @@ -1319,7 +1200,7 @@ return false; } - FFTModel *fft = getFFTModel(v); + FFTModel *fft = getFFTModel(); if (!fft) return false; double s0 = 0, s1 = 0; @@ -1338,22 +1219,23 @@ bool haveAdj = false; - bool peaksOnly = (m_binDisplay == PeakBins || - m_binDisplay == PeakFrequencies); + bool peaksOnly = (m_binDisplay == BinDisplay::PeakBins || + m_binDisplay == BinDisplay::PeakFrequencies); for (int q = q0i; q <= q1i; ++q) { for (int s = s0i; s <= s1i; ++s) { - if (!fft->isColumnAvailable(s)) continue; - 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, float(m_threshold * double(m_fftSize)/2.0))) continue; + if (!fft->isOverThreshold + (s, q, float(m_threshold * double(getFFTSize())/2.0))) { + continue; + } double freq = binfreq; @@ -1399,11 +1281,7 @@ bool rv = false; - int zp = getZeroPadLevel(v); - q0i *= zp + 1; - q1i *= zp + 1; - - FFTModel *fft = getFFTModel(v); + FFTModel *fft = getFFTModel(); if (fft) { @@ -1420,15 +1298,13 @@ for (int s = s0i; s <= s1i; ++s) { if (s >= 0 && q >= 0 && s < cw && q < ch) { - if (!fft->isColumnAvailable(s)) continue; - 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.0); + value = fft->getMagnitudeAt(s, q) / (getFFTSize()/2.0); if (!have || value < min) { min = value; } if (!have || value > max) { max = value; } @@ -1444,214 +1320,92 @@ return rv; } - -int -SpectrogramLayer::getZeroPadLevel(const LayerGeometryProvider *v) const -{ - //!!! tidy all this stuff - - if (m_binDisplay != AllBins) return 0; - - Preferences::SpectrogramSmoothing smoothing = - Preferences::getInstance()->getSpectrogramSmoothing(); - - if (smoothing == Preferences::NoSpectrogramSmoothing || - smoothing == Preferences::SpectrogramInterpolated) return 0; - - if (m_frequencyScale == LogFrequencyScale) return 3; - - sv_samplerate_t sr = m_model->getSampleRate(); - - 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; - } - - 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; - } - - double perPixel = - double(v->getPaintHeight()) / - double((maxbin - minbin) / (m_zeroPadLevel + 1)); - - if (perPixel > 2.8) { - return 3; // 4x oversampling - } else if (perPixel > 1.5) { - return 1; // 2x - } else { - return 0; // 1x - } -} - -int -SpectrogramLayer::getFFTSize(const LayerGeometryProvider *v) const -{ - return m_fftSize * (getZeroPadLevel(v) + 1); -} FFTModel * -SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const +SpectrogramLayer::getFFTModel() const { if (!m_model) return 0; - int fftSize = getFFTSize(v); - - const View *view = v->getView(); + int fftSize = getFFTSize(); + + //!!! it is now surely slower to do this on every getFFTModel() + //!!! request than it would be to recreate the model immediately + //!!! when something changes instead of just invalidating it - if (m_fftModels.find(view) != m_fftModels.end()) { - if (m_fftModels[view] == 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl; -#endif - return 0; - } - if (m_fftModels[view]->getHeight() != fftSize / 2 + 1) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; -#endif - delete m_fftModels[view]; - m_fftModels.erase(view); - delete m_peakCaches[view]; - m_peakCaches.erase(view); - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view]->getHeight() << endl; -#endif - return m_fftModels[view]; - } + if (m_fftModel && + m_fftModel->getHeight() == fftSize / 2 + 1 && + m_fftModel->getWindowIncrement() == getWindowIncrement()) { + return m_fftModel; } - - if (m_fftModels.find(view) == m_fftModels.end()) { - - FFTModel *model = new FFTModel(m_model, - m_channel, - m_windowType, - m_windowSize, - getWindowIncrement(), - fftSize); - - if (!model->isOK()) { - QMessageBox::critical - (0, tr("FFT cache failed"), - 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[view] = 0; - return 0; - } - - if (!m_sliceableModel) { -#ifdef DEBUG_SPECTROGRAM - cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << endl; -#endif - ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model); - m_sliceableModel = model; - } - - m_fftModels[view] = model; + + delete m_peakCache; + m_peakCache = 0; + + delete m_fftModel; + m_fftModel = new FFTModel(m_model, + m_channel, + m_windowType, + m_windowSize, + getWindowIncrement(), + fftSize); + + if (!m_fftModel->isOK()) { + QMessageBox::critical + (0, tr("FFT cache failed"), + tr("Failed to create the FFT model for this spectrogram.\n" + "There may be insufficient memory or disc space to continue.")); + delete m_fftModel; + m_fftModel = 0; + return 0; } - return m_fftModels[view]; + ((SpectrogramLayer *)this)->sliceableModelReplaced(0, m_fftModel); + + return m_fftModel; } Dense3DModelPeakCache * -SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const +SpectrogramLayer::getPeakCache() const { - const View *view = v->getView(); - if (!m_peakCaches[view]) { - FFTModel *f = getFFTModel(v); + //!!! see comment in getFFTModel + + if (!m_peakCache) { + FFTModel *f = getFFTModel(); if (!f) return 0; - m_peakCaches[view] = new Dense3DModelPeakCache(f, 8); + m_peakCache = new Dense3DModelPeakCache(f, m_peakCacheDivisor); } - return m_peakCaches[view]; + return m_peakCache; } const Model * SpectrogramLayer::getSliceableModel() const { - if (m_sliceableModel) return m_sliceableModel; - if (m_fftModels.empty()) return 0; - m_sliceableModel = m_fftModels.begin()->second; - return m_sliceableModel; + return m_fftModel; } void -SpectrogramLayer::invalidateFFTModels() +SpectrogramLayer::invalidateFFTModel() { - for (ViewFFTMap::iterator i = m_fftModels.begin(); - i != m_fftModels.end(); ++i) { - delete i->second; - } - for (PeakCacheMap::iterator i = m_peakCaches.begin(); - i != m_peakCaches.end(); ++i) { - delete i->second; - } - - m_fftModels.clear(); - m_peakCaches.clear(); - - if (m_sliceableModel) { - cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << endl; - emit sliceableModelReplaced(m_sliceableModel, 0); - m_sliceableModel = 0; - } +#ifdef DEBUG_SPECTROGRAM + cerr << "SpectrogramLayer::invalidateFFTModel called" << endl; +#endif + + emit sliceableModelReplaced(m_fftModel, 0); + + delete m_fftModel; + delete m_peakCache; + + m_fftModel = 0; + m_peakCache = 0; } void SpectrogramLayer::invalidateMagnitudes() { +#ifdef DEBUG_SPECTROGRAM + cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl; +#endif m_viewMags.clear(); - for (std::vector::iterator i = m_columnMags.begin(); - i != m_columnMags.end(); ++i) { - *i = MagnitudeRange(); - } -} - -bool -SpectrogramLayer::updateViewMagnitudes(LayerGeometryProvider *v) const -{ - MagnitudeRange mag; - - int x0 = 0, x1 = v->getPaintWidth(); - double s00 = 0, s01 = 0, s10 = 0, s11 = 0; - - if (!getXBinRange(v, x0, s00, s01)) { - s00 = s01 = double(m_model->getStartFrame()) / getWindowIncrement(); - } - - if (!getXBinRange(v, x1, s10, s11)) { - 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); - -// SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl; - - if (int(m_columnMags.size()) <= s1) { - m_columnMags.resize(s1 + 1); - } - - for (int s = s0; s <= s1; ++s) { - if (m_columnMags[s].isSet()) { - mag.sample(m_columnMags[s]); - } - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols " - << s0 << " -> " << s1 << " inclusive" << endl; -#endif - - if (!mag.isSet()) return false; - if (mag == m_viewMags[v]) return false; - m_viewMags[v] = mag; - return true; } void @@ -1660,22 +1414,149 @@ m_synchronous = synchronous; } +Colour3DPlotRenderer * +SpectrogramLayer::getRenderer(LayerGeometryProvider *v) const +{ + int viewId = v->getId(); + + if (m_renderers.find(viewId) == m_renderers.end()) { + + Colour3DPlotRenderer::Sources sources; + sources.verticalBinLayer = this; + sources.fft = getFFTModel(); + sources.source = sources.fft; + sources.peaks = getPeakCache(); + + ColourScale::Parameters cparams; + cparams.colourMap = m_colourMap; + cparams.scaleType = m_colourScale; + cparams.multiple = m_colourScaleMultiple; + + if (m_colourScale != ColourScaleType::Phase) { + cparams.gain = m_gain; + cparams.threshold = m_threshold; + } + + float minValue = 0.0f; + float maxValue = 1.0f; + + if (m_normalizeVisibleArea && m_viewMags[viewId].isSet()) { + minValue = m_viewMags[viewId].getMin(); + maxValue = m_viewMags[viewId].getMax(); + } else if (m_colourScale == ColourScaleType::Linear && + m_normalization == ColumnNormalization::None) { + maxValue = 0.1f; + } + + if (maxValue <= minValue) { + maxValue = minValue + 0.1f; + } + if (maxValue <= m_threshold) { + maxValue = m_threshold + 0.1f; + } + + cparams.minValue = minValue; + cparams.maxValue = maxValue; + + m_lastRenderedMags[viewId] = MagnitudeRange(minValue, maxValue); + + Colour3DPlotRenderer::Parameters params; + params.colourScale = ColourScale(cparams); + params.normalization = m_normalization; + params.binDisplay = m_binDisplay; + params.binScale = m_binScale; + params.alwaysOpaque = true; + params.invertVertical = false; + params.scaleFactor = 1.0; + params.colourRotation = m_colourRotation; + + if (m_colourScale != ColourScaleType::Phase && + m_normalization != ColumnNormalization::Hybrid) { + params.scaleFactor *= 2.f / float(getFFTSize()); + } + + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + params.interpolate = + (smoothing == Preferences::SpectrogramInterpolated || + smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated); + + m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params); + } + + return m_renderers[v->getId()]; +} + +void +SpectrogramLayer::paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const +{ + Colour3DPlotRenderer *renderer = getRenderer(v); + + Colour3DPlotRenderer::RenderResult result; + MagnitudeRange magRange; + int viewId = v->getId(); + + bool continuingPaint = !renderer->geometryChanged(v); + + if (continuingPaint) { + magRange = m_viewMags[viewId]; + } + + if (m_synchronous) { + + result = renderer->render(v, paint, rect); + + } else { + + result = renderer->renderTimeConstrained(v, paint, rect); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "rect width from this paint: " << result.rendered.width() + << ", mag range in this paint: " << result.range.getMin() << " -> " + << result.range.getMax() << endl; +#endif + + QRect uncached = renderer->getLargestUncachedRect(v); + if (uncached.width() > 0) { + v->updatePaintRect(uncached); + } + } + + magRange.sample(result.range); + + if (magRange.isSet()) { + if (m_viewMags[viewId] != magRange) { + m_viewMags[viewId] = magRange; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "mag range in this view has changed: " + << magRange.getMin() << " -> " << magRange.getMax() << endl; +#endif + } + } + + if (!continuingPaint && m_normalizeVisibleArea && + m_viewMags[viewId] != m_lastRenderedMags[viewId]) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "mag range has changed from last rendered range: re-rendering" + << endl; +#endif + delete m_renderers[viewId]; + m_renderers.erase(viewId); + v->updatePaintRect(v->getPaintRect()); + } +} + void 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 - cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << 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 - sv_frame_t startFrame = v->getStartFrame(); - if (!m_model || !m_model->isOK() || !m_model->isReady()) { return; } @@ -1684,1025 +1565,9 @@ SVDEBUG << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << endl; } - // Need to do this even if !isLayerDormant, as that could mean v - // 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 inter use cache-fill thread - const_cast(this)->Layer::setLayerDormant(v, false); - - int fftSize = getFFTSize(v); -/* - FFTModel *fft = getFFTModel(v); - if (!fft) { - cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << endl; - return; - } -*/ - - const View *view = v->getView(); - - ImageCache &cache = m_imageCaches[view]; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint(): image cache valid area " << cache. - -validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl; -#endif - - int zoomLevel = v->getZoomLevel(); - - int x0 = 0; - int x1 = v->getPaintWidth(); - - bool recreateWholeImageCache = true; - - x0 = rect.left(); - x1 = rect.right() + 1; -/* - double xPixelRatio = double(fft->getResolution()) / double(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->getPaintWidth() && - ch == v->getPaintHeight()) { - - 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; - size_t 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->getPaintWidth()) { - cerr << "(cache width " << cw - << " != " << v->getPaintWidth(); - } - if (ch != v->getPaintHeight()) { - cerr << "(cache height " << ch - << " != " << v->getPaintHeight(); - } -#endif - cache.validArea = QRect(); -// recreateWholeImageCache = true; - } - } - - if (updateViewMagnitudes(v)) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; -#endif - if (m_normalization == NormalizeVisibleArea) { - cache.validArea = QRect(); - recreateWholeImageCache = true; - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; -#endif - } - - if (recreateWholeImageCache) { - x0 = 0; - x1 = v->getPaintWidth(); - } - - 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 > 100) { - paintBlockWidth /= 2; - lastTime = lastTime / 2; - } - while (lastTime < RealTime::fromMilliseconds(90) && - paintBlockWidth < 1500) { - paintBlockWidth *= 2; - lastTime = lastTime * 2; - } - } - - if (paintBlockWidth < 50) paintBlockWidth = 50; - } - -#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->getPaintHeight(); - - 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(); - -#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; -#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 - - 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 - // to the actual scale frequency extents (presumably not zero padded). - - // If we are zero padding, we want to use the zero-padded - // equivalents of the bins that we would be using if not zero - // padded, to avoid spaces at the top and bottom of the display. - - // Note fftSize is the actual zero-padded fft size, m_fftSize the - // nominal fft size. - - 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; - } - - 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; - if (minbin < 1) minbin = 1; - if (minbin >= maxbin) minbin = maxbin - 1; - } - - int zpl = getZeroPadLevel(v) + 1; - minbin = minbin * zpl; - maxbin = (maxbin + 1) * zpl - 1; - - double minFreq = (double(minbin) * sr) / fftSize; - double maxFreq = (double(maxbin) * sr) / fftSize; - - double displayMinFreq = minFreq; - double displayMaxFreq = maxFreq; - - if (fftSize != m_fftSize) { - displayMinFreq = getEffectiveMinFrequency(); - displayMaxFreq = getEffectiveMaxFrequency(); - } - -// cerr << "(giving actual minFreq " << minFreq << " and display minFreq " << displayMinFreq << ")" << endl; - - int increment = getWindowIncrement(); - - bool logarithmic = (m_frequencyScale == LogFrequencyScale); -/* - double yforbin[maxbin - minbin + 1]; - - for (int q = minbin; q <= maxbin; ++q) { - double f0 = (double(q) * sr) / fftSize; - yforbin[q - minbin] = - v->getYForFrequency(f0, displayMinFreq, displayMaxFreq, - logarithmic); - } -*/ - MagnitudeRange overallMag = m_viewMags[v]; - bool overallMagChanged = false; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl; -#endif - - if (w == 0) { - SVDEBUG << "*** NOTE: w == 0" << endl; - } - - Profiler outerprof("SpectrogramLayer::paint: all cols"); - - // The draw buffer contains a fragment at either our pixel - // resolution (if there is more than one time-bin per pixel) or - // time-bin resolution (if a time-bin spans more than one pixel). - // We need to ensure that it starts and ends at points where a - // time-bin boundary occurs at an exact pixel boundary, and with a - // certain amount of overlap across existing pixels so that we can - // scale and draw from it without smoothing errors at the edges. - - // If (getFrameForX(x) / increment) * increment == - // getFrameForX(x), then x is a time-bin boundary. We want two - // 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; - - sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; - sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; - - int bufwid; - - if (bufferBinResolution) { - - for (int x = x0; ; --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; } - } - } - for (int x = x0 + w; ; ++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; } - } - } -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Left: crop: " << leftCropFrame << " (bin " << leftCropFrame/increment << "); boundary: " << leftBoundaryFrame << " (bin " << leftBoundaryFrame/increment << ")" << endl; - cerr << "Right: crop: " << rightCropFrame << " (bin " << rightCropFrame/increment << "); boundary: " << rightBoundaryFrame << " (bin " << rightBoundaryFrame/increment << ")" << endl; -#endif - - bufwid = int((rightBoundaryFrame - leftBoundaryFrame) / increment); - - } else { - - bufwid = w; - } - - vector binforx(bufwid); - vector binfory(h); - - bool usePeaksCache = false; - - if (bufferBinResolution) { - for (int x = 0; x < bufwid; ++x) { - binforx[x] = int(leftBoundaryFrame / increment) + x; -// cerr << "binforx[" << x << "] = " << binforx[x] << endl; - } - m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); - } else { - for (int x = 0; x < bufwid; ++x) { - 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) { - 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((unsigned char)pixel, - m_palette.getColour((unsigned char)pixel).rgb()); - } - - m_drawBuffer.fill(0); - - if (m_binDisplay != PeakFrequencies) { - - for (int y = 0; y < h; ++y) { - 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); - - } else { - - paintDrawBufferPeakFrequencies(v, bufwid, h, binforx, - minbin, maxbin, - displayMinFreq, displayMaxFreq, - logarithmic, - overallMag, overallMagChanged); - } - -/* - 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) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Run out of data -- dropping out of loop" << endl; -#endif - break; - } - } -*/ -#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; -#endif - } - - outerprof.end(); - - Profiler profiler2("SpectrogramLayer::paint: draw image"); - - if (recreateWholeImageCache) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Recreating image cache: width = " << v->getPaintWidth() - << ", height = " << h << endl; -#endif - cache.image = QImage(v->getPaintWidth(), h, QImage::Format_ARGB32_Premultiplied); - } - - if (w > 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Painting " << w << "x" << h - << " from draw buffer at " << 0 << "," << 0 - << " to " << w << "x" << h << " on cache at " - << x0 << "," << 0 << endl; -#endif - - QPainter cachePainter(&cache.image); - - if (bufferBinResolution) { - int scaledLeft = v->getXForFrame(leftBoundaryFrame); - int scaledRight = v->getXForFrame(rightBoundaryFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "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 - cerr << "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)); - } else { - cachePainter.drawImage(QRect(x0, 0, w, h), - m_drawBuffer, - QRect(0, 0, w, h)); - } - - cachePainter.end(); - } - - QRect pr = rect & cache.validArea; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Painting " << pr.width() << "x" << pr.height() - << " from cache at " << pr.x() << "," << pr.y() - << " to window" << endl; -#endif - - paint.drawImage(pr.x(), pr.y(), cache.image, - 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_normalization != NormalizeVisibleArea) || !overallMagChanged) { - - if (cache.validArea.x() > 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint() updating left (0, " - << cache.validArea.x() << ")" << endl; -#endif - v->getView()->update(0, 0, cache.validArea.x(), h); - } - - if (cache.validArea.x() + cache.validArea.width() < - cache.image.width()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint() updating right (" - << cache.validArea.x() + cache.validArea.width() - << ", " - << cache.image.width() - (cache.validArea.x() + - cache.validArea.width()) - << ")" << endl; -#endif - v->getView()->update(cache.validArea.x() + cache.validArea.width(), - 0, - cache.image.width() - (cache.validArea.x() + - cache.validArea.width()), - h); - } - } else { - // overallMagChanged - cerr << "\noverallMagChanged - updating all\n" << endl; - cache.validArea = QRect(); - v->getView()->update(); - } - } + paintWithRenderer(v, paint, rect); illuminateLocalFeatures(v, paint); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint() returning" << endl; -#endif - - if (!m_synchronous) { - m_lastPaintBlockWidth = paintBlockWidth; - (void)gettimeofday(&tv, 0); - m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart; - } -} - -bool -SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, - int w, - int h, - const vector &binforx, - int minbin, - int maxbin, - double displayMinFreq, - double displayMaxFreq, - bool logarithmic, - MagnitudeRange &overallMag, - bool &overallMagChanged) const -{ - Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies"); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "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; - - FFTModel::PeakSet peakfreqs; - - int psx = -1; - -#ifdef __GNUC__ - float values[maxbin - minbin + 1]; -#else - float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); -#endif - - for (int x = 0; x < w; ++x) { - - if (binforx[x] < 0) continue; - - int sx0 = binforx[x]; - int sx1 = sx0; - if (x+1 < w) sx1 = binforx[x+1]; - if (sx0 < 0) sx0 = sx1 - 1; - if (sx0 < 0) continue; - if (sx1 <= sx0) sx1 = sx0 + 1; - - for (int sx = sx0; sx < sx1; ++sx) { - - 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) { - peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, - minbin, maxbin - 1); - if (m_colourScale == PhaseColourScale) { - fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1); - } 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); - } - psx = sx; - } - - for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); - pi != peakfreqs.end(); ++pi) { - - int bin = pi->first; - double freq = pi->second; - - if (bin < minbin) continue; - if (bin > maxbin) break; - - double value = values[bin - minbin]; - - if (m_colourScale != PhaseColourScale) { - if (m_normalization != NormalizeColumns) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - double y = v->getYForFrequency - (freq, displayMinFreq, displayMaxFreq, logarithmic); - - int iy = int(y + 0.5); - if (iy < 0 || iy >= h) continue; - - m_drawBuffer.setPixel(x, iy, getDisplayValue(v, value)); - } - - if (mag.isSet()) { - if (sx >= int(m_columnMags.size())) { -#ifdef DEBUG_SPECTROGRAM - cerr << "INTERNAL ERROR: " << sx << " >= " - << m_columnMags.size() - << " at SpectrogramLayer.cpp::paintDrawBuffer" - << endl; -#endif - } else { - m_columnMags[sx].sample(mag); - if (overallMag.sample(mag)) overallMagChanged = true; - } - } - } - } - - return true; -} - -bool -SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v, - int w, - int h, - const vector &binforx, - const vector &binfory, - bool usePeaksCache, - MagnitudeRange &overallMag, - bool &overallMagChanged) const -{ - Profiler profiler("SpectrogramLayer::paintDrawBuffer"); - - int minbin = int(binfory[0] + 0.0001); - int maxbin = int(binfory[h-1]); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; -#endif - if (minbin < 0) minbin = 0; - if (maxbin < 0) maxbin = minbin+1; - - DenseThreeDimensionalModel *sourceModel = 0; - 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; -#endif - if (usePeaksCache) { //!!! - sourceModel = getPeakCache(v); - divisor = 8;//!!! - minbin = 0; - maxbin = sourceModel->getHeight(); - } else { - sourceModel = fft = getFFTModel(v); - } - - if (!sourceModel) return false; - - bool interpolate = false; - Preferences::SpectrogramSmoothing smoothing = - Preferences::getInstance()->getSpectrogramSmoothing(); - if (smoothing == Preferences::SpectrogramInterpolated || - smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) { - if (m_binDisplay != PeakBins && - m_binDisplay != PeakFrequencies) { - interpolate = true; - } - } - - int psx = -1; - -#ifdef __GNUC__ - float autoarray[maxbin - minbin + 1]; - float peaks[h]; -#else - float *autoarray = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); - float *peaks = (float *)alloca(h * sizeof(float)); -#endif - - const float *values = autoarray; - DenseThreeDimensionalModel::Column c; - - for (int x = 0; x < w; ++x) { - - if (binforx[x] < 0) continue; - -// float columnGain = m_gain; - float columnMax = 0.f; - - int sx0 = binforx[x] / divisor; - int sx1 = sx0; - if (x+1 < w) sx1 = binforx[x+1] / divisor; - if (sx0 < 0) sx0 = sx1 - 1; - if (sx0 < 0) continue; - if (sx1 <= sx0) sx1 = sx0 + 1; - - for (int y = 0; y < h; ++y) peaks[y] = 0.f; - - for (int sx = sx0; sx < sx1; ++sx) { - -#ifdef DEBUG_SPECTROGRAM_REPAINT -// cerr << "sx = " << sx << endl; -#endif - - 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 - cerr << "Retrieving column " << sx << " from fft directly" << endl; -#endif - if (m_colourScale == PhaseColourScale) { - fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1); - } 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 - cerr << "Retrieving column " << sx << " from peaks cache" << endl; -#endif - c = sourceModel->getColumn(sx); - 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; - } - psx = sx; - } - - for (int y = 0; y < h; ++y) { - - double sy0 = binfory[y]; - double sy1 = sy0 + 1; - if (y+1 < h) sy1 = binfory[y+1]; - - 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; - 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.0; - if (other == minbin || other == maxbin || - v1 < values[other-minbin-1] || - v1 < values[other-minbin+1]) v1 = 0.0; - } - if (v0 == 0.0 && v1 == 0.0) continue; - value = prop * v0 + (1.0 - prop) * v1; - - if (m_colourScale != PhaseColourScale) { - if (m_normalization != NormalizeColumns && - m_normalization != NormalizeHybrid) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - peaks[y] = float(value); - - } else { - - int by0 = int(sy0 + 0.0001); - int by1 = int(sy1 + 0.0001); - if (by1 < by0 + 1) by1 = by0 + 1; - - for (int bin = by0; bin < by1; ++bin) { - - value = values[bin - minbin]; - if (m_binDisplay == PeakBins) { - if (bin == minbin || bin == maxbin || - value < values[bin-minbin-1] || - value < values[bin-minbin+1]) continue; - } - - if (m_colourScale != PhaseColourScale) { - if (m_normalization != NormalizeColumns && - m_normalization != NormalizeHybrid) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - if (value > peaks[y]) { - peaks[y] = float(value); //!!! not right for phase! - } - } - } - } - - if (mag.isSet()) { - if (sx >= int(m_columnMags.size())) { -#ifdef DEBUG_SPECTROGRAM - cerr << "INTERNAL ERROR: " << sx << " >= " - << m_columnMags.size() - << " at SpectrogramLayer.cpp::paintDrawBuffer" - << endl; -#endif - } else { - m_columnMags[sx].sample(mag); - if (overallMag.sample(mag)) overallMagChanged = true; - } - } - } - - for (int y = 0; y < h; ++y) { - - double peak = peaks[y]; - - if (m_colourScale != PhaseColourScale && - (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); - } - } - - return true; } void @@ -2715,8 +1580,10 @@ return; } -// cerr << "SpectrogramLayer: illuminateLocalFeatures(" -// << localPos.x() << "," << localPos.y() << ")" << endl; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: illuminateLocalFeatures(" + << localPos.x() << "," << localPos.y() << ")" << endl; +#endif double s0, s1; double f0, f1; @@ -2733,8 +1600,10 @@ int y1 = int(getYForFrequency(v, f1)); int y0 = int(getYForFrequency(v, f0)); -// cerr << "SpectrogramLayer: illuminate " -// << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: illuminate " + << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; +#endif paint.setPen(v->getForeground()); @@ -2750,7 +1619,7 @@ return v->getYForFrequency(frequency, getEffectiveMinFrequency(), getEffectiveMaxFrequency(), - m_frequencyScale == LogFrequencyScale); + m_binScale == BinScale::Log); } double @@ -2759,17 +1628,14 @@ return v->getFrequencyForY(y, getEffectiveMinFrequency(), getEffectiveMaxFrequency(), - m_frequencyScale == LogFrequencyScale); + m_binScale == BinScale::Log); } int -SpectrogramLayer::getCompletion(LayerGeometryProvider *v) const +SpectrogramLayer::getCompletion(LayerGeometryProvider *) const { - const View *view = v->getView(); - - if (m_fftModels.find(view) == m_fftModels.end()) return 100; - - int completion = m_fftModels[view]->getCompletion(); + if (!m_fftModel) return 100; + int completion = m_fftModel->getCompletion(); #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl; #endif @@ -2777,11 +1643,10 @@ } QString -SpectrogramLayer::getError(LayerGeometryProvider *v) const +SpectrogramLayer::getError(LayerGeometryProvider *) const { - const View *view = v->getView(); - if (m_fftModels.find(view) == m_fftModels.end()) return ""; - return m_fftModels[view]->getError(); + if (!m_fftModel) return ""; + return m_fftModel->getError(); } bool @@ -2791,10 +1656,10 @@ if (!m_model) return false; sv_samplerate_t sr = m_model->getSampleRate(); - min = double(sr) / m_fftSize; + min = double(sr) / getFFTSize(); max = double(sr) / 2; - logarithmic = (m_frequencyScale == LogFrequencyScale); + logarithmic = (m_binScale == BinScale::Log); unit = "Hz"; return true; } @@ -2824,7 +1689,7 @@ if (m_minFrequency == minf && m_maxFrequency == maxf) return true; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_minFrequency = minf; @@ -2876,16 +1741,10 @@ void SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e) { - const View *view = v->getView(); - ImageCache &cache = m_imageCaches[view]; - - cerr << "cache width: " << cache.image.width() << ", height: " - << cache.image.height() << endl; - - QImage image = cache.image; - - ImageRegionFinder finder; - QRect rect = finder.findRegionExtents(&image, e->pos()); + const Colour3DPlotRenderer *renderer = getRenderer(v); + if (!renderer) return; + + QRect rect = renderer->findSimilarRegionExtents(e->pos()); if (rect.isValid()) { MeasureRect mr; setMeasureRectFromPixrect(v, mr, rect); @@ -2897,7 +1756,7 @@ bool SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos, - std::vector &extents) const + vector &extents) const { QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight()); extents.push_back(vertical); @@ -2953,35 +1812,35 @@ double fundamental = getFrequencyForY(v, cursorPos.y()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, sw + 2, cursorPos.y() - 2, QString("%1 Hz").arg(fundamental), - View::OutlinedText); + PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(fundamental)) { QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, sw + 2, cursorPos.y() + paint.fontMetrics().ascent() + 2, pitchLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); } 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, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2, v->getPaintHeight() - 2, frameLabel, - View::OutlinedText); - v->drawVisibleText(paint, + PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() + 2, v->getPaintHeight() - 2, rtLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); int harmonic = 2; @@ -3037,7 +1896,7 @@ QString adjFreqText = "", adjPitchText = ""; - if (m_binDisplay == PeakFrequencies) { + if (m_binDisplay == BinDisplay::PeakFrequencies) { if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax, adjFreqMin, adjFreqMax)) { @@ -3099,12 +1958,12 @@ QString dbMinString; QString dbMaxString; if (dbMin == AudioLevel::DB_FLOOR) { - dbMinString = tr("-Inf"); + dbMinString = Strings::minus_infinity; } else { dbMinString = QString("%1").arg(lrint(dbMin)); } if (dbMax == AudioLevel::DB_FLOOR) { - dbMaxString = tr("-Inf"); + dbMaxString = Strings::minus_infinity; } else { dbMaxString = QString("%1").arg(lrint(dbMax)); } @@ -3149,13 +2008,14 @@ int fw = paint.fontMetrics().width(tr("43Hz")); if (tw < fw) tw = fw; - int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); + int tickw = (m_binScale == BinScale::Log ? 10 : 4); return cw + tickw + tw + 13; } void -SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *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; @@ -3164,107 +2024,32 @@ Profiler profiler("SpectrogramLayer::paintVerticalScale"); //!!! cache this? - + int h = rect.height(), w = rect.width(); - - int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); - int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); - - int bins = m_fftSize / 2; + int textHeight = paint.fontMetrics().height(); + + if (detailed && (h > textHeight * 3 + 10)) { + paintDetailedScale(v, paint, rect); + } + m_haveDetailedScale = detailed; + + int tickw = (m_binScale == BinScale::Log ? 10 : 4); + int pkw = (m_binScale == BinScale::Log ? 10 : 0); + + int bins = getFFTSize() / 2; sv_samplerate_t sr = m_model->getSampleRate(); if (m_maxFrequency > 0) { - bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); - if (bins > m_fftSize / 2) bins = m_fftSize / 2; + bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1); + if (bins > getFFTSize() / 2) bins = getFFTSize() / 2; } int cw = 0; - if (detailed) cw = getColourScaleWidth(paint); - int cbw = paint.fontMetrics().width("dB"); int py = -1; - int textHeight = paint.fontMetrics().height(); int toff = -textHeight + paint.fontMetrics().ascent() + 2; - if (detailed && (h > textHeight * 3 + 10)) { - - int topLines = 2; - if (m_colourScale == PhaseColourScale) topLines = 1; - - int ch = h - textHeight * (topLines + 1) - 8; -// paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); - paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); - - QString top, bottom; - double min = m_viewMags[v].getMin(); - double max = m_viewMags[v].getMax(); - - double dBmin = AudioLevel::multiplier_to_dB(min); - double dBmax = AudioLevel::multiplier_to_dB(max); - - if (dBmax < -60.f) dBmax = -60.f; - else top = QString("%1").arg(lrint(dBmax)); - - if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; - bottom = QString("%1").arg(lrint(dBmin)); - - //!!! & phase etc - - if (m_colourScale != PhaseColourScale) { - paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, - 2 + textHeight + toff, "dBFS"); - } - -// paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), - 2 + textHeight * topLines + toff + textHeight/2, top); - - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), - h + toff - 3 - textHeight/2, bottom); - - paint.save(); - paint.setBrush(Qt::NoBrush); - - int lasty = 0; - int lastdb = 0; - - for (int i = 0; i < ch; ++i) { - - double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); - int idb = int(dBval); - - double value = AudioLevel::dB_to_multiplier(dBval); - int colour = getDisplayValue(v, value * m_gain); - - paint.setPen(m_palette.getColour((unsigned char)colour)); - - int y = textHeight * topLines + 4 + ch - i; - - paint.drawLine(5 + cw - cbw, y, cw + 2, y); - - if (i == 0) { - lasty = y; - lastdb = idb; - } else if (i < ch - paint.fontMetrics().ascent() && - idb != lastdb && - ((abs(y - lasty) > textHeight && - idb % 10 == 0) || - (abs(y - lasty) > paint.fontMetrics().ascent() && - idb % 5 == 0))) { - paint.setPen(v->getBackground()); - QString text = QString("%1").arg(idb); - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), - y + toff + textHeight/2, text); - paint.setPen(v->getForeground()); - paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); - lasty = y; - lastdb = idb; - } - } - paint.restore(); - } - paint.drawLine(cw + 7, 0, cw + 7, h); int bin = -1; @@ -3283,10 +2068,10 @@ continue; } - int freq = int((sr * bin) / m_fftSize); + int freq = int((sr * bin) / getFFTSize()); if (py >= 0 && (vy - py) < textHeight - 1) { - if (m_frequencyScale == LinearFrequencyScale) { + if (m_binScale == BinScale::Linear) { paint.drawLine(w - tickw, h - vy, w, h - vy); } continue; @@ -3297,14 +2082,14 @@ 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); } py = vy; } - if (m_frequencyScale == LogFrequencyScale) { + if (m_binScale == BinScale::Log) { // piano keyboard @@ -3316,6 +2101,152 @@ m_haveDetailedScale = detailed; } +void +SpectrogramLayer::paintDetailedScale(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + // The colour scale + + if (m_colourScale == ColourScaleType::Phase) { + paintDetailedScalePhase(v, paint, rect); + return; + } + + int h = rect.height(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + int cw = getColourScaleWidth(paint); + int cbw = paint.fontMetrics().width("dB"); + + int topLines = 2; + + int ch = h - textHeight * (topLines + 1) - 8; +// paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); + paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); + + QString top, bottom; + double min = m_viewMags[v->getId()].getMin(); + double max = m_viewMags[v->getId()].getMax(); + + if (min < m_threshold) min = m_threshold; + if (max <= min) max = min + 0.1; + + 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(lrint(dBmax)); + + if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; + bottom = QString("%1").arg(lrint(dBmin)); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "adjusted dB range to min = " << dBmin << ", max = " << dBmax + << endl; +#endif + + paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, + 2 + textHeight + toff, "dBFS"); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), + 2 + textHeight * topLines + toff + textHeight/2, top); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), + h + toff - 3 - textHeight/2, bottom); + + paint.save(); + paint.setBrush(Qt::NoBrush); + + int lasty = 0; + int lastdb = 0; + + for (int i = 0; i < ch; ++i) { + + double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); + int idb = int(dBval); + + double value = AudioLevel::dB_to_multiplier(dBval); + paint.setPen(getRenderer(v)->getColour(value)); + + int y = textHeight * topLines + 4 + ch - i; + + paint.drawLine(5 + cw - cbw, y, cw + 2, y); + + if (i == 0) { + lasty = y; + lastdb = idb; + } else if (i < ch - paint.fontMetrics().ascent() && + idb != lastdb && + ((abs(y - lasty) > textHeight && + idb % 10 == 0) || + (abs(y - lasty) > paint.fontMetrics().ascent() && + idb % 5 == 0))) { + paint.setPen(v->getForeground()); + QString text = QString("%1").arg(idb); + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), + y + toff + textHeight/2, text); + paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); + lasty = y; + lastdb = idb; + } + } + paint.restore(); +} + +void +SpectrogramLayer::paintDetailedScalePhase(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + // The colour scale in phase mode + + int h = rect.height(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + int cw = getColourScaleWidth(paint); + + // Phase is not measured in dB of course, but this places the + // scale at the same position as in the magnitude spectrogram + int cbw = paint.fontMetrics().width("dB"); + + int topLines = 1; + + int ch = h - textHeight * (topLines + 1) - 8; + paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); + + QString top = Strings::pi, bottom = Strings::minus_pi, middle = "0"; + + double min = -M_PI; + double max = M_PI; + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), + 2 + textHeight * topLines + toff + textHeight/2, top); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(middle), + 2 + textHeight * topLines + ch/2 + toff + textHeight/2, middle); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), + h + toff - 3 - textHeight/2, bottom); + + paint.save(); + paint.setBrush(Qt::NoBrush); + + for (int i = 0; i < ch; ++i) { + double val = min + (((max - min) * i) / (ch - 1)); + paint.setPen(getRenderer(v)->getColour(val)); + int y = textHeight * topLines + 4 + ch - i; + paint.drawLine(5 + cw - cbw, y, cw + 2, y); + } + paint.restore(); +} + class SpectrogramRangeMapper : public RangeMapper { public: @@ -3380,9 +2311,9 @@ sv_samplerate_t sr = m_model->getSampleRate(); - SpectrogramRangeMapper mapper(sr, m_fftSize); - -// int maxStep = mapper.getPositionForValue((double(sr) / m_fftSize) + 0.001); + SpectrogramRangeMapper mapper(sr, getFFTSize()); + +// int maxStep = mapper.getPositionForValue((double(sr) / getFFTSize()) + 0.001); int maxStep = mapper.getPositionForValue(0); int minStep = mapper.getPositionForValue(double(sr) / 2); @@ -3404,7 +2335,7 @@ double dmin, dmax; getDisplayExtents(dmin, dmax); - SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize); + SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize()); int n = mapper.getPositionForValue(dmax - dmin); // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl; return n; @@ -3421,12 +2352,12 @@ // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl; sv_samplerate_t sr = m_model->getSampleRate(); - SpectrogramRangeMapper mapper(sr, m_fftSize); + SpectrogramRangeMapper mapper(sr, getFFTSize()); double newdist = mapper.getValueForPosition(step); double newmin, newmax; - if (m_frequencyScale == LogFrequencyScale) { + if (m_binScale == BinScale::Log) { // need to pick newmin and newmax such that // @@ -3482,7 +2413,7 @@ SpectrogramLayer::getNewVerticalZoomRangeMapper() const { if (!m_model) return 0; - return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize); + return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize()); } void @@ -3538,11 +2469,11 @@ "binDisplay=\"%7\" ") .arg(m_minFrequency) .arg(m_maxFrequency) - .arg(m_colourScale) + .arg(convertFromColourScale(m_colourScale, m_colourScaleMultiple)) .arg(m_colourMap) .arg(m_colourRotation) - .arg(m_frequencyScale) - .arg(m_binDisplay); + .arg(int(m_binScale)) + .arg(int(m_binDisplay)); // New-style normalization attributes, allowing for more types of // normalization in future: write out the column normalization @@ -3550,8 +2481,8 @@ // area as well afterwards s += QString("columnNormalization=\"%1\" ") - .arg(m_normalization == NormalizeColumns ? "peak" : - m_normalization == NormalizeHybrid ? "hybrid" : "none"); + .arg(m_normalization == ColumnNormalization::Max1 ? "peak" : + m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none"); // Old-style normalization attribute. We *don't* write out // normalizeHybrid here because the only release that would accept @@ -3560,12 +2491,12 @@ // v2.0+ will look odd in Tony v1.0 s += QString("normalizeColumns=\"%1\" ") - .arg(m_normalization == NormalizeColumns ? "true" : "false"); + .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false"); // And this applies to both old- and new-style attributes s += QString("normalizeVisibleArea=\"%1\" ") - .arg(m_normalization == NormalizeVisibleArea ? "true" : "false"); + .arg(m_normalizeVisibleArea ? "true" : "false"); Layer::toXml(stream, indent, extraAttributes + " " + s); } @@ -3613,9 +2544,12 @@ setMaxFrequency(maxFrequency); } - ColourScale colourScale = (ColourScale) - attributes.value("colourScale").toInt(&ok); - if (ok) setColourScale(colourScale); + auto colourScale = convertToColourScale + (attributes.value("colourScale").toInt(&ok)); + if (ok) { + setColourScale(colourScale.first); + setColourScaleMultiple(colourScale.second); + } int colourMap = attributes.value("colourScheme").toInt(&ok); if (ok) setColourMap(colourMap); @@ -3623,9 +2557,9 @@ int colourRotation = attributes.value("colourRotation").toInt(&ok); if (ok) setColourRotation(colourRotation); - FrequencyScale frequencyScale = (FrequencyScale) + BinScale binScale = (BinScale) attributes.value("frequencyScale").toInt(&ok); - if (ok) setFrequencyScale(frequencyScale); + if (ok) setBinScale(binScale); BinDisplay binDisplay = (BinDisplay) attributes.value("binDisplay").toInt(&ok); @@ -3640,11 +2574,11 @@ haveNewStyleNormalization = true; if (columnNormalization == "peak") { - setNormalization(NormalizeColumns); + setNormalization(ColumnNormalization::Max1); } else if (columnNormalization == "hybrid") { - setNormalization(NormalizeHybrid); + setNormalization(ColumnNormalization::Hybrid); } else if (columnNormalization == "none") { - // do nothing + setNormalization(ColumnNormalization::None); } else { cerr << "NOTE: Unknown or unsupported columnNormalization attribute \"" << columnNormalization << "\"" << endl; @@ -3656,29 +2590,27 @@ bool normalizeColumns = (attributes.value("normalizeColumns").trimmed() == "true"); if (normalizeColumns) { - setNormalization(NormalizeColumns); + setNormalization(ColumnNormalization::Max1); } bool normalizeHybrid = (attributes.value("normalizeHybrid").trimmed() == "true"); if (normalizeHybrid) { - setNormalization(NormalizeHybrid); + setNormalization(ColumnNormalization::Hybrid); } } bool normalizeVisibleArea = - (attributes.value("normalizeVisibleArea").trimmed() == "true"); - if (normalizeVisibleArea) { - setNormalization(NormalizeVisibleArea); - } - - if (!haveNewStyleNormalization && m_normalization == NormalizeHybrid) { + (attributes.value("normalizeVisibleArea").trimmed() == "true"); + setNormalizeVisibleArea(normalizeVisibleArea); + + if (!haveNewStyleNormalization && m_normalization == ColumnNormalization::Hybrid) { // 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)); + setGain(m_gain / float(getFFTSize() / 2)); } } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/SpectrogramLayer.h --- a/layer/SpectrogramLayer.h Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/SpectrogramLayer.h Fri Aug 19 15:58:57 2016 +0100 @@ -13,11 +13,12 @@ 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" +#include "base/MagnitudeRange.h" #include "base/RealTime.h" #include "base/Thread.h" #include "base/PropertyContainer.h" @@ -25,6 +26,10 @@ #include "data/model/DenseTimeValueModel.h" #include "data/model/FFTModel.h" +#include "VerticalBinLayer.h" +#include "ColourScale.h" +#include "Colour3DPlotRenderer.h" + #include #include #include @@ -38,13 +43,12 @@ class FFTModel; class Dense3DModelPeakCache; - /** * SpectrogramLayer represents waveform data (obtained from a * DenseTimeValueModel) in spectrogram form. */ -class SpectrogramLayer : public SliceableLayer, +class SpectrogramLayer : public VerticalBinLayer, public PowerOfSqrtTwoZoomConstraint { Q_OBJECT @@ -110,9 +114,6 @@ void setWindowType(WindowType type); WindowType getWindowType() const; - void setZeroPadLevel(int level); - int getZeroPadLevel() const; - /** * Set the gain multiplier for sample values in this view. * The default is 1.0. @@ -124,7 +125,7 @@ * Set the threshold for sample values to qualify for being shown * in the FFT, in voltage units. * - * The default is 0.0. + * The default is 10^-8 (-80dB). */ void setThreshold(float threshold); float getThreshold() const; @@ -135,56 +136,44 @@ void setMaxFrequency(int); // 0 -> no maximum int getMaxFrequency() const; - enum ColourScale { - LinearColourScale, - MeterColourScale, - dBSquaredColourScale, - dBColourScale, - PhaseColourScale - }; + /** + * Specify the scale for sample levels. See ColourScale and + * WaveformLayer for comparison and details of meter and dB + * scaling. The default is LogColourScale. + */ + void setColourScale(ColourScaleType); + ColourScaleType getColourScale() const; /** - * Specify the scale for sample levels. See WaveformLayer for - * details of meter and dB scaling. The default is dBColourScale. + * Specify multiple factor for colour scale. This is 2.0 for + * log-power spectrogram and 1.0 otherwise. */ - void setColourScale(ColourScale); - ColourScale getColourScale() const; - - enum FrequencyScale { - LinearFrequencyScale, - LogFrequencyScale - }; + void setColourScaleMultiple(double); + double getColourScaleMultiple() const; /** * Specify the scale for the y axis. */ - void setFrequencyScale(FrequencyScale); - FrequencyScale getFrequencyScale() const; + void setBinScale(BinScale); + BinScale getBinScale() const; - enum BinDisplay { - AllBins, - PeakBins, - PeakFrequencies - }; - /** * Specify the processing of frequency bins for the y axis. */ void setBinDisplay(BinDisplay); BinDisplay getBinDisplay() const; - enum Normalization { - NoNormalization, - NormalizeColumns, - NormalizeVisibleArea, - NormalizeHybrid - }; + /** + * Specify the normalization mode for individual columns. + */ + void setNormalization(ColumnNormalization); + ColumnNormalization getNormalization() const; /** - * Specify the normalization mode for bin values. + * Specify whether to normalize the visible area. */ - void setNormalization(Normalization); - Normalization getNormalization() const; + void setNormalizeVisibleArea(bool); + bool getNormalizeVisibleArea() const; /** * Specify the colour map. See ColourMapper for the colour map @@ -212,6 +201,10 @@ double getYForFrequency(const LayerGeometryProvider *v, double frequency) const; double getFrequencyForY(const LayerGeometryProvider *v, int y) const; + //!!! VerticalBinLayer methods. Note overlap with get*BinRange() + double getYForBin(const LayerGeometryProvider *, double bin) const; + double getBinForY(const LayerGeometryProvider *, double y) const; + virtual int getCompletion(LayerGeometryProvider *v) const; virtual QString getError(LayerGeometryProvider *v) const; @@ -250,84 +243,38 @@ const DenseTimeValueModel *m_model; // I do not own this int m_channel; - int m_windowSize; + int m_windowSize; WindowType m_windowType; - int m_windowHopLevel; - int m_zeroPadLevel; - int m_fftSize; + int m_windowHopLevel; float m_gain; float m_initialGain; float m_threshold; float m_initialThreshold; int m_colourRotation; int m_initialRotation; - int m_minFrequency; - int m_maxFrequency; - int m_initialMaxFrequency; - ColourScale m_colourScale; + int m_minFrequency; + int m_maxFrequency; + int m_initialMaxFrequency; + ColourScaleType m_colourScale; + double m_colourScaleMultiple; int m_colourMap; QColor m_crosshairColour; - FrequencyScale m_frequencyScale; + BinScale m_binScale; BinDisplay m_binDisplay; - Normalization m_normalization; + ColumnNormalization m_normalization; // of individual columns + bool m_normalizeVisibleArea; 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 - - class Palette - { - public: - QColor getColour(unsigned char index) const { - return m_colours[index]; - } - - void setColour(unsigned char index, QColor colour) { - m_colours[index] = colour; - } - - private: - QColor m_colours[256]; - }; - - 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; - sv_frame_t startFrame; - int zoomLevel; - }; - typedef std::map ViewImageCache; - void invalidateImageCaches(); - void invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame); - mutable ViewImageCache m_imageCaches; - - /** - * When painting, we draw directly onto the draw buffer and then - * copy this to the part of the image cache that needed refreshing - * before copying the image cache onto the window. (Remind me why - * we don't draw directly onto the cache?) - */ - mutable QImage m_drawBuffer; + static std::pair convertToColourScale(int value); + static int convertFromColourScale(ColourScaleType type, double multiple); + static std::pair convertToColumnNorm(int value); + static int convertFromColumnNorm(ColumnNormalization norm, bool visible); bool m_exiting; - void initialisePalette(); - void rotatePalette(int distance); - - unsigned char getDisplayValue(LayerGeometryProvider *v, double input) const; - int getColourScaleWidth(QPainter &) const; void illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &painter) const; @@ -335,15 +282,8 @@ 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 - // as is pulled from the spectrogram and drawn at the given - // position, if the spectrogram has oversampling smoothing. Use - // getSmoothedYBinRange to obtain that. - 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(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) const; bool getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y, @@ -359,83 +299,38 @@ else return m_windowSize / (1 << (m_windowHopLevel - 1)); } - 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(); + int getFFTOversampling() const; + int getFFTSize() const; // m_windowSize * getFFTOversampling() - typedef std::map ViewFFTMap; - typedef std::map PeakCacheMap; - mutable ViewFFTMap m_fftModels; - mutable PeakCacheMap m_peakCaches; - mutable Model *m_sliceableModel; + mutable FFTModel *m_fftModel; //!!! should not be mutable, see getFFTModel()? + mutable Dense3DModelPeakCache *m_peakCache; + const int m_peakCacheDivisor; - class MagnitudeRange { - public: - MagnitudeRange() : m_min(0), m_max(0) { } - bool operator==(const MagnitudeRange &r) { - return r.m_min == m_min && r.m_max == m_max; - } - bool isSet() const { return (m_min != 0.f || m_max != 0.f); } - void set(float min, float max) { - m_min = min; - m_max = max; - if (m_max < m_min) m_max = m_min; - } - bool sample(float f) { - bool changed = false; - if (isSet()) { - if (f < m_min) { m_min = f; changed = true; } - if (f > m_max) { m_max = f; changed = true; } - } else { - m_max = m_min = f; - changed = true; - } - return changed; - } - bool sample(const MagnitudeRange &r) { - bool changed = false; - if (isSet()) { - if (r.m_min < m_min) { m_min = r.m_min; changed = true; } - if (r.m_max > m_max) { m_max = r.m_max; changed = true; } - } else { - m_min = r.m_min; - m_max = r.m_max; - changed = true; - } - return changed; - } - float getMin() const { return m_min; } - float getMax() const { return m_max; } - private: - float m_min; - float m_max; - }; + typedef std::map ViewMagMap; // key is view id + mutable ViewMagMap m_viewMags; + mutable ViewMagMap m_lastRenderedMags; // when in normalizeVisibleArea mode + void invalidateMagnitudes(); - typedef std::map ViewMagMap; - mutable ViewMagMap m_viewMags; - mutable std::vector m_columnMags; - void invalidateMagnitudes(); - bool updateViewMagnitudes(LayerGeometryProvider *v) const; - bool paintDrawBuffer(LayerGeometryProvider *v, int w, int h, - const std::vector &binforx, - const std::vector &binfory, - bool usePeaksCache, - MagnitudeRange &overallMag, - bool &overallMagChanged) const; - bool paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, int w, int h, - const std::vector &binforx, - int minbin, - int maxbin, - double displayMinFreq, - double displayMaxFreq, - bool logarithmic, - MagnitudeRange &overallMag, - bool &overallMagChanged) const; + typedef std::map ViewRendererMap; // key is view id + mutable ViewRendererMap m_renderers; + Colour3DPlotRenderer *getRenderer(LayerGeometryProvider *) const; + void invalidateRenderers(); + + FFTModel *getFFTModel() const; + Dense3DModelPeakCache *getPeakCache() const; + void invalidateFFTModel(); - virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const; - virtual void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const; + void paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; + + void paintDetailedScale(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const; + void paintDetailedScalePhase(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const; + + virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, + const MeasureRect &r) const; + virtual void setMeasureRectYCoord(LayerGeometryProvider *v, + MeasureRect &r, bool start, int y) const; }; #endif diff -r 96cf499fad62 -r c0d841cb8ab9 layer/SpectrumLayer.cpp --- a/layer/SpectrumLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/SpectrumLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -21,7 +21,10 @@ #include "base/Preferences.h" #include "base/RangeMapper.h" #include "base/Pitch.h" +#include "base/Strings.h" + #include "ColourMapper.h" +#include "PaintAssistant.h" #include #include @@ -491,19 +494,19 @@ int hoffset = 2; if (m_binScale == LogBins) hoffset = 13; - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() + 2, v->getPaintHeight() - 2 - hoffset, QString("%1 Hz").arg(fundamental), - View::OutlinedText); + PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(fundamental)) { QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2, v->getPaintHeight() - 2 - hoffset, pitchLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); } double value = getValueForY(cursorPos.y(), v); @@ -512,17 +515,17 @@ if (value > 0.0) db = 10.0 * log10(value); if (db < thresh) db = thresh; - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() - 2, QString("%1 V").arg(value), - View::OutlinedText); + PaintAssistant::OutlinedText); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() + 2 + paint.fontMetrics().ascent(), QString("%1 dBV").arg(db), - View::OutlinedText); + PaintAssistant::OutlinedText); int harmonic = 2; @@ -605,12 +608,12 @@ QString mindbstr; QString maxdbstr; if (mindb == AudioLevel::DB_FLOOR) { - mindbstr = tr("-Inf"); + mindbstr = Strings::minus_infinity; } else { mindbstr = QString("%1").arg(lrint(mindb)); } if (maxdb == AudioLevel::DB_FLOOR) { - maxdbstr = tr("-Inf"); + maxdbstr = Strings::minus_infinity; } else { maxdbstr = QString("%1").arg(lrint(maxdb)); } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/TimeInstantLayer.cpp --- a/layer/TimeInstantLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/TimeInstantLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -20,7 +20,9 @@ #include "view/View.h" #include "base/Profiler.h" #include "base/Clipboard.h" + #include "ColourDatabase.h" +#include "PaintAssistant.h" #include "data/model/SparseOneDimensionalModel.h" @@ -456,7 +458,7 @@ } if (good) { - v->drawVisibleText(paint, x + iw + 2, textY, p.label, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x + iw + 2, textY, p.label, PaintAssistant::OutlinedText); // paint.drawText(x + iw + 2, textY, p.label); } } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/TimeRulerLayer.cpp --- a/layer/TimeRulerLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/TimeRulerLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -19,19 +19,20 @@ #include "data/model/Model.h" #include "base/RealTime.h" +#include "view/View.h" + #include "ColourDatabase.h" -#include "view/View.h" +#include "PaintAssistant.h" #include #include #include +#include //#define DEBUG_TIME_RULER_LAYER 1 - - TimeRulerLayer::TimeRulerLayer() : SingleColourLayer(), m_model(0), @@ -182,6 +183,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; } @@ -241,6 +244,9 @@ // 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 @@ -253,10 +259,17 @@ 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) { @@ -313,7 +326,7 @@ // backmost layer, don't worry about outlining the text paint.drawText(x+2 - tw/2, y, text); } else { - v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText); } } } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/TimeValueLayer.cpp --- a/layer/TimeValueLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/TimeValueLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -21,7 +21,6 @@ #include "base/LogRange.h" #include "base/RangeMapper.h" #include "base/Pitch.h" -#include "ColourDatabase.h" #include "view/View.h" #include "data/model/SparseTimeValueModel.h" @@ -31,12 +30,14 @@ #include "widgets/ListInputDialog.h" #include "widgets/TextAbbrev.h" +#include "ColourDatabase.h" #include "ColourMapper.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" #include "LinearColourScale.h" #include "LogColourScale.h" +#include "PaintAssistant.h" #include #include @@ -1214,10 +1215,10 @@ if (haveRoom || (!haveNext && (pointCount == 0 || !italic))) { - v->drawVisibleText(paint, x + 5, textY, label, + PaintAssistant::drawVisibleText(v, paint, x + 5, textY, label, italic ? - View::OutlinedItalicText : - View::OutlinedText); + PaintAssistant::OutlinedItalicText : + PaintAssistant::OutlinedText); } } } diff -r 96cf499fad62 -r c0d841cb8ab9 layer/VerticalBinLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/VerticalBinLayer.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,62 @@ +/* -*- 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-2016 Chris Cannam and QMUL. + + 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 VERTICAL_BIN_LAYER_H +#define VERTICAL_BIN_LAYER_H + +#include "SliceableLayer.h" + +/** + * Interface for layers in which the Y axis corresponds to bin number + * rather than scale value. Colour3DPlotLayer is the obvious example. + * Conceptually these are always SliceableLayers as well, and this + * subclasses from SliceableLayer to avoid a big inheritance mess. + */ +class VerticalBinLayer : public SliceableLayer +{ +public: + /** + * 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. + */ + virtual double getYForBin(const LayerGeometryProvider *, double bin) const = 0; + + /** + * As getYForBin, but rounding to integer values. + */ + virtual int getIYForBin(const LayerGeometryProvider *v, int bin) const { + return int(round(getYForBin(v, bin))); + } + + /** + * 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). + */ + virtual double getBinForY(const LayerGeometryProvider *, double y) const = 0; + + /** + * As getBinForY, but rounding to integer values. + */ + virtual int getIBinForY(const LayerGeometryProvider *v, int y) const { + return int(floor(getBinForY(v, y))); + } +}; + +#endif + diff -r 96cf499fad62 -r c0d841cb8ab9 layer/VerticalScaleLayer.h --- a/layer/VerticalScaleLayer.h Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/VerticalScaleLayer.h Fri Aug 19 15:58:57 2016 +0100 @@ -16,6 +16,12 @@ #ifndef VERTICAL_SCALE_LAYER_H #define VERTICAL_SCALE_LAYER_H +/** + * Interface for layers in which the Y axis represents (or can + * sometimes represent, depending on the display mode) the sample + * value. For example, TimeValueLayer uses vertical scale when in + * point mode and so provides this interface. + */ class VerticalScaleLayer { public: diff -r 96cf499fad62 -r c0d841cb8ab9 layer/WaveformLayer.cpp --- a/layer/WaveformLayer.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/layer/WaveformLayer.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -19,7 +19,10 @@ #include "view/View.h" #include "base/Profiler.h" #include "base/RangeMapper.h" +#include "base/Strings.h" + #include "ColourDatabase.h" +#include "PaintAssistant.h" #include #include @@ -1201,7 +1204,7 @@ return paint.fontMetrics().width("0.0") + 13; } else { return std::max(paint.fontMetrics().width(tr("0dB")), - paint.fontMetrics().width(tr("-Inf"))) + 13; + paint.fontMetrics().width(Strings::minus_infinity)) + 13; } } @@ -1255,7 +1258,7 @@ text = QString("%1").arg(meterdbs[i]); if (i == n) text = tr("0dB"); if (i == 0) { - text = tr("-Inf"); + text = Strings::minus_infinity; val = 0.0; } break; @@ -1265,7 +1268,7 @@ text = QString("%1").arg(-(10*n) + i * 10); if (i == n) text = tr("0dB"); if (i == 0) { - text = tr("-Inf"); + text = Strings::minus_infinity; val = 0.0; } break; diff -r 96cf499fad62 -r c0d841cb8ab9 svgui.pro --- a/svgui.pro Tue Oct 20 12:55:09 2015 +0100 +++ b/svgui.pro Fri Aug 19 15:58:57 2016 +0100 @@ -1,6 +1,9 @@ TEMPLATE = lib +INCLUDEPATH += ../vamp-plugin-sdk +DEFINES += HAVE_VAMP HAVE_VAMPHOSTSDK + exists(config.pri) { include(config.pri) } @@ -22,7 +25,7 @@ LIBS += -L../sv-dependency-builds/osx/lib } - DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_LIBLO HAVE_MAD HAVE_ID3TAG + 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 c++11 @@ -36,14 +39,17 @@ MOC_DIR = o HEADERS += layer/Colour3DPlotLayer.h \ + layer/Colour3DPlotRenderer.h \ layer/ColourDatabase.h \ layer/ColourMapper.h \ + layer/ColourScale.h \ layer/ColourScaleLayer.h \ layer/FlexiNoteLayer.h \ layer/ImageLayer.h \ layer/ImageRegionFinder.h \ layer/Layer.h \ layer/LayerFactory.h \ + layer/LayerGeometryProvider.h \ layer/LinearNumericalScale.h \ layer/LogNumericalScale.h \ layer/LinearColourScale.h \ @@ -52,6 +58,9 @@ layer/PaintAssistant.h \ layer/PianoScale.h \ layer/RegionLayer.h \ + layer/RenderTimer.h \ + layer/ScrollableImageCache.h \ + layer/ScrollableMagRangeCache.h \ layer/SingleColourLayer.h \ layer/SliceableLayer.h \ layer/SliceLayer.h \ @@ -64,8 +73,10 @@ layer/VerticalScaleLayer.h \ layer/WaveformLayer.h SOURCES += layer/Colour3DPlotLayer.cpp \ + layer/Colour3DPlotRenderer.cpp \ layer/ColourDatabase.cpp \ layer/ColourMapper.cpp \ + layer/ColourScale.cpp \ layer/FlexiNoteLayer.cpp \ layer/ImageLayer.cpp \ layer/ImageRegionFinder.cpp \ @@ -79,6 +90,8 @@ layer/PaintAssistant.cpp \ layer/PianoScale.cpp \ layer/RegionLayer.cpp \ + layer/ScrollableImageCache.cpp \ + layer/ScrollableMagRangeCache.cpp \ layer/SingleColourLayer.cpp \ layer/SliceLayer.cpp \ layer/SpectrogramLayer.cpp \ @@ -89,13 +102,16 @@ layer/TimeValueLayer.cpp \ layer/WaveformLayer.cpp -HEADERS += view/Overview.h \ +HEADERS += view/AlignmentView.h \ + view/Overview.h \ view/Pane.h \ view/PaneStack.h \ view/View.h \ view/ViewManager.h \ view/ViewProxy.h -SOURCES += view/Overview.cpp \ + +SOURCES += view/AlignmentView.cpp \ + view/Overview.cpp \ view/Pane.cpp \ view/PaneStack.cpp \ view/View.cpp \ diff -r 96cf499fad62 -r c0d841cb8ab9 view/AlignmentView.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/AlignmentView.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,200 @@ +/* -*- 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-2014 Chris Cannam and QMUL. + + 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 "AlignmentView.h" + +#include + +#include "data/model/SparseOneDimensionalModel.h" + +#include "layer/TimeInstantLayer.h" + +using std::vector; + +AlignmentView::AlignmentView(QWidget *w) : + View(w, false), + m_above(0), + m_below(0) +{ + setObjectName(tr("AlignmentView")); +} + +void +AlignmentView::globalCentreFrameChanged(sv_frame_t f) +{ + View::globalCentreFrameChanged(f); + update(); +} + +void +AlignmentView::viewCentreFrameChanged(View *v, sv_frame_t f) +{ + View::viewCentreFrameChanged(v, f); + if (v == m_above) { + m_centreFrame = f; + update(); + } else if (v == m_below) { + update(); + } +} + +void +AlignmentView::viewManagerPlaybackFrameChanged(sv_frame_t) +{ + update(); +} + +void +AlignmentView::viewAboveZoomLevelChanged(int level, bool) +{ + m_zoomLevel = level; + update(); +} + +void +AlignmentView::viewBelowZoomLevelChanged(int, bool) +{ + update(); +} + +void +AlignmentView::setViewAbove(View *v) +{ + if (m_above) { + disconnect(m_above, 0, this, 0); + } + + m_above = v; + + if (m_above) { + connect(m_above, + SIGNAL(zoomLevelChanged(int, bool)), + this, + SLOT(viewAboveZoomLevelChanged(int, bool))); + } +} + +void +AlignmentView::setViewBelow(View *v) +{ + if (m_below) { + disconnect(m_below, 0, this, 0); + } + + m_below = v; + + if (m_below) { + connect(m_below, + SIGNAL(zoomLevelChanged(int, bool)), + this, + SLOT(viewBelowZoomLevelChanged(int, bool))); + } +} + +void +AlignmentView::paintEvent(QPaintEvent *) +{ + if (m_above == 0 || m_below == 0 || !m_manager) return; + + bool darkPalette = false; + if (m_manager) darkPalette = m_manager->getGlobalDarkBackground(); + + QColor fg, bg; + if (darkPalette) { + fg = Qt::gray; + bg = Qt::black; + } else { + fg = Qt::black; + bg = Qt::gray; + } + + QPainter paint(this); + paint.setPen(QPen(fg, 2)); + paint.setBrush(Qt::NoBrush); + paint.setRenderHint(QPainter::Antialiasing, true); + + paint.fillRect(rect(), bg); + + vector keyFrames = getKeyFrames(); + + foreach (sv_frame_t f, keyFrames) { + int ax = m_above->getXForFrame(f); + sv_frame_t rf = m_above->alignToReference(f); + sv_frame_t bf = m_below->alignFromReference(rf); + int bx = m_below->getXForFrame(bf); + paint.drawLine(ax, 0, bx, height()); + } + + paint.end(); +} + +vector +AlignmentView::getKeyFrames() +{ + if (!m_above) { + return getDefaultKeyFrames(); + } + + SparseOneDimensionalModel *m = 0; + + // get the topmost such + for (int i = 0; i < m_above->getLayerCount(); ++i) { + if (qobject_cast(m_above->getLayer(i))) { + SparseOneDimensionalModel *mm = + qobject_cast + (m_above->getLayer(i)->getModel()); + if (mm) m = mm; + } + } + + if (!m) { + return getDefaultKeyFrames(); + } + + vector keyFrames; + + const SparseOneDimensionalModel::PointList pp = m->getPoints(); + for (SparseOneDimensionalModel::PointList::const_iterator pi = pp.begin(); + pi != pp.end(); ++pi) { + keyFrames.push_back(pi->frame); + } + + return keyFrames; +} + +vector +AlignmentView::getDefaultKeyFrames() +{ + vector keyFrames; + + if (!m_above || !m_manager) return keyFrames; + + sv_samplerate_t rate = m_manager->getMainModelSampleRate(); + if (rate == 0) return keyFrames; + + for (sv_frame_t f = m_above->getModelsStartFrame(); + f <= m_above->getModelsEndFrame(); + f += sv_frame_t(rate * 5 + 0.5)) { + keyFrames.push_back(f); + } + + return keyFrames; +} + + + + + + diff -r 96cf499fad62 -r c0d841cb8ab9 view/AlignmentView.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/AlignmentView.h Fri Aug 19 15:58:57 2016 +0100 @@ -0,0 +1,50 @@ +/* -*- 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-2014 Chris Cannam and QMUL. + + 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 ALIGNMENT_VIEW_H +#define ALIGNMENT_VIEW_H + +#include "View.h" + +class AlignmentView : public View +{ + Q_OBJECT + +public: + AlignmentView(QWidget *parent = 0); + virtual QString getPropertyContainerIconName() const { return "alignment"; } + + void setViewAbove(View *view); + void setViewBelow(View *view); + +public slots: + virtual void globalCentreFrameChanged(sv_frame_t); + virtual void viewCentreFrameChanged(View *, sv_frame_t); + virtual void viewAboveZoomLevelChanged(int, bool); + virtual void viewBelowZoomLevelChanged(int, bool); + virtual void viewManagerPlaybackFrameChanged(sv_frame_t); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual bool shouldLabelSelections() const { return false; } + + std::vector getKeyFrames(); + std::vector getDefaultKeyFrames(); + + View *m_above; + View *m_below; +}; + +#endif diff -r 96cf499fad62 -r c0d841cb8ab9 view/LayerGeometryProvider.h --- a/view/LayerGeometryProvider.h Tue Oct 20 12:55:09 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -/* -*- 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" - -class ViewManager; -class View; -class Layer; - -class LayerGeometryProvider -{ -public: - /** - * 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 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 View *getView() = 0; - virtual const View *getView() const = 0; -}; - -#endif diff -r 96cf499fad62 -r c0d841cb8ab9 view/Pane.cpp --- a/view/Pane.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/view/Pane.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -25,6 +25,7 @@ #include "base/Preferences.h" #include "layer/WaveformLayer.h" #include "layer/TimeRulerLayer.h" +#include "layer/PaintAssistant.h" // GF: added so we can propagate the mouse move event to the note layer for context handling. #include "layer/LayerFactory.h" @@ -717,6 +718,10 @@ void Pane::drawCentreLine(sv_samplerate_t sampleRate, QPainter &paint, bool omitLine) { + if (omitLine && m_manager->getMainModelSampleRate() == 0) { + return; + } + int fontHeight = paint.fontMetrics().height(); int fontAscent = paint.fontMetrics().ascent(); @@ -773,14 +778,14 @@ int tw = paint.fontMetrics().width(text); int x = width()/2 - 4 - tw; - drawVisibleText(paint, x, y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, x, y, text, PaintAssistant::OutlinedText); } QString text = QString("%1").arg(m_centreFrame); int x = width()/2 + 4; - drawVisibleText(paint, x, y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, x, y, text, PaintAssistant::OutlinedText); } } @@ -862,8 +867,8 @@ return; } - drawVisibleText(paint, m_scaleWidth + 5, - paint.fontMetrics().ascent() + y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, m_scaleWidth + 5, + paint.fontMetrics().ascent() + y, text, PaintAssistant::OutlinedText); paint.restore(); } @@ -901,8 +906,8 @@ return; } - drawVisibleText(paint, m_scaleWidth + 5, - paint.fontMetrics().ascent() + y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, m_scaleWidth + 5, + paint.fontMetrics().ascent() + y, text, PaintAssistant::OutlinedText); paint.restore(); } @@ -950,9 +955,9 @@ paint.setPen(getForeground()); } - drawVisibleText(paint, llx, + PaintAssistant::drawVisibleText(this, paint, llx, lly - fontHeight + fontAscent, - texts[i], OutlinedText); + texts[i], PaintAssistant::OutlinedText); if (!pixmaps[i].isNull()) { paint.drawPixmap(llx - fontAscent - 3, @@ -1014,10 +1019,10 @@ offsetText = tr("+%1").arg(offsetText); } } - drawVisibleText(paint, p0 + 2, fontAscent + fontHeight + 4, startText, OutlinedText); - drawVisibleText(paint, p1 + 2, fontAscent + fontHeight + 4, endText, OutlinedText); - drawVisibleText(paint, p0 + 2, fontAscent + fontHeight*2 + 4, offsetText, OutlinedText); - drawVisibleText(paint, p1 + 2, fontAscent + fontHeight*2 + 4, offsetText, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p0 + 2, fontAscent + fontHeight + 4, startText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p1 + 2, fontAscent + fontHeight + 4, endText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p0 + 2, fontAscent + fontHeight*2 + 4, offsetText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p1 + 2, fontAscent + fontHeight*2 + 4, offsetText, PaintAssistant::OutlinedText); //!!! duplicating display policy with View::drawSelections @@ -1080,9 +1085,9 @@ if (x < pbw + 5) x = pbw + 5; if (r.x() < x + paint.fontMetrics().width(desc)) { - drawVisibleText(paint, x, + PaintAssistant::drawVisibleText(this, paint, x, height() - fontHeight + fontAscent - 6, - desc, OutlinedText); + desc, PaintAssistant::OutlinedText); } } @@ -1943,7 +1948,7 @@ true, // can move horiz canTopLayerMoveVertical(), // can move vert canTopLayerMoveVertical() || (m_manager && m_manager->isPlaying()), // resist horiz - !(m_manager && m_manager->isPlaying())); // resist vert + true); // resist vert if (m_dragMode == HorizontalDrag || m_dragMode == FreeDrag) { @@ -2312,7 +2317,7 @@ m_pendingWheelAngle = 0; return; } - + while (abs(m_pendingWheelAngle) >= 120) { int sign = (m_pendingWheelAngle < 0 ? -1 : 1); diff -r 96cf499fad62 -r c0d841cb8ab9 view/PaneStack.cpp --- a/view/PaneStack.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/view/PaneStack.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -21,6 +21,7 @@ #include "widgets/ClickableLabel.h" #include "layer/Layer.h" #include "ViewManager.h" +#include "AlignmentView.h" #include #include @@ -40,6 +41,7 @@ QFrame(parent), m_currentPane(0), m_showAccessories(true), + m_showAlignmentViews(false), m_splitter(new QSplitter), m_propertyStackStack(new QStackedWidget), m_viewManager(viewManager), @@ -67,6 +69,15 @@ m_showAccessories = show; } +void +PaneStack::setShowAlignmentViews(bool show) +{ + m_showAlignmentViews = show; + foreach (const PaneRec &r, m_panes) { + r.alignmentView->setVisible(m_showAlignmentViews); + } +} + Pane * PaneStack::addPane(bool suppressPropertyBox) { @@ -112,6 +123,12 @@ layout->addWidget(pane, 0, 1, 2, 1); layout->setColumnStretch(1, 20); + AlignmentView *av = new AlignmentView(frame); + av->setFixedHeight(40);//!!! + av->setVisible(m_showAlignmentViews); + av->setViewManager(m_viewManager); + layout->addWidget(av, 2, 1); + QWidget *properties = 0; if (suppressPropertyBox) { properties = new QFrame(); @@ -139,6 +156,7 @@ rec.currentIndicator = currentIndicator; rec.frame = frame; rec.layout = layout; + rec.alignmentView = av; m_panes.push_back(rec); frame->setLayout(layout); @@ -167,11 +185,34 @@ } showOrHidePaneAccessories(); + relinkAlignmentViews(); return pane; } void +PaneStack::relinkAlignmentViews() +{ + for (int i = 0; i < (int)m_panes.size(); ++i) { + m_panes[i].alignmentView->setViewAbove(m_panes[i].pane); + if (i + 1 < (int)m_panes.size()) { + m_panes[i].alignmentView->setViewBelow(m_panes[i+1].pane); + } else { + m_panes[i].alignmentView->setViewBelow(0); + } + } +} + +void +PaneStack::unlinkAlignmentViews() +{ + for (int i = 0; i < (int)m_panes.size(); ++i) { + m_panes[i].alignmentView->setViewAbove(0); + m_panes[i].alignmentView->setViewBelow(0); + } +} + +void PaneStack::setPropertyStackMinWidth(int mw) { for (std::vector::iterator i = m_panes.begin(); @@ -279,6 +320,7 @@ } emit paneAboutToBeDeleted(pane); + unlinkAlignmentViews(); cerr << "PaneStack::deletePane: about to delete parent " << pane->parent() << " of pane " << pane << endl; @@ -303,6 +345,7 @@ } showOrHidePaneAccessories(); + relinkAlignmentViews(); emit paneDeleted(); } @@ -362,6 +405,8 @@ ++i; } + relinkAlignmentViews(); + cerr << "WARNING: PaneStack::hidePane(" << pane << "): Pane not found in visible panes" << endl; } @@ -386,6 +431,8 @@ ++i; } + relinkAlignmentViews(); + cerr << "WARNING: PaneStack::showPane(" << pane << "): Pane not found in hidden panes" << endl; } diff -r 96cf499fad62 -r c0d841cb8ab9 view/PaneStack.h --- a/view/PaneStack.h Tue Oct 20 12:55:09 2015 +0100 +++ b/view/PaneStack.h Fri Aug 19 15:58:57 2016 +0100 @@ -34,6 +34,7 @@ class ViewManager; class PropertyContainer; class PropertyStack; +class AlignmentView; class PaneStack : public QFrame { @@ -73,6 +74,8 @@ void setShowPaneAccessories(bool show); // current indicator, close button + void setShowAlignmentViews(bool show); + void sizePanesEqually(); signals: @@ -114,18 +117,20 @@ struct PaneRec { - Pane *pane; - QWidget *propertyStack; - QPushButton *xButton; - QLabel *currentIndicator; - QFrame *frame; - QGridLayout *layout; + Pane *pane; + QWidget *propertyStack; + QPushButton *xButton; + QLabel *currentIndicator; + QFrame *frame; + QGridLayout *layout; + AlignmentView *alignmentView; }; std::vector m_panes; std::vector m_hiddenPanes; bool m_showAccessories; + bool m_showAlignmentViews; QSplitter *m_splitter; QStackedWidget *m_propertyStackStack; @@ -136,6 +141,9 @@ void showOrHidePaneAccessories(); + void unlinkAlignmentViews(); + void relinkAlignmentViews(); + LayoutStyle m_layoutStyle; }; diff -r 96cf499fad62 -r c0d841cb8ab9 view/View.cpp --- a/view/View.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/view/View.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -24,6 +24,8 @@ #include "layer/TimeRulerLayer.h" #include "layer/SingleColourLayer.h" +#include "layer/PaintAssistant.h" + #include "data/model/PowerOfSqrtTwoZoomConstraint.h" #include "data/model/RangeSummarisableTimeValueModel.h" @@ -49,9 +51,9 @@ //#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), @@ -365,15 +367,17 @@ sv_frame_t View::getFrameForX(int x) const { - int z = m_zoomLevel; + 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; } double @@ -411,12 +415,12 @@ } double -View::getFrequencyForY(int y, +View::getFrequencyForY(double y, double minf, double maxf, bool logarithmic) const { - int h = height(); + double h = height(); if (logarithmic) { @@ -808,56 +812,6 @@ } void -View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style) const -{ - if (style == OutlinedText || style == OutlinedItalicText) { - - paint.save(); - - if (style == OutlinedItalicText) { - QFont f(paint.font()); - f.setItalic(true); - paint.setFont(f); - } - - QColor penColour, surroundColour, boxColour; - - penColour = getForeground(); - surroundColour = getBackground(); - boxColour = surroundColour; - boxColour.setAlpha(127); - - paint.setPen(Qt::NoPen); - paint.setBrush(boxColour); - - QRect r = paint.fontMetrics().boundingRect(text); - r.translate(QPoint(x, y)); -// cerr << "drawVisibleText: r = " << r.x() << "," < #include -#include "LayerGeometryProvider.h" +#include "layer/LayerGeometryProvider.h" #include "base/ZoomConstraint.h" #include "base/PropertyContainer.h" @@ -64,6 +64,12 @@ 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. @@ -108,6 +114,20 @@ 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 * frequency, if the frequency range is as specified. This does * not imply any policy about layer frequency ranges, but it might @@ -124,8 +144,8 @@ * * Not thread-safe in logarithmic mode. Call only from GUI thread. */ - double getFrequencyForY(int y, double minFreq, double maxFreq, - bool logarithmic) const; + double getFrequencyForY(double y, double minFreq, double maxFreq, + bool logarithmic) const; /** * Return the zoom level, i.e. the number of frames per pixel @@ -246,9 +266,6 @@ virtual QColor getForeground() const; virtual QColor getBackground() const; - 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; @@ -333,6 +350,8 @@ 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; } @@ -351,7 +370,7 @@ bool globalScroll, PlaybackFollowMode followMode); - void zoomLevelChanged(int, bool); + void zoomLevelChanged(int level, bool locked); void contextHelpChanged(const QString &); @@ -384,6 +403,9 @@ protected: View(QWidget *, bool showProgress); + + int m_id; + virtual void paintEvent(QPaintEvent *e); virtual void drawSelections(QPainter &); virtual bool shouldLabelSelections() const { return true; } diff -r 96cf499fad62 -r c0d841cb8ab9 view/ViewManager.cpp --- a/view/ViewManager.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/view/ViewManager.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -741,3 +741,4 @@ if (pixels != 0 && scaled == 0) scaled = 1; return scaled; } + diff -r 96cf499fad62 -r c0d841cb8ab9 view/ViewProxy.h --- a/view/ViewProxy.h Tue Oct 20 12:55:09 2015 +0100 +++ b/view/ViewProxy.h Fri Aug 19 15:58:57 2016 +0100 @@ -15,7 +15,7 @@ #ifndef VIEW_PROXY_H #define VIEW_PROXY_H -#include "LayerGeometryProvider.h" +#include "layer/LayerGeometryProvider.h" class ViewProxy : public LayerGeometryProvider { @@ -23,6 +23,9 @@ 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(); } @@ -42,6 +45,12 @@ 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(); } @@ -54,14 +63,10 @@ return m_scaleFactor * m_view->getYForFrequency(frequency, minFreq, maxFreq, logarithmic); } - virtual double getFrequencyForY(int y, double minFreq, double maxFreq, + virtual double getFrequencyForY(double y, double minFreq, double maxFreq, bool logarithmic) const { - double f0 = m_view->getFrequencyForY + return 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); @@ -119,16 +124,18 @@ 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; } diff -r 96cf499fad62 -r c0d841cb8ab9 widgets/AudioDial.cpp --- a/widgets/AudioDial.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/widgets/AudioDial.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -409,13 +409,27 @@ if (m_showTooltip) { QString name = objectName(); - QString unit = ""; + QString label; + if (m_rangeMapper) { + label = m_rangeMapper->getLabel(value); + } QString text; - if (m_rangeMapper) unit = m_rangeMapper->getUnit(); - if (name != "") { - text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); + if (label != "") { + if (name != "") { + text = tr("%1: %2").arg(name).arg(label); + } else { + text = label; + } } else { - text = tr("%2%3").arg(m_mappedValue).arg(unit); + QString unit = ""; + if (m_rangeMapper) { + unit = m_rangeMapper->getUnit(); + } + if (name != "") { + text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); + } else { + text = tr("%2%3").arg(m_mappedValue).arg(unit); + } } setToolTip(text); } diff -r 96cf499fad62 -r c0d841cb8ab9 widgets/PropertyBox.cpp --- a/widgets/PropertyBox.cpp Tue Oct 20 12:55:09 2015 +0100 +++ b/widgets/PropertyBox.cpp Fri Aug 19 15:58:57 2016 +0100 @@ -212,10 +212,10 @@ gainDial->setFixedWidth(24); gainDial->setFixedHeight(24); gainDial->setNotchesVisible(false); - gainDial->setDefaultValue(0); gainDial->setObjectName(tr("Playback Gain")); gainDial->setRangeMapper(new LinearRangeMapper (-50, 50, -25, 25, tr("dB"))); + gainDial->setDefaultValue(0); gainDial->setShowToolTip(true); connect(gainDial, SIGNAL(valueChanged(int)), this, SLOT(playGainDialChanged(int))); @@ -384,8 +384,10 @@ dial->setMaximum(max); dial->setPageStep(1); dial->setNotchesVisible((max - min) <= 12); + // important to set the range mapper before the default, + // because the range mapper is used to map the default + dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); dial->setDefaultValue(deflt); - dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); dial->setShowToolTip(true); connect(dial, SIGNAL(valueChanged(int)), this, SLOT(propertyControllerChanged(int)));