patch 8.1.1502: cannot play any sound

Problem:    Cannot play any sound.
Solution:   Use libcanberra if available.  Add sound functions.
diff --git a/.travis.yml b/.travis.yml
index 51d3578..53331e6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -77,6 +77,7 @@
       - clang
       - lcov
       - gettext
+      - libcanberra-dev
       - libperl-dev
       - python-dev
       - python3-dev
diff --git a/Filelist b/Filelist
index d6a90e2..7bf4fd6 100644
--- a/Filelist
+++ b/Filelist
@@ -88,6 +88,7 @@
 		src/search.c \
 		src/sha256.c \
 		src/sign.c \
+		src/sound.c \
 		src/spell.c \
 		src/spell.h \
 		src/spellfile.c \
@@ -150,6 +151,7 @@
 		src/testdir/samples/test000 \
 		src/testdir/if_ver*.vim \
 		src/testdir/color_ramp.vim \
+		src/testdir/silent.wav \
 		src/proto.h \
 		src/protodef.h \
 		src/proto/arabic.pro \
@@ -209,6 +211,7 @@
 		src/proto/search.pro \
 		src/proto/sha256.pro \
 		src/proto/sign.pro \
+		src/proto/sound.pro \
 		src/proto/spell.pro \
 		src/proto/spellfile.pro \
 		src/proto/syntax.pro \
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 85230a6..d1f6824 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2622,6 +2622,12 @@
 sinh({expr})			Float	hyperbolic sine of {expr}
 sort({list} [, {func} [, {dict}]])
 				List	sort {list}, using {func} to compare
+sound_playevent({name} [, {callback}])
+				Number	play an event sound
+sound_playfile({name} [, {callback}])
+				Number	play a sound file
+sound_stop({id})		none	stop playing sound {id}
+sound_stopall()			none	stop playing all sounds
 soundfold({word})		String	sound-fold {word}
 spellbadword()			String	badly spelled word at cursor
 spellsuggest({word} [, {max} [, {capital}]])
@@ -8837,6 +8843,49 @@
 			   return a:i1 - a:i2
 			endfunc
 <
+							*sound_playevent()*
+sound_playevent({name} [, {callback}])
+		Play a sound identified by {name}.  Which event names are
+		supported depends on the system.  Often the XDG sound names
+		are used.  On Ubuntu they may be found in
+		/usr/share/sounds/freedesktop/stereo.  Example: >
+			call sound_playevent('bell')
+
+<		When {callback} is specified it is invoked when the sound is
+		finished.  The first argument is the sound ID, the second
+		argument is the status:
+			0	sound was played to the end
+			1	sound was interruped
+			2	error occured after sound started
+		Example: >
+		   func Callback(id, status)
+		     echomsg "sound " .. a:id .. " finished with " .. a:status
+		   endfunc
+		   call sound_playevent('bell', 'Callback')
+
+<		Returns the sound ID, which can be passed to `sound_stop()`.
+		Returns zero if the sound could not be played.
+		{only available when compiled with the +sound feature}
+
+							*sound_playfile()*
+sound_playfile({name} [, {callback}])
+		Like `sound_playevent()` but play sound file {name}.  {name}
+		must be a full path.  On Ubuntu you may find files to play
+		with this command: >
+		    :!find /usr/share/sounds -type f | grep -v index.theme
+
+<		{only available when compiled with the +sound feature}
+
+
+sound_stop({id})					*sound_stop()*
+		Stop playing sound {id}.  {id} must be previously returned by
+		`sound_playevent()` or `sound_playfile()`.
+		{only available when compiled with the +sound feature}
+
+sound_stopall()						*sound_stopall()*
+		Stop playing all sounds.
+		{only available when compiled with the +sound feature}
+
 							*soundfold()*
 soundfold({word})
 		Return the sound-folded equivalent of {word}.  Uses the first
@@ -10756,6 +10805,7 @@
 showcmd			Compiled with 'showcmd' support.
 signs			Compiled with |:sign| support.
 smartindent		Compiled with 'smartindent' support.
