Add support for the ':data' keyword for play-sound in MS-Windows.

* src/sound.c (parse_sound) [WINDOWSNT]: Check that either :file
or :data is present.
(do_play_sound): Added parameter to select file or data, and code to
play from data.
(Fplay_sound_internal): Fixed volume format, and send file or data
to 'do_play_sound'.  (Bug#74863)
* etc/NEWS: Add entry for this change.
* etc/PROBLEMS: Remove entry about missing support for :data.
This commit is contained in:
Cecilio Pardo 2024-12-15 01:13:16 +01:00 committed by Eli Zaretskii
parent b6852b67b0
commit fd529bbd07
3 changed files with 127 additions and 125 deletions

View file

@ -1111,6 +1111,11 @@ current buffer, if the major mode supports it. (Support for
Transformed images are smoothed using the bilinear interpolation by
means of the GDI+ library.
---
** Emacs on MS-Windows now supports the ':data' keyword for 'play-sound'.
In addition to ':file FILE' for playing a sound from a file, ':data
DATA' can now be used to play a sound from memory.
----------------------------------------------------------------------
This file is part of GNU Emacs.

View file

@ -3278,11 +3278,6 @@ Files larger than 4GB cause overflow in the size (represented as a
well, since the Windows port uses a Lisp emulation of 'ls', which relies
on 'file-attributes'.
** Playing sound doesn't support the :data method
Sound playing is not supported with the ':data DATA' key-value pair.
You _must_ use the ':file FILE' method.
** Typing Alt-Shift has strange effects on MS-Windows.
This combination of keys is a command to change keyboard layout. If

View file

@ -26,14 +26,12 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
implementation of the play-sound specification for Windows.
Notes:
In the Windows implementation of play-sound-internal only the
:file and :volume keywords are supported. The :device keyword,
if present, is ignored. The :data keyword, if present, will
cause an error to be generated.
In the Windows implementation of play-sound-internal the :device
keyword, if present, is ignored.
The Windows implementation of play-sound is implemented via the
Windows API functions mciSendString, waveOutGetVolume, and
waveOutSetVolume which are exported by Winmm.dll.
Windows API functions mciSendString, waveOutGetVolume,
waveOutSetVolume and PlaySound which are exported by Winmm.dll.
*/
#include <config.h>
@ -91,6 +89,11 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "w32.h"
/* END: Windows Specific Includes */
/* Missing in mingw32. */
#ifndef SND_SENTRY
#define SND_SENTRY 0x00080000
#endif
#endif /* WINDOWSNT */
/* BEGIN: Common Definitions */
@ -278,7 +281,7 @@ static void au_play (struct sound *, struct sound_device *);
#else /* WINDOWSNT */
/* BEGIN: Windows Specific Definitions */
static int do_play_sound (const char *, unsigned long);
static int do_play_sound (const char *, unsigned long, bool);
/*
END: Windows Specific Definitions */
#endif /* WINDOWSNT */
@ -366,21 +369,10 @@ parse_sound (Lisp_Object sound, Lisp_Object *attrs)
attrs[SOUND_DEVICE] = plist_get (sound, QCdevice);
attrs[SOUND_VOLUME] = plist_get (sound, QCvolume);
#ifndef WINDOWSNT
/* File name or data must be specified. */
if (!STRINGP (attrs[SOUND_FILE])
&& !STRINGP (attrs[SOUND_DATA]))
return 0;
#else /* WINDOWSNT */
/*
Data is not supported in Windows. Therefore a
File name MUST be supplied.
*/
if (!STRINGP (attrs[SOUND_FILE]))
{
return 0;
}
#endif /* WINDOWSNT */
/* Volume must be in the range 0..100 or unspecified. */
if (!NILP (attrs[SOUND_VOLUME]))
@ -1225,7 +1217,7 @@ alsa_init (struct sound_device *sd)
} while (0)
static int
do_play_sound (const char *psz_file, unsigned long ui_volume)
do_play_sound (const char *psz_file_or_data, unsigned long ui_volume, bool in_memory)
{
int i_result = 0;
MCIERROR mci_error = 0;
@ -1236,65 +1228,7 @@ do_play_sound (const char *psz_file, unsigned long ui_volume)
BOOL b_reset_volume = FALSE;
char warn_text[560];
/* Since UNICOWS.DLL includes only a stub for mciSendStringW, we
need to encode the file in the ANSI codepage on Windows 9X even
if w32_unicode_filenames is non-zero. */
if (w32_major_version <= 4 || !w32_unicode_filenames)
{
char fname_a[MAX_PATH], shortname[MAX_PATH], *fname_to_use;
filename_to_ansi (psz_file, fname_a);
fname_to_use = fname_a;
/* If the file name is not encodable in ANSI, try its short 8+3
alias. This will only work if w32_unicode_filenames is
non-zero. */
if (_mbspbrk ((const unsigned char *)fname_a,
(const unsigned char *)"?"))
{
if (w32_get_short_filename (psz_file, shortname, MAX_PATH))
fname_to_use = shortname;
else
mci_error = MCIERR_FILE_NOT_FOUND;
}
if (!mci_error)
{
memset (sz_cmd_buf_a, 0, sizeof (sz_cmd_buf_a));
memset (sz_ret_buf_a, 0, sizeof (sz_ret_buf_a));
sprintf (sz_cmd_buf_a,
"open \"%s\" alias GNUEmacs_PlaySound_Device wait",
fname_to_use);
mci_error = mciSendStringA (sz_cmd_buf_a,
sz_ret_buf_a, sizeof (sz_ret_buf_a), NULL);
}
}
else
{
wchar_t sz_cmd_buf_w[520];
wchar_t sz_ret_buf_w[520];
wchar_t fname_w[MAX_PATH];
filename_to_utf16 (psz_file, fname_w);
memset (sz_cmd_buf_w, 0, sizeof (sz_cmd_buf_w));
memset (sz_ret_buf_w, 0, sizeof (sz_ret_buf_w));
/* _swprintf is not available on Windows 9X, so we construct the
UTF-16 command string by hand. */
wcscpy (sz_cmd_buf_w, L"open \"");
wcscat (sz_cmd_buf_w, fname_w);
wcscat (sz_cmd_buf_w, L"\" alias GNUEmacs_PlaySound_Device wait");
mci_error = mciSendStringW (sz_cmd_buf_w,
sz_ret_buf_w, ARRAYELTS (sz_ret_buf_w) , NULL);
}
if (mci_error != 0)
{
strcpy (warn_text,
"mciSendString: 'open' command failed to open sound file ");
strcat (warn_text, psz_file);
SOUND_WARNING (mciGetErrorString, mci_error, warn_text);
i_result = (int) mci_error;
return i_result;
}
if ((ui_volume > 0) && (ui_volume != UINT_MAX))
if (ui_volume > 0)
{
mm_result = waveOutGetVolume ((HWAVEOUT) WAVE_MAPPER, &ui_volume_org);
if (mm_result == MMSYSERR_NOERROR)
@ -1319,34 +1253,105 @@ do_play_sound (const char *psz_file, unsigned long ui_volume)
" not be used.");
}
}
memset (sz_cmd_buf_a, 0, sizeof (sz_cmd_buf_a));
memset (sz_ret_buf_a, 0, sizeof (sz_ret_buf_a));
strcpy (sz_cmd_buf_a, "play GNUEmacs_PlaySound_Device wait");
mci_error = mciSendStringA (sz_cmd_buf_a, sz_ret_buf_a, sizeof (sz_ret_buf_a),
NULL);
if (mci_error != 0)
if (in_memory)
{
strcpy (warn_text,
"mciSendString: 'play' command failed to play sound file ");
strcat (warn_text, psz_file);
SOUND_WARNING (mciGetErrorString, mci_error, warn_text);
i_result = (int) mci_error;
int flags = SND_MEMORY;
if (w32_major_version >= 6) /* Vista and later */
flags |= SND_SENTRY;
i_result = !PlaySound (psz_file_or_data, NULL, flags);
}
memset (sz_cmd_buf_a, 0, sizeof (sz_cmd_buf_a));
memset (sz_ret_buf_a, 0, sizeof (sz_ret_buf_a));
strcpy (sz_cmd_buf_a, "close GNUEmacs_PlaySound_Device wait");
mci_error = mciSendStringA (sz_cmd_buf_a, sz_ret_buf_a, sizeof (sz_ret_buf_a),
NULL);
else
{
/* Since UNICOWS.DLL includes only a stub for mciSendStringW, we
need to encode the file in the ANSI codepage on Windows 9X even
if w32_unicode_filenames is non-zero. */
if (w32_major_version <= 4 || !w32_unicode_filenames)
{
char fname_a[MAX_PATH], shortname[MAX_PATH], *fname_to_use;
filename_to_ansi (psz_file_or_data, fname_a);
fname_to_use = fname_a;
/* If the file name is not encodable in ANSI, try its short 8+3
alias. This will only work if w32_unicode_filenames is
non-zero. */
if (_mbspbrk ((const unsigned char *)fname_a,
(const unsigned char *)"?"))
{
if (w32_get_short_filename (psz_file_or_data, shortname, MAX_PATH))
fname_to_use = shortname;
else
mci_error = MCIERR_FILE_NOT_FOUND;
}
if (!mci_error)
{
memset (sz_cmd_buf_a, 0, sizeof (sz_cmd_buf_a));
memset (sz_ret_buf_a, 0, sizeof (sz_ret_buf_a));
sprintf (sz_cmd_buf_a,
"open \"%s\" alias GNUEmacs_PlaySound_Device wait",
fname_to_use);
mci_error = mciSendStringA (sz_cmd_buf_a,
sz_ret_buf_a, sizeof (sz_ret_buf_a), NULL);
}
}
else
{
wchar_t sz_cmd_buf_w[520];
wchar_t sz_ret_buf_w[520];
wchar_t fname_w[MAX_PATH];
filename_to_utf16 (psz_file_or_data, fname_w);
memset (sz_cmd_buf_w, 0, sizeof (sz_cmd_buf_w));
memset (sz_ret_buf_w, 0, sizeof (sz_ret_buf_w));
/* _swprintf is not available on Windows 9X, so we construct the
UTF-16 command string by hand. */
wcscpy (sz_cmd_buf_w, L"open \"");
wcscat (sz_cmd_buf_w, fname_w);
wcscat (sz_cmd_buf_w, L"\" alias GNUEmacs_PlaySound_Device wait");
mci_error = mciSendStringW (sz_cmd_buf_w,
sz_ret_buf_w, ARRAYELTS (sz_ret_buf_w) , NULL);
}
if (mci_error != 0)
{
strcpy (warn_text,
"mciSendString: 'open' command failed to open sound file ");
strcat (warn_text, psz_file_or_data);
SOUND_WARNING (mciGetErrorString, mci_error, warn_text);
i_result = (int) mci_error;
return i_result;
}
memset (sz_cmd_buf_a, 0, sizeof (sz_cmd_buf_a));
memset (sz_ret_buf_a, 0, sizeof (sz_ret_buf_a));
strcpy (sz_cmd_buf_a, "play GNUEmacs_PlaySound_Device wait");
mci_error = mciSendStringA (sz_cmd_buf_a, sz_ret_buf_a, sizeof (sz_ret_buf_a),
NULL);
if (mci_error != 0)
{
strcpy (warn_text,
"mciSendString: 'play' command failed to play sound file ");
strcat (warn_text, psz_file_or_data);
SOUND_WARNING (mciGetErrorString, mci_error, warn_text);
i_result = (int) mci_error;
}
memset (sz_cmd_buf_a, 0, sizeof (sz_cmd_buf_a));
memset (sz_ret_buf_a, 0, sizeof (sz_ret_buf_a));
strcpy (sz_cmd_buf_a, "close GNUEmacs_PlaySound_Device wait");
mci_error = mciSendStringA (sz_cmd_buf_a, sz_ret_buf_a, sizeof (sz_ret_buf_a),
NULL);
}
if (b_reset_volume == TRUE)
{
mm_result = waveOutSetVolume ((HWAVEOUT) WAVE_MAPPER, ui_volume_org);
if (mm_result != MMSYSERR_NOERROR)
{
SOUND_WARNING (waveOutGetErrorText, mm_result,
{
SOUND_WARNING (waveOutGetErrorText, mm_result,
"waveOutSetVolume: failed to reset the original"
" volume level of the WAVE_MAPPER device.");
}
" volume level of the WAVE_MAPPER device.");
}
}
return i_result;
}
@ -1364,8 +1369,7 @@ Internal use only, use `play-sound' instead. */)
specpdl_ref count = SPECPDL_INDEX ();
#ifdef WINDOWSNT
unsigned long ui_volume_tmp = UINT_MAX;
unsigned long ui_volume = UINT_MAX;
unsigned long ui_volume = 0;
#endif /* WINDOWSNT */
/* Parse the sound specification. Give up if it is invalid. */
@ -1432,33 +1436,31 @@ Internal use only, use `play-sound' instead. */)
#else /* WINDOWSNT */
file = Fexpand_file_name (attrs[SOUND_FILE], Vdata_directory);
file = ENCODE_FILE (file);
if (FIXNUMP (attrs[SOUND_VOLUME]))
{
ui_volume_tmp = XFIXNAT (attrs[SOUND_VOLUME]);
}
ui_volume = XFIXNAT (attrs[SOUND_VOLUME]);
else if (FLOATP (attrs[SOUND_VOLUME]))
{
ui_volume_tmp = XFLOAT_DATA (attrs[SOUND_VOLUME]) * 100;
}
ui_volume = XFLOAT_DATA (attrs[SOUND_VOLUME]) * 100;
if (ui_volume > 100)
ui_volume = 100;
/* For volume (32 bits), low order 16 bits are the value for left
channel, and high order 16 bits for the right channel. We use the
specified volume on both channels. */
ui_volume = ui_volume * 0xFFFF / 100;
ui_volume = (ui_volume << 16) + ui_volume;
CALLN (Frun_hook_with_args, Qplay_sound_functions, sound);
/*
Based on some experiments I have conducted, a value of 100 or less
for the sound volume is much too low. You cannot even hear it.
A value of UINT_MAX indicates that you wish for the sound to played
at the maximum possible volume. A value of UINT_MAX/2 plays the
sound at 50% maximum volume. Therefore the value passed to do_play_sound
(and thus to waveOutSetVolume) must be some fraction of UINT_MAX.
The following code adjusts the user specified volume level appropriately.
*/
if ((ui_volume_tmp > 0) && (ui_volume_tmp <= 100))
if (STRINGP (attrs[SOUND_FILE]))
{
ui_volume = ui_volume_tmp * (UINT_MAX / 100);
file = Fexpand_file_name (attrs[SOUND_FILE], Vdata_directory);
file = ENCODE_FILE (file);
do_play_sound (SSDATA (file), ui_volume, false);
}
(void)do_play_sound (SSDATA (file), ui_volume);
else
do_play_sound (SDATA (attrs[SOUND_DATA]), ui_volume, true);
#endif /* WINDOWSNT */