Files
nethack/sound/macsound/macsound.m
nhmall 3d908c49ef take initial stab at macsound interface
Use macOS AppKit framework routines for a first cut at a
macsound soundlib interface.

Requires WANT_MACSOUND=1 on build.

Nothing has been done to move the stock sounds into the resources
of a bundle, so after building, if you want to try the stock sounds
out:
	cp sound/wav/*.wav ~/Library/Sounds

Because the NSSound macOS routines always do the search, supposedly
the following locations are searched in this order:
   1. the application’s main bundle
   2. ~/Library/Sounds
   3. /Library/Sounds
   4. /Network/Library/Sounds
   5. /System/Library/Sounds

Although not specifically implemented as of yet, it may be pretty
close to being able to put soundeffects wav files (by se_ name)
into ~/Library/Sounds working for the SND_SOUNDEFFECTS_AUTOMAP feature.

Feedback is welcome. Contributions for improving it are even more
welcome.

The new soundlib supporting file is named
sound/macsound/macsound.m since it's got objective C in it.

Known bugs and glitches:

The Hero_playnotes on a set of 5 notes goes too fast, so there
needs to be a slight delay added between the note of a multi-note
play.
2023-01-23 23:50:43 -05:00

328 lines
10 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* macsound.m */
/* Copyright Michael Allison, 2023 */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#ifdef DEBUG
#undef DEBUG
#endif
/* #import <Foundation/Foundation.h> */
#import <AppKit/AppKit.h>
/*
* Sample sound interface for NetHack
*
* Replace 'macsound' with your soundlib name in this template file.
* Should be placed in ../sound/macsound/.
*/
#define soundlib_macsound soundlib_nosound + 1
static void macsound_init_nhsound(void);
static void macsound_exit_nhsound(const char *);
static void macsound_achievement(schar, schar, int32_t);
static void macsound_soundeffect(char *, int32_t, int32_t);
static void macsound_hero_playnotes(int32_t, const char *, int32_t);
static void macsound_play_usersound(char *, int32_t, int32_t);
static int affiliate(int32_t seid, const char *soundname);
struct sound_procs macsound_procs = {
SOUNDID(macsound),
// SNDCAP_USERSOUNDS | SNDCAP_HEROMUSIC
// | SNDCAP_ACHIEVEMENTS |SNDCAP_SOUNDEFFECTS,
SNDCAP_USERSOUNDS | SNDCAP_HEROMUSIC | SNDCAP_SOUNDEFFECTS,
macsound_init_nhsound,
macsound_exit_nhsound,
macsound_achievement,
macsound_soundeffect,
macsound_hero_playnotes,
macsound_play_usersound,
};
/*
*
* Types of potential sound supports (all are optionally implemented):
*
* SNDCAP_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);
*
* SNDCAP_HEROMUSIC Invoked by the core when the in-game hero is
* playing a tune on an instrument. The sound
* interface function pointer used to invoke it:
*
* void (*sound_hero_playnotes)(int32_t instrument,
* char *str, int32_t volume);
*
* SNDCAP_ACHIEVEMENTS Invoked by the core when an in-game achievement
* is reached. The soundlib routines could play
* appropriate theme or mood music in response.
* There would need to be a way to map the
* achievements to external user-specified sounds.
* The sound interface function pointer used to
* invoke it:
*
* void (*sound_achievement)(schar, schar,
* int32_t);
*
* SNDCAP_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
* game source and made available during the build
* process, or (not-yet-implemented) a way to
* tie particular sound effects to a user-specified
* sound macsounds in a config file. The sound
* interface function pointer used to invoke it:
*
* void (*sound_soundeffect)(char *desc, int32_t,
* int32_t volume);
*
* The routines below would call into your sound library.
* to fulfill the functionality.
*/
static void
macsound_init_nhsound(void)
{
/* Initialize external sound library */
}
static void
macsound_exit_nhsound(const char *reason UNUSED)
{
/* Close / Terminate external sound library */
}
/* fulfill SNDCAP_ACHIEVEMENTS */
static void
macsound_achievement(schar ach1 UNUSED, schar ach2 UNUSED, int32_t repeat UNUSED)
{
}
/* magic number 40 is the current number of sound_ files to include */
#define EXTRA_SOUNDS 40
static int32_t affiliation[number_of_se_entries + EXTRA_SOUNDS] = { 0 };
static NSString *soundstring[number_of_se_entries + EXTRA_SOUNDS];
static NSSound *seSound[number_of_se_entries + EXTRA_SOUNDS];
/* fulfill SNDCAP_SOUNDEFFECTS */
static void
macsound_soundeffect(char *desc UNUSED, int32_t seid, int volume UNUSED)
{
#ifdef SND_SOUNDEFFECTS_AUTOMAP
/* Supposedly, the following locations are searched in this order:
* 1. the applications main bundle
* 2. ~/Library/Sounds
* 3. /Library/Sounds
* 4. /Network/Library/Sounds
* 5. /System/Library/Sounds
*/
char buf[1024];
const char *soundname;
if (seid <= se_zero_invalid || seid >= number_of_se_entries)
return;
if (!affiliation[seid]) {
soundname = get_sound_effect_filename(seid, buf, sizeof buf, 1);
if (soundname) {
affiliate(seid, soundname);
}
}
if (affiliation[seid]) {
if ([seSound[seid] isPlaying])
[seSound[seid] stop];
[seSound[seid] play];
}
#endif
}
#define WAVEMUSIC_SOUNDS
/*
0 sound_Bell.wav
1 sound_Drum_Of_Earthquake.wav
2 sound_Fire_Horn.wav
3 sound_Frost_Horn.wav
4 sound_Leather_Drum.wav
5 sound_Bugle_A.wav
6 sound_Bugle_B.wav
7 sound_Bugle_C.wav
8 sound_Bugle_D.wav
9 sound_Bugle_E.wav
10 sound_Bugle_F.wav
11 sound_Bugle_G.wav
12 sound_Magic_Harp_A.wav
13 sound_Magic_Harp_B.wav
14 sound_Magic_Harp_C.wav
15 sound_Magic_Harp_D.wav
16 sound_Magic_Harp_E.wav
17 sound_Magic_Harp_F.wav
18 sound_Magic_Harp_G.wav
19 sound_Tooled_Horn_A.wav
20 sound_Tooled_Horn_B.wav
21 sound_Tooled_Horn_C.wav
22 sound_Tooled_Horn_D.wav
23 sound_Tooled_Horn_E.wav
24 sound_Tooled_Horn_F.wav
25 sound_Tooled_Horn_G.wav
26 sound_Wooden_Flute_A.wav
27 sound_Wooden_Flute_B.wav
28 sound_Wooden_Flute_C.wav
29 sound_Wooden_Flute_D.wav
30 sound_Wooden_Flute_E.wav
31 sound_Wooden_Flute_F.wav
32 sound_Wooden_Flute_G.wav
33 sound_Wooden_Harp_A.wav
34 sound_Wooden_Harp_B.wav
35 sound_Wooden_Harp_C.wav
36 sound_Wooden_Harp_D.wav
37 sound_Wooden_Harp_E.wav
38 sound_Wooden_Harp_F.wav
39 sound_Wooden_Harp_G.wav
*/
/* fulfill SNDCAP_HEROMUSIC */
static void macsound_hero_playnotes(int32_t instrument,
const char *str, int32_t vol UNUSED)
{
#ifdef WAVEMUSIC_SOUNDS
uint32_t pseudo_seid;
boolean has_note_variations = FALSE;
char resourcename[120], *end_of_res = 0;
const char *c = 0;
if (!str)
return;
resourcename[0] = '\0';
switch(instrument) {
case ins_tinkle_bell:
Strcpy(resourcename, "sound_Bell");
pseudo_seid = 0;
break;
case ins_taiko_drum: /* DRUM_OF_EARTHQUAKE */
Strcpy(resourcename, "sound_Drum_Of_Earthquake");
pseudo_seid = 1;
break;
case ins_baritone_sax: /* FIRE_HORN */
Strcpy(resourcename, "sound_Fire_Horn");
pseudo_seid = 2;
break;
case ins_french_horn: /* FROST_HORN */
Strcpy(resourcename, "sound_Frost_Horn");
pseudo_seid = 3;
break;
case ins_melodic_tom: /* LEATHER_DRUM */
Strcpy(resourcename, "sound_Leather_Drum");
pseudo_seid = 4;
break;
case ins_trumpet: /* BUGLE */
Strcpy(resourcename, "sound_Bugle");
has_note_variations = TRUE;
pseudo_seid = 5;
break;
case ins_cello: /* MAGIC_HARP */
Strcpy(resourcename, "sound_Magic_Harp");
has_note_variations = TRUE;
pseudo_seid = 12;
case ins_english_horn: /* TOOLED_HORN */
Strcpy(resourcename, "sound_Tooled_Horn");
has_note_variations = TRUE;
pseudo_seid = 19;
break;
case ins_flute: /* WOODEN_FLUTE */
Strcpy(resourcename, "sound_Wooden_Flute");
has_note_variations = TRUE;
pseudo_seid = 26;
break;
case ins_orchestral_harp: /* WOODEN_HARP */
Strcpy(resourcename, "sound_Wooden_Harp");
has_note_variations = TRUE;
pseudo_seid = 33;
break;
case ins_pan_flute: /* MAGIC_FLUTE */
/* wav files for sound_Magic_Flute not added yet */
Strcpy(resourcename, "sound_Wooden_Flute");
has_note_variations = TRUE;
pseudo_seid = 26;
break;
}
pseudo_seid += number_of_se_entries; /* get past se_ entries */
if (has_note_variations) {
int i, idx = 0, notecount = strlen(str);
static const char *const note_suffixes[]
= { "_A", "_B", "_C", "_D", "_E", "_F", "_G" };
end_of_res = eos(resourcename);
c = str;
for (i = 0; i < notecount; ++i) {
if (*c >= 'A' && *c <= 'G') {
idx = (*c) - 'A';
pseudo_seid += idx;
if (pseudo_seid >= SIZE(affiliation))
break;
Strcpy(end_of_res, note_suffixes[idx]);
if (!affiliation[pseudo_seid]) {
affiliate(pseudo_seid, resourcename);
}
if (affiliation[pseudo_seid]) {
if ([seSound[pseudo_seid] isPlaying])
[seSound[pseudo_seid] stop];
[seSound[pseudo_seid] play];
}
}
c++;
}
} else {
if (!affiliation[pseudo_seid]) {
affiliate(pseudo_seid, resourcename);
}
if (affiliation[pseudo_seid]) {
if ([seSound[pseudo_seid] isPlaying])
[seSound[pseudo_seid] stop];
[seSound[pseudo_seid] play];
}
}
#endif
}
/* fulfill SNDCAP_USERSOUNDS */
static void
macsound_play_usersound(char *filename UNUSED, int volume UNUSED, int idx UNUSED)
{
}
static int
affiliate(int32_t seid, const char *soundname)
{
if (!soundname || seid <= se_zero_invalid || seid >= SIZE(affiliation))
return 0;
if (!affiliation[seid]) {
affiliation[seid] = seid;
soundstring[seid] = [NSString stringWithUTF8String:soundname];
seSound[seid] = [NSSound soundNamed:soundstring[seid]];
}
return 1;
}
/* end of macsound.m */