1 NetHack 3.7 sound.txt $NHDT-Date: 1693253363 2023/08/28 20:09:23 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.7 $
5 This file documents the support for various sound library integration
6 interface, or just soundlib for short.
8 The soundlib support is through a standard interface, separating the
9 main NetHack code from soundlib-specific code. The implementation
10 supports multiple soundlib systems in the same binary. Even if you only
11 wish to support one soundlib, you will need to follow the instructions
12 to end up with a compiled binary
14 Copyright 2023, Michael Allison, with acknowledgement and thanks to
15 David Cohrs for window.txt that provided the inspiration for the layout
18 NetHack may be freely redistributed. See license for details.
21 I. Sound trigger types and terminology
22 II. Interface Specification
24 IV. Other related routines
25 V. Game Startup and Soundlib Activation Sequencing
27 VII. Implementation and Multiple Soundlib Support
29 I. Sound Trigger Types and Terminology
31 There are 4 distinct types of sound sound_triggers used by NetHack.
33 SOUND_TRIGGER_USERSOUNDS User-specified sounds that play based
34 on config file entries that identify a
35 regular expression to match against
36 message window text, and identify an
37 external sound file to load in response.
38 The sound interface function pointer
41 void (*sound_play_usersound)(char *filename,
42 int32_t volume, int32_t idx);
44 SOUND_TRIGGER_HEROMUSIC Invoked by the core when the in-game hero,
45 or perhaps another creature, is playing
46 a tune on an instrument. The sound interface
47 function pointer used to invoke it:
49 void (*sound_hero_playnotes)
50 (int32_t instrument, const char *str,
53 SOUND_TRIGGER_ACHIEVEMENTS Invoked by the core when an in-game
54 achievement is reached. The soundlib routines
55 could play appropriate theme or some fanfare
57 There needs to be a way to map each
58 achievement to a specific external
59 sound file or resource. The sound
60 interface function pointer used to
63 void (*sound_achievement)(schar, schar,
66 SOUND_TRIGGER_SOUNDEFFECTS Invoked by the core when something
67 sound-producing happens in the game. The
68 soundlib routines could play an appropriate
69 sound effect in response. They can be
70 public-domain or suitably licensed stock
71 sounds included with the
72 gamesource. The soundeffect must be made
73 available during the build process, or
74 (not-yet-implemented) a way to tie a
75 particular sound effect to a player-specified
76 sound samples within the player's config
77 file. The sound interface function
78 pointer used to invoke it:
80 void (*sound_soundeffect)(char *desc,
81 int32_t seid, int32_t volume);
83 SOUND_TRIGGER_AMBIENCE Invoked by the core in response to something
84 atmosphere or mood-producing or flavorful.
85 Unlike the other interface functions, this
86 one gets called to notify the sound interface
87 at the outset (ambience_begin), at the
88 termination (ambience_end), and
89 periodically in-between as needed
90 (ambience_upate), likely with a different
93 SOUND_TRIGGER_VERBAL Invoked by the core when someone (or something)
97 The types of sound sound_triggers supported by a particular soundlib
98 implementation are specified in that library's soundlib file, which is usually
99 found in sound/<library_name>/<library_name>.c (.m in the case of macound), in
100 the sound_triggers field of the sound_procs struct:
103 const char *soundname;
104 enum soundlib_ids soundlib_id;
105 unsigned long sound_triggers;
106 void (*sound_init_nhsound)(void);
107 void (*sound_exit_nhsound)(const char *reason);
108 void (*sound_achievement)(schar arg1, schar arg2, int32_t avals);
109 void (*sound_soundeffect)(char *desc, int32_t, int32_t volume);
110 void (*sound_hero_playnotes)(int32_t instrument, const char *notestr,
112 void (*sound_play_usersound)(char *filename, int32_t volume,
114 void (*sound_ambience)(int32_t ambienceid, int32_t ambience_action,
115 int32_t hero_proximity);
116 void (*sound_verbal)(char *text, int32_t gender, int32_t tone,
117 int32_t vol, int32_t moreinfo);
120 A sound library integration support file can implement one, two, three, four,
121 five, or six of the sound trigger types. The more types of sound_triggers the
122 soundlib implements, the more full-featured the sound experience will be
125 The values can be or'd together in the sound_triggers field initialization.
126 SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
127 | SOUND_TRIGGER_ACHIEVEMENTS | SOUND_TRIGGER_SOUNDEFFECTS
128 | SOUND_TRIGGER_AMBIENCE | SOUND_TRIGGER_VERBAL
131 II. Interface Specification
133 A. Integration routines:
135 All functions below are void unless otherwise noted.
137 sound_init_nhsound(void);
139 -- NetHack will call this function when it is ready to enable sound
140 library support. It will do that during start-up, and it might do
141 it if the player has chosen to switch soundlib options.
143 sound_exit_nhsound(const char *reason);
145 -- NetHack will call this function when it wants to turn off the
146 sound library support and make it inactive. It will do that
147 when the game is ending, whether because the game is over or
148 because the player has chosen to save the game. The function
149 might also get called if the player has chosen to switch soundlib
152 sound_achievement(schar arg1, schar arg2, int32_t avals);
154 -- NetHack will call this function when it wants to invoke a sound
155 based on an achievement, or an event that has occurred in the
157 -- arg1 will contain the achievement value used internal to the
158 game, and if it is non-zero then arg2 should be ignored.
159 -- avals may contain more specific information about the achievement
160 or event that has just occurred.
161 For internal achievements (non-zero arg1), it will be zero if
162 this is the first occurrence of that achievement, or it will be
163 non-zero if this is a repeat occurrence.
164 -- arg2 is only used when arg1 iz zero. arg2 contains event
165 identifiers for various events that occur during the game. The
166 identifiers are from the 'enum achievements_arg2' list in
167 include/sndprocs.h. It is recommended that you look them up
168 there as new ones get added periodically as the game development
169 continues. The identifiers all begin with 'sa2_'.
170 For arg2 events, avals may contain additional integer information
171 specific to that particular event. For other events, it may be
172 meaningless. Those relationships will also be documented in
173 include/sndprocs.h as they develop.
175 sound_soundeffect(char *desc, int32_t seid, int32_t volume);
177 -- NetHack will call this function when it wants to invoke a particular
178 sound effect during the course of a game. The calls are typically
179 hard-coded into the NetHack sources at various appropriate points,
180 and the calls typically use the Soundeffects(desc, seid, volume)
181 macro to do so. They benefit of using the macro is that it does some
182 suitable validation checks before actually invoking the soundlib
184 -- desc may hold a text description of the sound effect. Often this
185 field is not set, so the soundlib routine needs to be aware of
186 that and not depend on it holding a description.
187 -- The soundeffects identifiers (seid) are from the
188 'enum sound_effect_entries' list in include/sndprocs.h.
189 It is recommended that you look them up there as new ones get
190 added periodically as game development continues. The identifiers
191 all begin with 'se_'.
193 sound_hero_playnotes(int32_t instrument, const char *notestr, int32_t volume);
195 -- NetHack will call this function when it wants a sequence of notes
196 (A,B,C,D,E,F,G) played, because the hero in the game, or a
197 creature in the game, is playing an instrument.
198 -- instrument specifies the instrument to use. The instrument
199 identifiers are from the 'enum instruments' list in
200 include/sndprocs.h. It is recommended that you look them up
201 there as new ones get added periodically as game development
202 continues. The identifiers all begin with 'ins_'.
203 Side note: the underlying values associated with those enums
204 match the instrument values in some MIDI specifications. If
205 they match the values for instruments in the underlying sound
206 library or platform API, they may be able to be passed through.
207 -- The sequence of notes is in notestr. At this time, NetHack may
208 be capping the number of notes at 5, but it is not
209 recommended that the soundlib integration support functions
210 rely on that note count cap as a hard rule.
211 -- A soundlib integration support file that has SOUND_TRIGGER_HEROMUSIC
212 support is expected to play the sound at the volume specified
213 by the volume argument (1 - 100, representing percentage of
214 possible volume levels), if the underlying sound library supports
215 volume adjustments. If it doesn't, the volume argument would
216 just have to be ignored.
218 sound_play_usersound(char *filename, int32_t volume, int32_t usidx);
220 -- NetHack will call this function when it wants a particular
221 external sound file played, based on a regular expression match
222 that the player has defined in their config file.
223 -- A soundlib integration support file that has SOUND_TRIGGER_USERSOUNDS
224 support is expected to play the sound file specified by the filename
226 -- A soundlib integration support file that has SOUND_TRIGGER_USERSOUNDS
227 support is expected to play the sound at the volume specified
228 by the volume argument (1 - 100, representing percentage of
229 possible volume levels), if the underlying sound library supports
230 volume adjustments. If it doesn't, the volume argument would
231 just have to be ignored.
233 sound_ambience(int32_t ambienceid, int32_t ambience_action,
234 int32_t hero_proximity);
235 -- NetHack will call this function when it wants a particular
236 ambience related sound played in order to provide atomosphere
238 -- The ambienceid is used to identify the particular ambience sound
239 being sought. The abienceid identifiers are from the
240 'enum ambiences' list in include/sndprocs.h. It is recommended
241 that you look them up there as new ones get added periodically
242 as game development continues. The identifiers all begin
244 -- ambience_action. A soundlib integration support file that has
245 SOUND_TRIGGER_AMBIENCE support is expected to commence playing the
246 sound when it receives an ambience_action of ambience_begin. It
247 will receive an ambience_action of ambience_end when it should cease
248 playing the sound. It may receive an ambience_action of
249 ambience_update periodically, anytime the ambience is underway,
250 and it should respond accordingly: perhaps by adjusting the nature
251 of the sound being heard, or possibly by just adjusting the volume.
252 -- hero_proximity could be zero, in which case the ambience being
253 triggered is not impacted by the hero's distance from anything.
254 If the distance of the hero from the source of the ambience does
255 matter, then a distance value will be in hero_proximity.
257 sound_verbal(char *text, int32_t gender, int32_t tone, int32_t vol,
259 -- NetHack will call this function when it wants to pass text of
260 spoken language by a character or creature within the game.
261 -- text is a transcript of what has been spoken.
262 -- gender indicates MALE or FEMALE or NEUTRAL (either MALE
264 -- tone indicates the tone of the voice.
265 -- vol is the volume (1% - 100%) for the sound.
266 -- moreinfo is used to provide additional information to the soundlib.
267 -- there may be some accessibility uses for this function.
270 III. Global variables
272 The following global variables are defined in decl.c and are related to
273 the soundlib support in some way. They are just being documented here,
274 Some of them are expected to be used by the soundlib support file,
275 particularly where the core options interface is responsible for
283 a usersound mappings reference
285 iflags.sounds is the master on/off switch to control whether any audio
286 is produced by the soundlib interface
288 iflags.voice is the master on/off switch for voices produced by
289 the soundlib interface.
291 IV. Other related routines
293 These are not part of the interface, but mentioned here for your information.
295 assign_soundlib(soundlib_identifier)
297 -- Places a value into gc.chosen_soundlib as a "hint" that the
298 particular soundlib support is compiled in and thus available.
300 activate_chosen_soundlib(void)
302 -- If a soundlib is already active, indicated by ga.active_soundlib
303 holding the identifier of one of the added soundlib integrations,
304 then the sound_exit_nhsound() is called to turn it off or
306 -- The soundlib identified in gc.chosen_soundlib is activated by
307 updating ga.active_soundlib, copying the chosen interface's
308 sound_proc structure into soundprocs, and calling the
309 soundlib interface's sound_init_nhsound() function.
311 V. Game Start-up and Soundlib Activation Sequencing
313 The following is the general order of calls that lead to soundlib
316 1. assign_soundlib(soundlib_identifier)
318 The platform-specific main, or in the case of at least one
319 sound library that is integrated with the full visual/window
320 support (Qt) - the win_init_nhwindows() routine, drops
321 a hint about a supported soundlib by calling assign_soundlib.
323 [The window interface has already been enabled by this point,
324 meaning that its win_init_nhwindows() has already been called.
325 That's important because for at least one of the soundlibs (Qt),
326 it is the window interface becoming active, and thus initializing
327 the underlying framework that includes both display and sound,
328 that has caused assign_soundlib() to be called again to indicate
329 that its soundlib interface routines are now the preferred soundlib
330 routines to use. That will have superseded the gc.chosen_soundlib
331 value that the platform's main() may have placed there earlier
332 via its own call to assign_soundlib()]
334 2. init_sound_disp_gamewindows()
335 -> activate_chosen_soundlib() is called prior to
336 displaying the game windows.
342 The windsound soundlib is contained in sound/windsound, the macsound
343 soundlib is contained in sound/macsound. The files in these directories
344 contain _only_ soundlib code, and may be replaced completely by other
348 VII. Implementation and Multiple Soundlib Support
351 Multiple soundlib routines are supported in the same binary.
352 When writing a new set of sound library integration routines
353 use the following guidelines:
355 1) Pick a unique name to identify your soundlib. The default
356 built-in soundlib uses "nosound". Your name pick should make it
357 obvious which 3rd party sound library you are writing your
358 interface glue-code for.
359 2) When declaring your soundlib interface functions, precede the
360 function names with your unique prefix. For example,
362 void myprefix_init_nhsound()
364 /* code for initializing the underlying sound library */
367 When/if calling one your own soundlib functions from within your
368 soundlib code (one calling another), we suggest calling the
369 prefixed version to avoid unnecessary overhead of using the
370 soundprocs function pointer.
372 We also suggest declaring all functions (not just the interface
373 functions) with the prefix, or declare them static, to avoid
374 unexpected overlaps with other soundlibs. The same applies to
375 file-scope data variables.
377 3) Declare a structure, "struct sound_procs myprefix_procs", (with your
378 prefix instead of "myprefix") and fill in names of all of your
379 interface functions. All functions specified as part of the interface
380 must be present, even if they have empty function bodies.
382 struct sound_procs myprefix_procs = {
384 SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
385 | SOUND_TRIGGER_ACHIEVEMENTS |SOUND_TRIGGER_SOUNDEFFECTS
386 | SOUND_TRIGGER_AMBIENCE | SOUND_TRIGGER_VERBAL,
387 myprefix_init_nhsound,
388 myprefix_exit_nhsound,
389 myprefix_achievement,
390 myprefix_soundeffect,
391 myprefix_hero_playnotes,
392 myprefix_play_usersound,
397 The first entry in this structure should be the SOUNDID(myprefix)
398 where myprefix should be the name of your soundlib port.
399 After that, the next entry is the sound_triggers mask that identifies
400 what sound_triggers your soundlib will actually react to and
401 support. Don't include the sound_triggers values for functions that are
402 empty, so that the NetHack core code won't bother trying to call
403 them. The other entries are the function addresses.
405 Assuming that you followed the convention in (2), you can safely copy
406 the structure definition from the sample skeleton located below in
407 this document and just change the prefix from "sample" to your prefix.
409 4) Add a new section to the 'enum soundlib_ids' in include/sndprocs.h,
410 just above the entry for 'soundlib_notused'. There are some
411 placeholders for some soundlib possibilities in there already. You
412 can skip this step if your prefix matches one of those, as long as
413 it is unused and you aren't colliding with work already done. Check
414 for a subdirectory in sounds as a reliable indicator of whether it
415 is already being used.
417 Enclose your new section in #ifdef preprocessor directive prefixed
418 with "SND_LIB_" (without the quotes) and the uppercase variation of
421 #ifdef SND_LIB_MYPREFIX
425 Again, place that in the enum above the entry for 'soundlib_notused'.
427 5) Edit mdlib.c and add an entry for your soundlib to the
428 soundlib_information soundlib_opts[] array
429 right above the final "{ 0, 0, 0, FALSE }," entry.
431 #ifdef SND_LIB_MYPREFIX
432 { soundlib_myprefix, "soundlib_myprefix",
433 "<url goes here>", FALSE },
437 6) Edit include/sndprocs.h and add yours to the multiline #if defined
438 used to #define SND_LIB_INTEGRATED when none of the active soundlibs
439 and placeholders are defined.
440 #if defined(SND_LIB_QTSOUND) || defined(SND_LIB_PORTAUDIO) \
441 || defined(SND_LIB_OPENAL) || defined(SND_LIB_SDL_MIXER) \
442 || defined(SND_LIB_MINIAUDIO) || defined(SND_LIB_FMOD) \
443 || defined(SND_LIB_SOUND_ESCCODES) || defined(SND_LIB_VISSOUND) \
444 || defined(SND_LIB_WINDSOUND) || defined(SND_LIB_MACSOUND) \
445 || defined(SND_LIB_MYPREFIX)
447 #define SND_LIB_INTEGRATED
450 7) Edit sounds.c and add the extern entry for your soundlib's sound_procs
451 struct, enclosed with an #ifdef SND_LIB_MYPREFIX block.
453 #ifdef SND_LIB_MYPREFIX
454 extern struct sound_procs myprefix_procs;
458 8) Also while editing sounds.c, add a reference for your myprefix_procs
459 as the last entry in the soundlib_choices[] array initializations,
460 enclosed with and #ifdef SND_LIB_MYPREFIX block.
462 #ifdef SND_LIB_MYPREFIX
466 9) If the soundlib you are using work across multiple (more than one)
467 platform, several files related to building for the various
468 systems and/or build tools will likely require updates in order
469 for them to be able to compile for, and link with, your soundlib
470 interface support and the underlying sound library it is meant to
471 use. This part often isn't particularly fun. The build tools are
472 all quite different, and many developers only understand the build
473 system for one platform/system better than others. It typically
474 comes down to experience and familiarity.
476 Don't be afraid to get assistance with unfamiliar ones.
478 Generally speaking, build tools need to know about:
479 - changes that are needed to include C preprocessor defines
480 for enabling the inclusion of your soundlib support on the
481 compiler command line for all NetHack files
482 ( -DSND_LIB_MYPREFIX ).
483 - changes that are needed to include header files supplied
484 by the underlying sound library, so they can be found
485 via include path updates on the compiler command line
486 ( -I../lib/myprefix/inc).
487 - changes that are needed to link in the sound library
488 itself during the linking stage of the build
489 ( -L ../lib/myprefix/lib/myprefix.lib ).
491 Here are some known examples of what might have to change
492 at the time of this writing:
495 These hints files might require updates to
496 include your new soundlib addition. You can
497 look at what was done for macsound support
498 in sys/unix/hints/macOS.370 for inspiration.
500 sys/unix/NetHack.xcodeproj/project.pbxproj
502 Will require updates in order to build with
505 sys/windows/Makefile.nmake
507 Will require updates in order to build on
508 Windows with Visual studio nmake at the command
511 sys/windows/GNUmakefile
512 sys/windows/GNUmakefile.depend
514 Will require updates in order to build on
515 Windows with mingw32 or MSYS2 using GNU make at
518 sys/windows/vs/NetHackW/NetHackW.vcxproj
520 Will need an <ClCompile Include="$(SndWindDir)myprefix.c" />
521 entry added in order to build under visual
522 studio, and additional updates to link in the
523 underlying sound library, if it requires one.
525 10) Look at your soundlib support file in sound/myprefix/myprefix.c
526 and make sure that all of the calls match the requirements laid out in
529 What follows is a sample soundlib interface that can be used as a
534 /* NetHack may be freely redistributed. See license for details. */
539 * Sample sound interface for NetHack
541 * Replace 'sample' with your soundlib name in this template file.
542 * Should be placed in ../sound/sample/.
545 static void sample_init_nhsound(void);
546 static void sample_exit_nhsound(const char *);
547 static void sample_achievement(schar, schar, int32_t);
548 static void sample_soundeffect(char *, int32_t, int32_t);
549 static void sample_hero_playnotes(int32_t, const char *, int32_t);
550 static void sample_play_usersound(char *, int32_t, int32_t);
551 static void sample_ambience(int32_t ambienceid, int32_t ambience_action,
552 int32_t hero_proximity);
553 static void (*sound_verbal)(char *text, int32_t gender, int32_t tone,
554 int32_t vol, int32_t moreinfo);
556 struct sound_procs sample_procs = {
558 SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
559 | SOUND_TRIGGER_ACHIEVEMENTS |SOUND_TRIGGER_SOUNDEFFECTS
560 | SOUND_TRIGGER_AMBIENCE
565 sample_hero_playnotes,
566 sample_play_usersound,
572 sample_init_nhsound(void)
574 /* Initialize external sound library */
578 sample_exit_nhsound(const char *reason)
580 /* Close / Terminate external sound library */
584 /* fulfill SOUND_TRIGGER_ACHIEVEMENTS */
586 sample_achievement(schar ach1, schar ach2, int32_t avals)
592 /* fulfill SOUND_TRIGGER_SOUNDEFFECTS */
594 sample_soundeffect(char *desc, int32_t seid, int volume)
599 /* fulfill SOUND_TRIGGER_HEROMUSIC */
600 static void sample_hero_playnotes(int32_t instrument, const char *str, int32_t volume)
605 /* fulfill SOUND_TRIGGER_USERSOUNDS */
607 sample_play_usersound(char *filename, int volume, int usidx)
613 sample_ambience(int32_t ambienceid, int32_t ambience_action,
614 int32_t hero_proximity)
619 sample_verbal(char *text, int32_t gender, int32_t tone,
620 int32_t vol, int32_t moreinfo)
624 /* end of sample.c */