Files
nethack/doc/sound.txt
nhmall fec6320e68 rename sys/windows/Makefile.mingw32 to GNUmakefile
GNU make looks first for a file called GNUmakefile, ahead of
looking for Makefile and then makefile.

Renaming sys/windows/Makefile.mingw32 to sys/windows/GNUmakefile
allows:

o src/GNUmakefile (for use by GNU make) and src/Makefile (for use
  Microsoft nmake) to both reside in the src folder during build.

o src/GNUmakefile will be used by GNU make, without having to
  explicitly specify "-f GNUmakefile" on the GNU make command line.

o src/Makefile will be used by Microsoft nmake, without having to
  explicitly specify "-f Makefile" on the Microsoft nmake command line.

For the gcc build, the movemement of sys/windows/GNUmakefile needs
to be copied to src/GNUmakefile as part of the build process (see
sys/windows/build-msys2.txt).

For the Microsoft Visual Studio command line build with nmake,
sys/windows/Makefile.nmake needs to be copied to src/Makefile as
part of the build process (see sys/windows/build-nmake.txt).

They are both copied to the src folder from their respective
repository source file names when the nhsetup.bat file is used.
2024-12-02 19:04:08 -05:00

626 lines
26 KiB
Plaintext

NetHack 3.7 sound.txt $NHDT-Date: 1693253363 2023/08/28 20:09:23 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.7 $
Introduction
This file documents the support for various sound library integration
interface, or just soundlib for short.
The soundlib support is through a standard interface, separating the
main NetHack code from soundlib-specific code. The implementation
supports multiple soundlib systems in the same binary. Even if you only
wish to support one soundlib, you will need to follow the instructions
to end up with a compiled binary
Copyright 2023, Michael Allison, with acknowledgement and thanks to
David Cohrs for window.txt that provided the inspiration for the layout
of this document.
NetHack may be freely redistributed. See license for details.
Contents:
I. Sound trigger types and terminology
II. Interface Specification
III. Global variables
IV. Other related routines
V. Game Startup and Soundlib Activation Sequencing
VI. Conventions
VII. Implementation and Multiple Soundlib Support
I. Sound Trigger Types and Terminology
There are 4 distinct types of sound sound_triggers used by NetHack.
SOUND_TRIGGER_USERSOUNDS User-specified sounds that play based
on config file entries that identify a
regular expression to match against
message window text, and identify an
external sound file to load in response.
The sound interface function pointer
used to invoke it:
void (*sound_play_usersound)(char *filename,
int32_t volume, int32_t idx);
SOUND_TRIGGER_HEROMUSIC Invoked by the core when the in-game hero,
or perhaps another creature, is playing
a tune on an instrument. The sound interface
function pointer used to invoke it:
void (*sound_hero_playnotes)
(int32_t instrument, const char *str,
int32_t volume);
SOUND_TRIGGER_ACHIEVEMENTS Invoked by the core when an in-game
achievement is reached. The soundlib routines
could play appropriate theme or some fanfare
in response.
There needs to be a way to map each
achievement to a specific external
sound file or resource. The sound
interface function pointer used to
invoke it:
void (*sound_achievement)(schar, schar,
int32_t);
SOUND_TRIGGER_SOUNDEFFECTS Invoked by the core when something
sound-producing happens in the game. The
soundlib routines could play an appropriate
sound effect in response. They can be
public-domain or suitably licensed stock
sounds included with the
gamesource. The soundeffect must be made
available during the build process, or
(not-yet-implemented) a way to tie a
particular sound effect to a player-specified
sound samples within the player's config
file. The sound interface function
pointer used to invoke it:
void (*sound_soundeffect)(char *desc,
int32_t seid, int32_t volume);
SOUND_TRIGGER_AMBIENCE Invoked by the core in response to something
atmosphere or mood-producing or flavorful.
Unlike the other interface functions, this
one gets called to notify the sound interface
at the outset (ambience_begin), at the
termination (ambience_end), and
periodically in-between as needed
(ambience_upate), likely with a different
hero proximity value.
SOUND_TRIGGER_VERBAL Invoked by the core when someone (or something)
is speaking.
The types of sound sound_triggers supported by a particular soundlib
implementation are specified in that library's soundlib file, which is usually
found in sound/<library_name>/<library_name>.c (.m in the case of macound), in
the sound_triggers field of the sound_procs struct:
struct sound_procs {
const char *soundname;
enum soundlib_ids soundlib_id;
unsigned long sound_triggers;
void (*sound_init_nhsound)(void);
void (*sound_exit_nhsound)(const char *reason);
void (*sound_achievement)(schar arg1, schar arg2, int32_t avals);
void (*sound_soundeffect)(char *desc, int32_t, int32_t volume);
void (*sound_hero_playnotes)(int32_t instrument, const char *notestr,
int32_t volume);
void (*sound_play_usersound)(char *filename, int32_t volume,
int32_t usidx);
void (*sound_ambience)(int32_t ambienceid, int32_t ambience_action,
int32_t hero_proximity);
void (*sound_verbal)(char *text, int32_t gender, int32_t tone,
int32_t vol, int32_t moreinfo);
};
A sound library integration support file can implement one, two, three, four,
five, or six of the sound trigger types. The more types of sound_triggers the
soundlib implements, the more full-featured the sound experience will be
during the game.
The values can be or'd together in the sound_triggers field initialization.
SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
| SOUND_TRIGGER_ACHIEVEMENTS | SOUND_TRIGGER_SOUNDEFFECTS
| SOUND_TRIGGER_AMBIENCE | SOUND_TRIGGER_VERBAL
II. Interface Specification
A. Integration routines:
All functions below are void unless otherwise noted.
sound_init_nhsound(void);
-- NetHack will call this function when it is ready to enable sound
library support. It will do that during start-up, and it might do
it if the player has chosen to switch soundlib options.
sound_exit_nhsound(const char *reason);
-- NetHack will call this function when it wants to turn off the
sound library support and make it inactive. It will do that
when the game is ending, whether because the game is over or
because the player has chosen to save the game. The function
might also get called if the player has chosen to switch soundlib
options.
sound_achievement(schar arg1, schar arg2, int32_t avals);
-- NetHack will call this function when it wants to invoke a sound
based on an achievement, or an event that has occurred in the
game.
-- arg1 will contain the achievement value used internal to the
game, and if it is non-zero then arg2 should be ignored.
-- avals may contain more specific information about the achievement
or event that has just occurred.
For internal achievements (non-zero arg1), it will be zero if
this is the first occurrence of that achievement, or it will be
non-zero if this is a repeat occurrence.
-- arg2 is only used when arg1 iz zero. arg2 contains event
identifiers for various events that occur during the game. The
identifiers are from the 'enum achievements_arg2' list in
include/sndprocs.h. It is recommended that you look them up
there as new ones get added periodically as the game development
continues. The identifiers all begin with 'sa2_'.
For arg2 events, avals may contain additional integer information
specific to that particular event. For other events, it may be
meaningless. Those relationships will also be documented in
include/sndprocs.h as they develop.
sound_soundeffect(char *desc, int32_t seid, int32_t volume);
-- NetHack will call this function when it wants to invoke a particular
sound effect during the course of a game. The calls are typically
hard-coded into the NetHack sources at various appropriate points,
and the calls typically use the Soundeffects(desc, seid, volume)
macro to do so. They benefit of using the macro is that it does some
suitable validation checks before actually invoking the soundlib
function.
-- desc may hold a text description of the sound effect. Often this
field is not set, so the soundlib routine needs to be aware of
that and not depend on it holding a description.
-- The soundeffects identifiers (seid) are from the
'enum sound_effect_entries' list in include/sndprocs.h.
It is recommended that you look them up there as new ones get
added periodically as game development continues. The identifiers
all begin with 'se_'.
sound_hero_playnotes(int32_t instrument, const char *notestr, int32_t volume);
-- NetHack will call this function when it wants a sequence of notes
(A,B,C,D,E,F,G) played, because the hero in the game, or a
creature in the game, is playing an instrument.
-- instrument specifies the instrument to use. The instrument
identifiers are from the 'enum instruments' list in
include/sndprocs.h. It is recommended that you look them up
there as new ones get added periodically as game development
continues. The identifiers all begin with 'ins_'.
Side note: the underlying values associated with those enums
match the instrument values in some MIDI specifications. If
they match the values for instruments in the underlying sound
library or platform API, they may be able to be passed through.
-- The sequence of notes is in notestr. At this time, NetHack may
be capping the number of notes at 5, but it is not
recommended that the soundlib integration support functions
rely on that note count cap as a hard rule.
-- A soundlib integration support file that has SOUND_TRIGGER_HEROMUSIC
support is expected to play the sound at the volume specified
by the volume argument (1 - 100, representing percentage of
possible volume levels), if the underlying sound library supports
volume adjustments. If it doesn't, the volume argument would
just have to be ignored.
sound_play_usersound(char *filename, int32_t volume, int32_t usidx);
-- NetHack will call this function when it wants a particular
external sound file played, based on a regular expression match
that the player has defined in their config file.
-- A soundlib integration support file that has SOUND_TRIGGER_USERSOUNDS
support is expected to play the sound file specified by the filename
argument.
-- A soundlib integration support file that has SOUND_TRIGGER_USERSOUNDS
support is expected to play the sound at the volume specified
by the volume argument (1 - 100, representing percentage of
possible volume levels), if the underlying sound library supports
volume adjustments. If it doesn't, the volume argument would
just have to be ignored.
sound_ambience(int32_t ambienceid, int32_t ambience_action,
int32_t hero_proximity);
-- NetHack will call this function when it wants a particular
ambience related sound played in order to provide atomosphere
or flavor.
-- The ambienceid is used to identify the particular ambience sound
being sought. The abienceid identifiers are from the
'enum ambiences' list in include/sndprocs.h. It is recommended
that you look them up there as new ones get added periodically
as game development continues. The identifiers all begin
with 'amb_'.
-- ambience_action. A soundlib integration support file that has
SOUND_TRIGGER_AMBIENCE support is expected to commence playing the
sound when it receives an ambience_action of ambience_begin. It
will receive an ambience_action of ambience_end when it should cease
playing the sound. It may receive an ambience_action of
ambience_update periodically, anytime the ambience is underway,
and it should respond accordingly: perhaps by adjusting the nature
of the sound being heard, or possibly by just adjusting the volume.
-- hero_proximity could be zero, in which case the ambience being
triggered is not impacted by the hero's distance from anything.
If the distance of the hero from the source of the ambience does
matter, then a distance value will be in hero_proximity.
sound_verbal(char *text, int32_t gender, int32_t tone, int32_t vol,
int32_t moreinfo);
-- NetHack will call this function when it wants to pass text of
spoken language by a character or creature within the game.
-- text is a transcript of what has been spoken.
-- gender indicates MALE or FEMALE or NEUTRAL (either MALE
or FEMALE) voice.
-- tone indicates the tone of the voice.
-- vol is the volume (1% - 100%) for the sound.
-- moreinfo is used to provide additional information to the soundlib.
-- there may be some accessibility uses for this function.
III. Global variables
The following global variables are defined in decl.c and are related to
the soundlib support in some way. They are just being documented here,
Some of them are expected to be used by the soundlib support file,
particularly where the core options interface is responsible for
setting the values.
[TO BE DONE]
soundprocs
gc.chosen_soundlib
ga.active_soundlib
a usersound mappings reference
iflags.sounds is the master on/off switch to control whether any audio
is produced by the soundlib interface
iflags.voice is the master on/off switch for voices produced by
the soundlib interface.
IV. Other related routines
These are not part of the interface, but mentioned here for your information.
assign_soundlib(soundlib_identifier)
-- Places a value into gc.chosen_soundlib as a "hint" that the
particular soundlib support is compiled in and thus available.
activate_chosen_soundlib(void)
-- If a soundlib is already active, indicated by ga.active_soundlib
holding the identifier of one of the added soundlib integrations,
then the sound_exit_nhsound() is called to turn it off or
deactivate it.
-- The soundlib identified in gc.chosen_soundlib is activated by
updating ga.active_soundlib, copying the chosen interface's
sound_proc structure into soundprocs, and calling the
soundlib interface's sound_init_nhsound() function.
V. Game Start-up and Soundlib Activation Sequencing
The following is the general order of calls that lead to soundlib
activation.
1. assign_soundlib(soundlib_identifier)
The platform-specific main, or in the case of at least one
sound library that is integrated with the full visual/window
support (Qt) - the win_init_nhwindows() routine, drops
a hint about a supported soundlib by calling assign_soundlib.
[The window interface has already been enabled by this point,
meaning that its win_init_nhwindows() has already been called.
That's important because for at least one of the soundlibs (Qt),
it is the window interface becoming active, and thus initializing
the underlying framework that includes both display and sound,
that has caused assign_soundlib() to be called again to indicate
that its soundlib interface routines are now the preferred soundlib
routines to use. That will have superseded the gc.chosen_soundlib
value that the platform's main() may have placed there earlier
via its own call to assign_soundlib()]
2. init_sound_disp_gamewindows()
-> activate_chosen_soundlib() is called prior to
displaying the game windows.
VI. Conventions
[to be done]
The windsound soundlib is contained in sound/windsound, the macsound
soundlib is contained in sound/macsound. The files in these directories
contain _only_ soundlib code, and may be replaced completely by other
soundlib support.
VII. Implementation and Multiple Soundlib Support
Multiple soundlib routines are supported in the same binary.
When writing a new set of sound library integration routines
use the following guidelines:
1) Pick a unique name to identify your soundlib. The default
built-in soundlib uses "nosound". Your name pick should make it
obvious which 3rd party sound library you are writing your
interface glue-code for.
2) When declaring your soundlib interface functions, precede the
function names with your unique prefix. For example,
void myprefix_init_nhsound()
{
/* code for initializing the underlying sound library */
}
When/if calling one your own soundlib functions from within your
soundlib code (one calling another), we suggest calling the
prefixed version to avoid unnecessary overhead of using the
soundprocs function pointer.
We also suggest declaring all functions (not just the interface
functions) with the prefix, or declare them static, to avoid
unexpected overlaps with other soundlibs. The same applies to
file-scope data variables.
3) Declare a structure, "struct sound_procs myprefix_procs", (with your
prefix instead of "myprefix") and fill in names of all of your
interface functions. All functions specified as part of the interface
must be present, even if they have empty function bodies.
struct sound_procs myprefix_procs = {
SOUNDID(myprefix),
SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
| SOUND_TRIGGER_ACHIEVEMENTS |SOUND_TRIGGER_SOUNDEFFECTS
| SOUND_TRIGGER_AMBIENCE | SOUND_TRIGGER_VERBAL,
myprefix_init_nhsound,
myprefix_exit_nhsound,
myprefix_achievement,
myprefix_soundeffect,
myprefix_hero_playnotes,
myprefix_play_usersound,
myprefix_ambience,
myprefix_verbal),
};
The first entry in this structure should be the SOUNDID(myprefix)
where myprefix should be the name of your soundlib port.
After that, the next entry is the sound_triggers mask that identifies
what sound_triggers your soundlib will actually react to and
support. Don't include the sound_triggers values for functions that are
empty, so that the NetHack core code won't bother trying to call
them. The other entries are the function addresses.
Assuming that you followed the convention in (2), you can safely copy
the structure definition from the sample skeleton located below in
this document and just change the prefix from "sample" to your prefix.
4) Add a new section to the 'enum soundlib_ids' in include/sndprocs.h,
just above the entry for 'soundlib_notused'. There are some
placeholders for some soundlib possibilities in there already. You
can skip this step if your prefix matches one of those, as long as
it is unused and you aren't colliding with work already done. Check
for a subdirectory in sounds as a reliable indicator of whether it
is already being used.
Enclose your new section in #ifdef preprocessor directive prefixed
with "SND_LIB_" (without the quotes) and the uppercase variation of
your soundlib name.
#ifdef SND_LIB_MYPREFIX
soundlib_myprefix,
#endif
Again, place that in the enum above the entry for 'soundlib_notused'.
5) Edit mdlib.c and add an entry for your soundlib to the
soundlib_information soundlib_opts[] array
right above the final "{ 0, 0, 0, FALSE }," entry.
#ifdef SND_LIB_MYPREFIX
{ soundlib_myprefix, "soundlib_myprefix",
"<url goes here>", FALSE },
#endif
6) Edit include/sndprocs.h and add yours to the multiline #if defined
used to #define SND_LIB_INTEGRATED when none of the active soundlibs
and placeholders are defined.
#if defined(SND_LIB_QTSOUND) || defined(SND_LIB_PORTAUDIO) \
|| defined(SND_LIB_OPENAL) || defined(SND_LIB_SDL_MIXER) \
|| defined(SND_LIB_MINIAUDIO) || defined(SND_LIB_FMOD) \
|| defined(SND_LIB_SOUND_ESCCODES) || defined(SND_LIB_VISSOUND) \
|| defined(SND_LIB_WINDSOUND) || defined(SND_LIB_MACSOUND) \
|| defined(SND_LIB_MYPREFIX)
#define SND_LIB_INTEGRATED
[...]
7) Edit sounds.c and add the extern entry for your soundlib's sound_procs
struct, enclosed with an #ifdef SND_LIB_MYPREFIX block.
#ifdef SND_LIB_MYPREFIX
extern struct sound_procs myprefix_procs;
#endif
8) Also while editing sounds.c, add a reference for your myprefix_procs
as the last entry in the soundlib_choices[] array initializations,
enclosed with and #ifdef SND_LIB_MYPREFIX block.
#ifdef SND_LIB_MYPREFIX
{ &myprefix_procs },
#endif
9) If the soundlib you are using work across multiple (more than one)
platform, several files related to building for the various
systems and/or build tools will likely require updates in order
for them to be able to compile for, and link with, your soundlib
interface support and the underlying sound library it is meant to
use. This part often isn't particularly fun. The build tools are
all quite different, and many developers only understand the build
system for one platform/system better than others. It typically
comes down to experience and familiarity.
Don't be afraid to get assistance with unfamiliar ones.
Generally speaking, build tools need to know about:
- changes that are needed to include C preprocessor defines
for enabling the inclusion of your soundlib support on the
compiler command line for all NetHack files
( -DSND_LIB_MYPREFIX ).
- changes that are needed to include header files supplied
by the underlying sound library, so they can be found
via include path updates on the compiler command line
( -I../lib/myprefix/inc).
- changes that are needed to link in the sound library
itself during the linking stage of the build
( -L ../lib/myprefix/lib/myprefix.lib ).
Here are some known examples of what might have to change
at the time of this writing:
sys/unix/hints/*
These hints files might require updates to
include your new soundlib addition. You can
look at what was done for macsound support
in sys/unix/hints/macOS.370 for inspiration.
sys/unix/NetHack.xcodeproj/project.pbxproj
Will require updates in order to build with
Xcode on macOS.
sys/windows/Makefile.nmake
Will require updates in order to build on
Windows with Visual studio nmake at the command
line.
sys/windows/GNUmakefile
sys/windows/GNUmakefile.depend
Will require updates in order to build on
Windows with mingw32 or MSYS2 using GNU make at
the command line.
sys/windows/vs/NetHackW/NetHackW.vcxproj
Will need an <ClCompile Include="$(SndWindDir)myprefix.c" />
entry added in order to build under visual
studio, and additional updates to link in the
underlying sound library, if it requires one.
10) Look at your soundlib support file in sound/myprefix/myprefix.c
and make sure that all of the calls match the requirements laid out in
Section VI and VII.
What follows is a sample soundlib interface that can be used as a
starting template.
-- snip 8< --
/* sample.c */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
/*
* Sample sound interface for NetHack
*
* Replace 'sample' with your soundlib name in this template file.
* Should be placed in ../sound/sample/.
*/
static void sample_init_nhsound(void);
static void sample_exit_nhsound(const char *);
static void sample_achievement(schar, schar, int32_t);
static void sample_soundeffect(char *, int32_t, int32_t);
static void sample_hero_playnotes(int32_t, const char *, int32_t);
static void sample_play_usersound(char *, int32_t, int32_t);
static void sample_ambience(int32_t ambienceid, int32_t ambience_action,
int32_t hero_proximity);
static void (*sound_verbal)(char *text, int32_t gender, int32_t tone,
int32_t vol, int32_t moreinfo);
struct sound_procs sample_procs = {
SOUNDID(sample),
SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
| SOUND_TRIGGER_ACHIEVEMENTS |SOUND_TRIGGER_SOUNDEFFECTS
| SOUND_TRIGGER_AMBIENCE
sample_init_nhsound,
sample_exit_nhsound,
sample_achievement,
sample_soundeffect,
sample_hero_playnotes,
sample_play_usersound,
sample_ambience,
sample_verbal
};
static void
sample_init_nhsound(void)
{
/* Initialize external sound library */
}
static void
sample_exit_nhsound(const char *reason)
{
/* Close / Terminate external sound library */
}
/* fulfill SOUND_TRIGGER_ACHIEVEMENTS */
static void
sample_achievement(schar ach1, schar ach2, int32_t avals)
{
}
/* fulfill SOUND_TRIGGER_SOUNDEFFECTS */
static void
sample_soundeffect(char *desc, int32_t seid, int volume)
{
}
/* fulfill SOUND_TRIGGER_HEROMUSIC */
static void sample_hero_playnotes(int32_t instrument, const char *str, int32_t volume)
{
}
/* fulfill SOUND_TRIGGER_USERSOUNDS */
static void
sample_play_usersound(char *filename, int volume, int usidx)
{
}
static void
sample_ambience(int32_t ambienceid, int32_t ambience_action,
int32_t hero_proximity)
{
}
static void
sample_verbal(char *text, int32_t gender, int32_t tone,
int32_t vol, int32_t moreinfo)
{
}
/* end of sample.c */
-- >8 --