+sound			Compiled with sound support, e.g. `sound_playevent()`
 spell			Compiled with spell checking support |spell|.
 startuptime		Compiled with |--startuptime| support.
 statusline		Compiled with support for 'statusline', 'rulerformat'
diff --git a/src/Makefile b/src/Makefile
index 65398d0..eef91ec 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1628,6 +1628,7 @@
 	search.c \
 	sha256.c \
 	sign.c \
+	sound.c \
 	spell.c \
 	spellfile.c \
 	syntax.c \
@@ -1743,6 +1744,7 @@
 	objects/search.o \
 	objects/sha256.o \
 	objects/sign.o \
+	objects/sound.o \
 	objects/spell.o \
 	objects/spellfile.o \
 	objects/syntax.o \
@@ -1883,6 +1885,7 @@
 	search.pro \
 	sha256.pro \
 	sign.pro \
+	sound.pro \
 	spell.pro \
 	spellfile.pro \
 	syntax.pro \
@@ -3235,6 +3238,9 @@
 objects/sign.o: sign.c
 	$(CCC) -o $@ sign.c
 
+objects/sound.o: sound.c
+	$(CCC) -o $@ sound.c
+
 objects/spell.o: spell.c
 	$(CCC) -o $@ spell.c
 
@@ -3650,6 +3656,10 @@
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+objects/sound.o: spell.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 objects/spell.o: spell.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
diff --git a/src/auto/configure b/src/auto/configure
index 1f5ee2a..d5b4f60 100755
--- a/src/auto/configure
+++ b/src/auto/configure
@@ -9303,28 +9303,8 @@
 
 
 
-
-if test -z "$SKIP_GTK2"; then
-
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking --disable-gtktest argument" >&5
-$as_echo_n "checking --disable-gtktest argument... " >&6; }
-  # Check whether --enable-gtktest was given.
-if test "${enable_gtktest+set}" = set; then :
-  enableval=$enable_gtktest;
-else
-  enable_gtktest=yes
-fi
-
-  if test "x$enable_gtktest" = "xyes" ; then
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test enabled" >&5
-$as_echo "gtk test enabled" >&6; }
-  else
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test disabled" >&5
-$as_echo "gtk test disabled" >&6; }
-  fi
-
-  if test "X$PKG_CONFIG" = "X"; then
-    if test -n "$ac_tool_prefix"; then
+if test "X$PKG_CONFIG" = "X"; then
+  if test -n "$ac_tool_prefix"; then
   # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
 set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -9422,6 +9402,26 @@
   PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
 fi
 
+fi
+
+
+if test -z "$SKIP_GTK2"; then
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking --disable-gtktest argument" >&5
+$as_echo_n "checking --disable-gtktest argument... " >&6; }
+  # Check whether --enable-gtktest was given.
+if test "${enable_gtktest+set}" = set; then :
+  enableval=$enable_gtktest;
+else
+  enable_gtktest=yes
+fi
+
+  if test "x$enable_gtktest" = "xyes" ; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test enabled" >&5
+$as_echo "gtk test enabled" >&6; }
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test disabled" >&5
+$as_echo "gtk test disabled" >&6; }
   fi
 
   if test "x$PKG_CONFIG" != "xno"; then
@@ -9677,107 +9677,6 @@
 $as_echo "gtk test disabled" >&6; }
   fi
 
