make rank() static again
[NetHack.git] / doc / sound.txt
blob2f90d333479e8d36824eb4d37af7f5759d72fd59
1 NetHack 3.7  sound.txt  $NHDT-Date: 1693253363 2023/08/28 20:09:23 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.7 $
3 Introduction
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
16 of this document.
18 NetHack may be freely redistributed.  See license for details.
20 Contents:
21         I.    Sound trigger types and terminology
22         II.   Interface Specification
23         III.  Global variables
24         IV.   Other related routines
25         V.    Game Startup and Soundlib Activation Sequencing
26         VI.   Conventions
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
39                                   used to invoke it:
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,
51                                         int32_t volume);
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
56                                  in response.
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
61                                  invoke it:
63                                  void (*sound_achievement)(schar, schar,
64                                                             int32_t);
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
91                                  hero proximity value.
93     SOUND_TRIGGER_VERBAL         Invoked by the core when someone (or something)
94                                  is speaking.
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:
102     struct sound_procs {
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,
111               int32_t volume);
112         void (*sound_play_usersound)(char *filename, int32_t volume,
113               int32_t usidx);
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);
118     };
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
123 during the game.
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
150        options.
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
156        game.
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
183        function.
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
225        argument.
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
237        or flavor.
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
243        with 'amb_'.
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,
258              int32_t moreinfo);
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
263        or FEMALE) voice.
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
276 setting the values.
278 [TO BE DONE]
280 soundprocs
281 gc.chosen_soundlib
282 ga.active_soundlib
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
305        deactivate it.
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
314 activation.
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.
338 VI.  Conventions
340 [to be done]
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
345 soundlib support.
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()
363         {
364             /* code for initializing the underlying sound library */
365         }
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 = {
383            SOUNDID(myprefix),
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,
393            myprefix_ambience,
394            myprefix_verbal),
395        };
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
419    your soundlib name.
421        #ifdef SND_LIB_MYPREFIX
422            soundlib_myprefix,
423        #endif
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 },
434        #endif
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
448        [...]
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;
455        #endif
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
463            { &myprefix_procs },
464        #endif
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:
494        sys/unix/hints/*
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
503                         Xcode on macOS.
505        sys/windows/Makefile.nmake
507                         Will require updates in order to build on
508                         Windows with Visual studio nmake at the command
509                         line.
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
516                         the command line.
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
527    Section VI and VII.
529 What follows is a sample soundlib interface that can be used as a
530 starting template.
532 -- snip 8< --
533 /* sample.c */
534 /* NetHack may be freely redistributed.  See license for details. */
536 #include "hack.h"
539  * Sample sound interface for NetHack
541  * Replace 'sample' with your soundlib name in this template file.
542  * Should be placed in ../sound/sample/.
543  */
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 = {
557     SOUNDID(sample),
558     SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
559         | SOUND_TRIGGER_ACHIEVEMENTS |SOUND_TRIGGER_SOUNDEFFECTS
560         | SOUND_TRIGGER_AMBIENCE
561     sample_init_nhsound,
562     sample_exit_nhsound,
563     sample_achievement,
564     sample_soundeffect,
565     sample_hero_playnotes,
566     sample_play_usersound,
567     sample_ambience,
568     sample_verbal
571 static void
572 sample_init_nhsound(void)
574     /* Initialize external sound library */
577 static void
578 sample_exit_nhsound(const char *reason)
580     /* Close / Terminate external sound library */
584 /* fulfill SOUND_TRIGGER_ACHIEVEMENTS */
585 static void
586 sample_achievement(schar ach1, schar ach2, int32_t avals)
592 /* fulfill SOUND_TRIGGER_SOUNDEFFECTS */
593 static void
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 */
606 static void
607 sample_play_usersound(char *filename, int volume, int usidx)
612 static void
613 sample_ambience(int32_t ambienceid, int32_t ambience_action,
614                 int32_t hero_proximity)
618 static void
619 sample_verbal(char *text, int32_t gender, int32_t tone,
620               int32_t vol, int32_t moreinfo)
624 /* end of sample.c */
625 -- >8 --