Skip to content
Browse files
Enhanced sound support (PR #450)
This fork features some enhancements for the sound system. Although my
plans initially were more ambitious, this will do for now.

Adds sound_file_path option, making the current sound option much easier
to use.

Moved all the sound code to its own set of files for future expansion (I
have some plans). Also, makes it painless to fiddle around with
PLAY_SOUND_COMMAND, which previously was in AppHdr.h despite being used
in just one function.

(UPDATE) Adds multiple cues that can be used in place of typical regular
expressions. Some examples are PICKUP, MEMORISE_SPELL, and CHANGE
QUIVER. See sound.h for a full list. I intend of expanding this.

Very incomplete support for a hold_sound option, which is supposed to
pause the game while the sound is playing. This pause is intended to be
sequential, delaying the rest of the message until the sound finishes. I
have two purposes for this: giving battles a real-time feel, and to
highlight those epic victories. This feature doesn't work as intended,
so I dropped the in-game documentation for now.

(UPDATE) Adds one_SDL_sound_channel. Originally, sound behavior depended
on whether you were playing with SDL or using the SOUND_PLAY_COMMAND
define; if playing with SDL/Tiles, only one sound would play at once,
whereas SOUND_PLAY_COMMAND allowed as many sounds as were necessary. By
default, one_SDL_sound_channel is set to false, making it behave as
SOUND_PLAY_COMMAND. However, players can restore the old behavior by
setting this option to true.

(UPDATE) The version screen now tells the user how sounds will be
played. (i.e. "Windows Multimedia API," "External command," or

Anyway, I know this works as intended on Linux and Cygwin, and I did run
crawl -test to double check stuff. UPDATE: I have now verified that it
works with Tiles! However, I can't test with Windows, Mac, etc. I may or
may not have broken the sound stuff. At worst, some more files need to
be included in

Finally, the game works as normal when compiled without sound support. I
only tested this in Linux, but I'm certain that it should be the same
everywhere else.

Happy crawling!
  • Loading branch information
Autofire authored and neilmoore committed Feb 27, 2017
1 parent f3a60fd commit 4270d38
Show file tree
Hide file tree
Showing 25 changed files with 341 additions and 78 deletions.
@@ -89,8 +89,8 @@ changes to the level compiler, you'll need the flex and bison/byacc tools
(Other lex/yaccs may also work). More details are available below.

Sound can be provided by SDL2_mixer (the default), by WINMM on Windows (defined
by WINMM_PLAY_SOUNDS in AppHdr.h), or by an external command on Unix systems
(SOUND_PLAY_COMMAND in AppHdr.h). To enable this, append SOUND=y to the make
by WINMM_PLAY_SOUNDS in sound.h), or by an external command on Unix systems
(SOUND_PLAY_COMMAND in sound.h). To enable this, append SOUND=y to the make

Crawl includes Lua 5.1.4 in its submodules. Crawl uses Lua for dungeon
@@ -111,6 +111,7 @@ Meta-commands
<unbound> CMD_TOGGLE_SOUND (only when compiled with sound)
@@ -19,7 +19,8 @@ The contents of this text are:
restart_after_game, restart_after_save,
default_manual_training, autopickup_starting_ammo
2- File System and Sound.
crawl_dir, morgue_dir, save_dir, macro_dir, sound
crawl_dir, morgue_dir, save_dir, macro_dir, sound, hold_sound,
3- Interface.
3-a Dropping and Picking up.
autopickup, autopickup_exceptions, default_autopickup,
@@ -397,10 +398,36 @@ macro_dir = settings/
It should end with the path delimiter.

sound ^= <regex>:<path to sound file>, <regex>:<path>, ...
(Requires "Sound support"; check your version info)
(Ordered list option)
Plays the sound file if a message contains regex. The regex
should not include commas or colons. For example
sound += LOW HITPOINT WARNING:sound\sounds2\danger3.wav
There are certain pre-defined regexes, as well, which can be
used to get a sound to trigger for something that cannot
otherwise be matched using a regex string. For example,
using FIRE_PROMPT_SOUND for the regex will cause Crawl to
play a sound whenever the fire prompt is opened. Check
sound.h for a full listing of all these pre-defined regex

sound_file_path = <path>
When playing sounds (with the option above), the contents of
this variable is appended to the start of the path. This is
intended to make it easier to take one configuration across
several platforms. These two lines are equivalent to the
example above:
sound_file_path = sound\sounds2\
sound += LOW HITPOINT WARNING:danger3.wav
The most recent instance of this option is the one that
takes effect, and is not applied retroactively to previous
instances of the sound option.

one_SDL_sound_channel = false
When true, only one sound can play at a time (if using SDL).
Is false by default since non-SDL sound backends don't
support such control.

3- Interface.
@@ -148,12 +148,6 @@ static inline double pow(int x, double y) { return std::pow((double)x, y); }
// uncomment the line below and add -lpcre to your makefile.
// #define REGEX_PCRE

// Uncomment (and edit as appropriate) to play sounds.
// WARNING: Filenames passed to this command *are not validated in any way*.
// #define SOUND_PLAY_COMMAND "/usr/bin/play -v .5 \"%s\" 2>/dev/null &"

#include "libunix.h"

#elif defined(TARGET_OS_WINDOWS)
@@ -172,9 +166,6 @@ static inline double pow(int x, double y) { return std::pow((double)x, y); }
#define FILE_SEPARATOR '/'

// Uncomment to play sounds. winmm must be linked in if this is uncommented.

// Use Perl-compatible regular expressions. libpcre must be available and
// linked in. Required in the absence of POSIX regexes.
#ifndef REGEX_PCRE
@@ -468,6 +468,7 @@ perl.exe "util/" compflag.h "&lt;UNKNOWN&gt;" "&lt;UNKNOWN&gt;"
<ClCompile Include="..\" />
<ClCompile Include="..\" />
<ClCompile Include="..\" />
<ClCompile Include="..\" />
<ClCompile Include="..\" />
<ClCompile Include="..\" />
<ClCompile Include="..\" />
@@ -18,7 +18,8 @@
# Typical parameters:
# TILES -- set to anything to enable tiles build
# SOUND -- set to anything to enable sound
# SOUND -- set to anything to enable sound; note that you will need to
# uncomment some lines in sound.h if not building tiles
# CROSSHOST -- target system, eg, i386-pc-msdosdjgpp or i586-mingw32msvc
@@ -202,6 +202,7 @@ show.o \
showsymb.o \
skill-menu.o \
skills.o \
sound.o \
species.o \
spl-book.o \
spl-cast.o \
@@ -222,6 +222,7 @@ LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
@@ -51,6 +51,7 @@ enum command_type
@@ -27,6 +27,7 @@
#include "output.h"
#include "prompt.h"
#include "showsymb.h"
#include "sound.h"
#include "state.h"
#include "stringutil.h"
#include "syscalls.h"
@@ -65,8 +66,8 @@ static const char *features[] =
"Glob patterns",

"Sound support",
#if defined(USE_SOUND) && defined(SOUND_BACKEND)

@@ -1026,6 +1027,9 @@ static void _add_formatted_keyhelp(column_composer &cols)
_add_command(cols, 1, CMD_SHOW_TERRAIN, "toggle view layers");
_add_command(cols, 1, CMD_DISPLAY_OVERMAP, "show dungeon Overview");
_add_command(cols, 1, CMD_TOGGLE_AUTOPICKUP, "toggle auto-pickup");
#ifdef USE_SOUND
_add_command(cols, 1, CMD_TOGGLE_SOUND, "mute/unmute sound effects");
_add_command(cols, 1, CMD_TOGGLE_TRAVEL_SPEED, "set your travel speed to your");
cols.add_formatted(1, " slowest ally\n",
@@ -57,6 +57,7 @@
#include "religion.h"
#include "rot.h"
#include "shout.h"
#include "sound.h"
#include "spl-other.h"
#include "spl-selfench.h"
#include "spl-util.h"
@@ -724,6 +725,9 @@ void JewelleryOnDelay::finish()

#ifdef USE_SOUND
puton_ring(, false);

@@ -737,6 +741,9 @@ void ArmourOnDelay::finish()

const equipment_type eq_slot = get_armour_slot(armour);

#ifdef USE_SOUND
mprf("You finish putting on %s.",;

if (eq_slot == EQ_BODY_ARMOUR)
@@ -758,12 +765,18 @@ void ArmourOffDelay::finish()
const equipment_type slot = get_armour_slot(armour);
ASSERT(you.equip[slot] ==;

#ifdef USE_SOUND
mprf("You finish taking off %s.",;

void MemoriseDelay::finish()
#ifdef USE_SOUND
mpr("You finish memorising.");
@@ -213,6 +213,8 @@ const vector<GameOption*> game_options::build_options_list()
new BoolGameOption(SIMPLE_NAME(arena_dump_msgs_all), false),
new BoolGameOption(SIMPLE_NAME(arena_list_eq), false),
new BoolGameOption(SIMPLE_NAME(default_manual_training), false),
new BoolGameOption(SIMPLE_NAME(one_SDL_sound_channel), false),
new BoolGameOption(SIMPLE_NAME(sounds_on), true),
new ColourGameOption(SIMPLE_NAME(tc_reachable), BLUE),
new ColourGameOption(SIMPLE_NAME(tc_excluded), LIGHTMAGENTA),
new ColourGameOption(SIMPLE_NAME(tc_exclude_circle), RED),
@@ -281,6 +283,7 @@ const vector<GameOption*> game_options::build_options_list()
"50:yellow, 25:red", _first_greater),
new ColourThresholdOption(stat_colour, {"stat_colour", "stat_color"},
"3:red", _first_less),
new StringGameOption(SIMPLE_NAME(sound_file_path), ""),
new BoolGameOption(SIMPLE_NAME(messaging), false),
@@ -2492,7 +2495,7 @@ void game_options::read_option_line(const string &str, bool runscript)
&& key != "race" && key != "class" && key != "ban_pickup"
&& key != "autopickup_exceptions"
&& key != "explore_stop_pickup_ignore"
&& key != "stop_travel" && key != "sound"
&& key != "stop_travel"
&& key != "force_more_message"
&& key != "flash_screen_message"
&& key != "confirm_action"
@@ -2511,6 +2514,7 @@ void game_options::read_option_line(const string &str, bool runscript)
&& key != "spell_slot"
&& key != "item_slot"
&& key != "ability_slot"
&& key != "sound" && key != "hold_sound" && key != "sound_file_path"
&& key.find("font") == string::npos)
@@ -3127,7 +3131,7 @@ void game_options::read_option_line(const string &str, bool runscript)
explore_stop |= new_conditions;
else if (key == "sound")
else if (key == "sound" || key == "hold_sound")
if (plain)
@@ -3140,7 +3144,12 @@ void game_options::read_option_line(const string &str, bool runscript)
sound_mapping entry;
entry.pattern = sub.substr(0, cpos);
entry.soundfile = sub.substr(cpos + 1);
entry.soundfile = sound_file_path + sub.substr(cpos + 1);
if (key == "hold_sound")
entry.interrupt_game = true;
entry.interrupt_game = false;

if (minus_equal)
remove_matching(sound_mappings, entry);
@@ -55,6 +55,7 @@
#include "rot.h"
#include "shout.h"
#include "skills.h"
#include "sound.h"
#include "spl-book.h"
#include "spl-clouds.h"
#include "spl-goditem.h"
@@ -545,7 +546,12 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages,
return false;

if (show_unwield_msg)
#ifdef USE_SOUND

// Switching to bare hands is extra fast.
you.turn_is_over = true;
@@ -598,7 +604,12 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages,
equip_item(EQ_WEAPON, item_slot, show_weff_messages);

if (show_wield_msg)
#ifdef USE_SOUND

check_item_hint(new_wpn, old_talents);

@@ -1970,6 +1981,9 @@ bool remove_ring(int slot, bool announce)
if (!_safe_to_remove_or_wear(you.inv[ring_wear_2], true))
return false;

#ifdef USE_SOUND
mprf("You remove %s.", you.inv[ring_wear_2].name(DESC_YOUR).c_str());
const unsigned int old_talents = your_talents(false).size();
@@ -71,6 +71,7 @@
#include "shopping.h"
#include "showsymb.h"
#include "slot-select-mode.h"
#include "sound.h"
#include "spl-book.h"
#include "spl-util.h"
#include "stash.h"
@@ -1912,6 +1913,9 @@ static bool _merge_stackable_item_into_inv(const item_def &it, int quant_got,

if (!quiet)
#ifdef USE_SOUND
mprf_nocap("%s (gained %d)",
@@ -2047,7 +2051,12 @@ static int _place_item_in_free_slot(item_def &it, int quant_got,
if (const item_def* newitem = auto_assign_item_slot(item))
return newitem->link;
else if (!quiet)
#ifdef USE_SOUND
mprf_nocap("%s", menu_colour_item_name(item, DESC_INVENTORY).c_str());

@@ -33,6 +33,7 @@ module "crawl"
#include "perlin.h"
#include "prompt.h"
#include "religion.h"
#include "sound.h"
#include "state.h"
#include "state.h"
#include "stringutil.h"

0 comments on commit 4270d38

Please sign in to comment.