-  if test "X$PKG_CONFIG" = "X"; then
-    if test -n "$ac_tool_prefix"; then
-  # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
-set dummy ${ac_tool_prefix}pkg-config; 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_path_PKG_CONFIG+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $PKG_CONFIG in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
-  ;;
-  *)
-  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_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
-    $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
-
-  ;;
-esac
-fi
-PKG_CONFIG=$ac_cv_path_PKG_CONFIG
-if test -n "$PKG_CONFIG"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
-$as_echo "$PKG_CONFIG" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-fi
-if test -z "$ac_cv_path_PKG_CONFIG"; then
-  ac_pt_PKG_CONFIG=$PKG_CONFIG
-  # Extract the first word of "pkg-config", so it can be a program name with args.
-set dummy pkg-config; 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_path_ac_pt_PKG_CONFIG+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $ac_pt_PKG_CONFIG in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
-  ;;
-  *)
-  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_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
-    $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
-
-  ;;
-esac
-fi
-ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
-if test -n "$ac_pt_PKG_CONFIG"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
-$as_echo "$ac_pt_PKG_CONFIG" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-  if test "x$ac_pt_PKG_CONFIG" = x; then
-    PKG_CONFIG="no"
-  else
-    case $cross_compiling:$ac_tool_warned in
-yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
-ac_tool_warned=yes ;;
-esac
-    PKG_CONFIG=$ac_pt_PKG_CONFIG
-  fi
-else
-  PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
-fi
-
-  fi
-
   if test "x$PKG_CONFIG" != "xno"; then
 
   if test "X$GTK_CONFIG" != "Xno" -o "X$PKG_CONFIG" != "Xno"; then
@@ -13026,6 +12925,56 @@
 fi
 
 
+
+if test "x$PKG_CONFIG" != "xno"; then
+  canberra_lib=`$PKG_CONFIG --libs libcanberrax 2>/dev/null`
+  canberra_cflags=`$PKG_CONFIG --cflags libcanberrax 2>/dev/null`
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_lib: $canberra_lib" >&5
+$as_echo "canberra_lib: $canberra_lib" >&6; }
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_cflags: $canberra_cflags" >&5
+$as_echo "canberra_cflags: $canberra_cflags" >&6; }
+fi
+if test "x$canberra_lib" = "x"; then
+  canberra_lib=-lcanberra
+  canberra_cflags=-D_REENTRANT
+fi
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_lib: $canberra_lib" >&5
+$as_echo "canberra_lib: $canberra_lib" >&6; }
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_cflags: $canberra_cflags" >&5
+$as_echo "canberra_cflags: $canberra_cflags" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcanberra" >&5
+$as_echo_n "checking for libcanberra... " >&6; }
+ac_save_CFLAGS="$CFLAGS"
+ac_save_LIBS="$LIBS"
+CFLAGS="$CFLAGS $canberra_cflags"
+LIBS="$LIBS $canberra_lib"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+# include <canberra.h>
+
+int
+main ()
+{
+
+   ca_context *hello;
+   ca_context_create(&hello);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }; $as_echo "#define HAVE_CANBERRA 1" >>confdefs.h
+
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }; CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for st_blksize" >&5
 $as_echo_n "checking for st_blksize... " >&6; }
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
diff --git a/src/config.h.in b/src/config.h.in
index 23e301c..c1ced6f 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -207,6 +207,7 @@
 #undef HAVE_STRNICMP
 #undef HAVE_STRPBRK
 #undef HAVE_STRTOL
+#undef HAVE_CANBERRA
 #undef HAVE_ST_BLKSIZE
 #undef HAVE_SYSCONF
 #undef HAVE_SYSCTL
diff --git a/src/configure.ac b/src/configure.ac
index 773844a..7e821e6 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -2702,6 +2702,10 @@
 	GNOME_INIT_HOOK([],fail)
 ])
 
+if test "X$PKG_CONFIG" = "X"; then
+  AC_PATH_TOOL(PKG_CONFIG, pkg-config, no)
+fi
+
 
 dnl ---------------------------------------------------------------------------
 dnl Check for GTK2.  If it fails, then continue on for Motif as before...
@@ -2717,10 +2721,6 @@
     AC_MSG_RESULT(gtk test disabled)
   fi
 
-  if test "X$PKG_CONFIG" = "X"; then
-    AC_PATH_TOOL(PKG_CONFIG, pkg-config, no)
-  fi
-
   if test "x$PKG_CONFIG" != "xno"; then
     dnl First try finding version 2.2.0 or later.  The 2.0.x series has
     dnl problems (bold fonts, --remote doesn't work).
@@ -2769,10 +2769,6 @@
     AC_MSG_RESULT(gtk test disabled)
   fi
 
-  if test "X$PKG_CONFIG" = "X"; then
-    AC_PATH_TOOL(PKG_CONFIG, pkg-config, no)
-  fi
-
   if test "x$PKG_CONFIG" != "xno"; then
     AM_PATH_GTK(3.0.0,
 		[GUI_LIB_LOC="$GTK_LIBDIR"
@@ -3755,6 +3751,29 @@
 dnl appropriate, so that off_t is 64 bits when needed.
 AC_SYS_LARGEFILE
 
+
+if test "x$PKG_CONFIG" != "xno"; then
+  canberra_lib=`$PKG_CONFIG --libs libcanberra 2>/dev/null`
+  canberra_cflags=`$PKG_CONFIG --cflags libcanberra 2>/dev/null`
+fi
+if test "x$canberra_lib" = "x"; then
+  canberra_lib=-lcanberra
+  canberra_cflags=-D_REENTRANT
+fi
+AC_MSG_CHECKING(for libcanberra)
+ac_save_CFLAGS="$CFLAGS"
+ac_save_LIBS="$LIBS"
+CFLAGS="$CFLAGS $canberra_cflags"
+LIBS="$LIBS $canberra_lib"
+AC_TRY_LINK([
+# include <canberra.h>
+    ], [
+   ca_context *hello;
+   ca_context_create(&hello);],
+     AC_MSG_RESULT(yes); AC_DEFINE(HAVE_CANBERRA),
+     AC_MSG_RESULT(no); CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS")
+
+
 dnl fstatfs() can take 2 to 4 arguments, try to use st_blksize if possible
 AC_MSG_CHECKING(for st_blksize)
 AC_TRY_COMPILE(
diff --git a/src/evalfunc.c b/src/evalfunc.c
index bc60567..4297379 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -925,6 +925,12 @@
     {"sinh",		1, 1, f_sinh},
 #endif
     {"sort",		1, 3, f_sort},
+#ifdef FEAT_SOUND
+    {"sound_playevent",	1, 2, f_sound_playevent},
+    {"sound_playfile",	1, 2, f_sound_playfile},
+    {"sound_stop",	1, 1, f_sound_stop},
+    {"sound_stopall",	0, 0, f_sound_stopall},
+#endif
     {"soundfold",	1, 1, f_soundfold},
     {"spellbadword",	0, 1, f_spellbadword},
     {"spellsuggest",	1, 3, f_spellsuggest},
@@ -6782,6 +6788,9 @@
 #ifdef FEAT_NETBEANS_INTG
 	"netbeans_intg",
 #endif
+#ifdef FEAT_SOUND
+	"sound",
+#endif
 #ifdef FEAT_SPELL
 	"spell",
 #endif
diff --git a/src/feature.h b/src/feature.h
index c613c4b..c5d7d77 100644
--- a/src/feature.h
+++ b/src/feature.h
@@ -660,6 +660,13 @@
 # define FEAT_TERM_POPUP_MENU
 #endif
 
+/*
+ * sound - currently only with libcanberra
+ */
+#if !defined(FEAT_SOUND) && defined(FEAT_BIG) && defined(HAVE_CANBERRA)
+# define FEAT_SOUND
+#endif
+
 /* There are two ways to use XPM. */
 #if (defined(HAVE_XM_XPMP_H) && defined(FEAT_GUI_MOTIF)) \
 		|| defined(HAVE_X11_XPM_H)
diff --git a/src/proto.h b/src/proto.h
index e606f08..264cbb9 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -183,6 +183,7 @@
 # ifdef FEAT_SIGNS
 #  include "sign.pro"
 # endif
+# include "sound.pro"
 # include "spell.pro"
 # include "spellfile.pro"
 # include "syntax.pro"
diff --git a/src/proto/sound.pro b/src/proto/sound.pro
new file mode 100644
index 0000000..43e4727
--- /dev/null
+++ b/src/proto/sound.pro
@@ -0,0 +1,7 @@
+/* sound.c */
+void f_sound_playevent(typval_T *argvars, typval_T *rettv);
+void f_sound_playfile(typval_T *argvars, typval_T *rettv);
+void f_sound_stop(typval_T *argvars, typval_T *rettv);
+void f_sound_stopall(typval_T *argvars, typval_T *rettv);
+void sound_free(void);
+/* vim: set ft=c : */
diff --git a/src/sound.c b/src/sound.c
new file mode 100644
index 0000000..ac6c377
--- /dev/null
+++ b/src/sound.c
@@ -0,0 +1,193 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * sound.c: functions related making noise
+ */
+
+#include "vim.h"
+
+#if (defined(FEAT_SOUND) && defined(HAVE_CANBERRA)) || defined(PROTO)
+
+#include <canberra.h>
+
+static long	    sound_id = 0;
+static ca_context   *context = NULL;
+
+typedef struct soundcb_S soundcb_T;
+
+struct soundcb_S {
+    callback_T	snd_callback;
+    soundcb_T	*snd_next;
+};
+
+static soundcb_T    *first_callback = NULL;
+
+    static soundcb_T *
+get_sound_callback(typval_T *arg)
+{
+    callback_T	callback;
+    soundcb_T	*soundcb;
+
+    if (arg->v_type == VAR_UNKNOWN)
+	return NULL;
+    callback = get_callback(arg);
+    if (callback.cb_name == NULL)
+	return NULL;
+
+    soundcb = ALLOC_ONE(soundcb_T);
+    if (soundcb == NULL)
+	free_callback(&callback);
+    else
+    {
+	soundcb->snd_next = first_callback;
+	first_callback = soundcb;
+	set_callback(&soundcb->snd_callback, &callback);
+    }
+    return soundcb;
+}
+
+/*
+ * Delete "soundcb" from the list of pending callbacks.
+ */
+    static void
+delete_sound_callback(soundcb_T *soundcb)
+{
+    soundcb_T	*p;
+    soundcb_T	*prev = NULL;
+
+    for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
+	if (p == soundcb)
+	{
+	    if (prev == NULL)
+		first_callback = p->snd_next;
+	    else
+		prev->snd_next = p->snd_next;
+	    free_callback(&p->snd_callback);
+	    vim_free(p);
+	    break;
+	}
+}
+
+    static void
+sound_callback(
+	ca_context  *c UNUSED,
+	uint32_t    id,
+	int	    error_code,
+	void	    *userdata)
+{
+    soundcb_T	*soundcb = (soundcb_T *)userdata;
+    typval_T	argv[3];
+    typval_T	rettv;
+    int		dummy;
+
+    argv[0].v_type = VAR_NUMBER;
+    argv[0].vval.v_number = id;
+    argv[1].v_type = VAR_NUMBER;
+    argv[1].vval.v_number = error_code == CA_SUCCESS ? 0
+			  : error_code == CA_ERROR_CANCELED
+					    || error_code == CA_ERROR_DESTROYED
+			  ? 1 : 2;
+    argv[2].v_type = VAR_UNKNOWN;
+
+    call_callback(&soundcb->snd_callback, -1,
+			    &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL);
+    clear_tv(&rettv);
+
+    delete_sound_callback(soundcb);
+    redraw_after_callback(TRUE);
+}
+
+    static void
+sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
+{
+    if (context == NULL)
+	ca_context_create(&context);
+    if (context != NULL)
+    {
+	soundcb_T	*soundcb = get_sound_callback(&argvars[1]);
+	int		res = CA_ERROR_INVALID;
+
+	++sound_id;
+	if (soundcb == NULL)
+	{
+	    res = ca_context_play(context, sound_id,
+		    playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
+						    tv_get_string(&argvars[0]),
+		    CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
+		    NULL);
+	}
+	else
+	{
+	    static ca_proplist *proplist = NULL;
+
+	    ca_proplist_create(&proplist);
+	    if (proplist != NULL)
+	    {
+		if (playfile)
+		    ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
+					   (char *)tv_get_string(&argvars[0]));
+		else
+		    ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
+					   (char *)tv_get_string(&argvars[0]));
+		ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
+			"volatile");
+		res = ca_context_play_full(context, sound_id, proplist,
+						      sound_callback, soundcb);
+		if (res != CA_SUCCESS)
+		    delete_sound_callback(soundcb);
+
+		ca_proplist_destroy(proplist);
+	    }
+	}
+	rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
+    }
+}
+
+    void
+f_sound_playevent(typval_T *argvars, typval_T *rettv)
+{
+    sound_play_common(argvars, rettv, FALSE);
+}
+
+    void
+f_sound_playfile(typval_T *argvars, typval_T *rettv)
+{
+    sound_play_common(argvars, rettv, TRUE);
+}
+
+    void
+f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    if (context != NULL)
+	ca_context_cancel(context, tv_get_number(&argvars[0]));
+}
+
+    void
+f_sound_stopall(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+    if (context != NULL)
+    {
+	ca_context_destroy(context);
+	context = NULL;
+    }
+}
+
+#if defined(EXITFREE) || defined(PROTO)
+    void
+sound_free(void)
+{
+    if (context != NULL)
+	ca_context_destroy(context);
+    while (first_callback != NULL)
+	delete_sound_callback(first_callback);
+}
+#endif
+
+#endif  // FEAT_SOUND && HAVE_CANBERRA
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 34644d6..08f6c5b 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -228,6 +228,7 @@
 	test_signs \
 	test_smartindent \
 	test_sort \
+	test_sound \
 	test_source \
 	test_source_utf8 \
 	test_spell \
@@ -399,6 +400,7 @@
 	test_signals.res \
 	test_signs.res \
 	test_smartindent.res \
+	test_sound.res \
 	test_source.res \
 	test_spell.res \
 	test_startup.res \
diff --git a/src/testdir/silent.wav b/src/testdir/silent.wav
new file mode 100644
index 0000000..4631a7e
--- /dev/null
+++ b/src/testdir/silent.wav
Binary files differ
diff --git a/src/testdir/test_sound.vim b/src/testdir/test_sound.vim
new file mode 100644
index 0000000..54c6a29
--- /dev/null
+++ b/src/testdir/test_sound.vim
@@ -0,0 +1,45 @@
+" Tests for the sound feature
+
+if !has('sound')
+  throw 'Skipped: sound feature not available'
+endif
+
+func PlayCallback(id, result)
+  let g:id = a:id
+  let g:result = a:result
+endfunc
+
+func Test_play_event()
+  let id = sound_playevent('bell', 'PlayCallback')
+  if id == 0
+    throw 'Skipped: bell event not available'
+  endif
+  " Stop it quickly, avoid annoying the user.
+  sleep 20m
+  call sound_stop(id)
+  sleep 20m
+  call assert_equal(id, g:id)
+  call assert_equal(1, g:result)  " sound was aborted
+endfunc
+
+func Test_play_silent()
+  let fname = fnamemodify('silent.wav', '%p')
+
+  " play without callback
+  let id1 = sound_playfile(fname)
+  call assert_true(id1 > 0)
+
+  " play until the end
+  let id2 = sound_playfile(fname, 'PlayCallback')
+  call assert_true(id2 > 0)
+  sleep 500m
+  call assert_equal(id2, g:id)
+  call assert_equal(0, g:result)
+
+  let id2 = sound_playfile(fname, 'PlayCallback')
+  call assert_true(id2 > 0)
+  sleep 20m
+  call sound_stopall()
+  call assert_equal(id2, g:id)
+  call assert_equal(1, g:result)
+endfunc
diff --git a/src/version.c b/src/version.c
index 3dcf0c6..5a69bef 100644
--- a/src/version.c
+++ b/src/version.c
@@ -580,6 +580,16 @@
 #else
 	"-smartindent",
 #endif
+#ifdef FEAT_SOUND
+	"+sound",
+#else
+	"-sound",
+#endif
+#ifdef FEAT_SPELL
+	"+spell",
+#else
+	"-spell",
+#endif
 #ifdef STARTUPTIME
 	"+startuptime",
 #else
@@ -768,6 +778,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1502,
+/**/
     1501,
 /**/
     1500,