372 lines
11 KiB
Objective-C
372 lines
11 KiB
Objective-C
/* 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>
|
|
|
|
#define SPEECH_SOUNDS
|
|
#ifdef SPEECH_SOUNDS
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#endif
|
|
|
|
|
|
/*
|
|
* 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 void macsound_ambience(int32_t, int32_t, int32_t);
|
|
static void macsound_verbal(char *, int32_t, int32_t, int32_t, int32_t);
|
|
static int affiliate(int32_t seid, const char *soundname);
|
|
|
|
|
|
|
|
/*
|
|
* Sound capabilities that can be enabled:
|
|
* SOUND_TRIGGER_USERSOUNDS | SOUND_TRIGGER_HEROMUSIC
|
|
* | SOUND_TRIGGER_ACHIEVEMENTS | SOUND_TRIGGER_SOUNDEFFECTS
|
|
* | SOUND_TRIGGER_AMBIENCE,
|
|
*/
|
|
|
|
struct sound_procs macsound_procs = {
|
|
SOUNDID(macsound),
|
|
SOUND_TRIGGER_HEROMUSIC | SOUND_TRIGGER_SOUNDEFFECTS
|
|
| SOUND_TRIGGER_ACHIEVEMENTS
|
|
#ifdef SND_SPEECH
|
|
| SOUND_TRIGGER_VERBAL
|
|
#endif
|
|
|0,
|
|
macsound_init_nhsound,
|
|
macsound_exit_nhsound,
|
|
macsound_achievement,
|
|
macsound_soundeffect,
|
|
macsound_hero_playnotes,
|
|
macsound_play_usersound,
|
|
macsound_ambience,
|
|
macsound_verbal,
|
|
};
|
|
|
|
static void
|
|
macsound_init_nhsound(void)
|
|
{
|
|
/* Initialize external sound library */
|
|
}
|
|
|
|
static void
|
|
macsound_exit_nhsound(const char *reason UNUSED)
|
|
{
|
|
/* Close / Terminate external sound library */
|
|
|
|
}
|
|
|
|
static void
|
|
macsound_ambience(int32_t ambienceid UNUSED, int32_t ambience_action UNUSED,
|
|
int32_t hero_proximity UNUSED)
|
|
{
|
|
}
|
|
|
|
/* magic number 47 is the current number of sound_ files to include */
|
|
#define EXTRA_SOUNDS 47
|
|
|
|
static int32_t affiliation[number_of_se_entries + number_of_sa2_entries + EXTRA_SOUNDS] = { 0 };
|
|
static NSString *soundstring[number_of_se_entries + number_of_sa2_entries + EXTRA_SOUNDS];
|
|
static NSSound *seSound[number_of_se_entries + number_of_sa2_entries + EXTRA_SOUNDS];
|
|
|
|
#ifdef SND_SOUNDEFFECTS_AUTOMAP
|
|
#define AUTOMAPONLY
|
|
#else
|
|
#define AUTOMAPONLY UNUSED
|
|
#endif
|
|
|
|
/* fulfill SOUND_TRIGGER_SOUNDEFFECTS */
|
|
static void
|
|
macsound_soundeffect(char *desc UNUSED, int32_t seid AUTOMAPONLY, int vol AUTOMAPONLY)
|
|
{
|
|
#ifdef SND_SOUNDEFFECTS_AUTOMAP
|
|
|
|
/* 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
|
|
*/
|
|
|
|
char buf[1024];
|
|
const char *soundname;
|
|
float fvolume = (float) vol / 100.00;
|
|
|
|
if (fvolume < 0.1 || fvolume > 1.0)
|
|
fvolume = 1.0;
|
|
|
|
if (seid <= se_zero_invalid || seid >= number_of_se_entries)
|
|
return;
|
|
if (!affiliation[seid]) {
|
|
soundname = get_sound_effect_filename(seid, buf, sizeof buf,
|
|
sff_base_only);
|
|
if (soundname) {
|
|
affiliate(seid, soundname);
|
|
}
|
|
}
|
|
if (affiliation[seid]) {
|
|
if ([seSound[seid] isPlaying])
|
|
[seSound[seid] stop];
|
|
if ([seSound[seid] volume] != fvolume)
|
|
[seSound[seid] setVolume:fvolume];
|
|
[seSound[seid] play];
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#define WAVEMUSIC_SOUNDS
|
|
|
|
#ifdef WAVEMUSIC_SOUNDS
|
|
#define WAVEMUSICONLY
|
|
#else
|
|
#define WAVEMUSICONLY UNUSED
|
|
#endif
|
|
|
|
/* This is the number of sound_ files that support WAVEMUSIC_SOUNDS */
|
|
static const int wavemusic_sound_count = EXTRA_SOUNDS;
|
|
|
|
/* fulfill SOUND_TRIGGER_HEROMUSIC */
|
|
static void macsound_hero_playnotes(int32_t instrument WAVEMUSICONLY,
|
|
const char *str WAVEMUSICONLY, int32_t vol WAVEMUSICONLY)
|
|
{
|
|
#ifdef WAVEMUSIC_SOUNDS
|
|
uint32_t pseudo_seid, pseudo_seid_base = 0;
|
|
BOOL has_note_variations = false;
|
|
char resourcename[120], *end_of_res = 0;
|
|
const char *c = 0;
|
|
float fvolume = (float) vol / 100.00;
|
|
|
|
if (fvolume < 0.1 || fvolume > 1.0)
|
|
fvolume = 1.0;
|
|
|
|
if (!str)
|
|
return;
|
|
resourcename[0] = '\0';
|
|
switch(instrument) {
|
|
case ins_tinkle_bell:
|
|
Strcpy(resourcename, "sound_Bell");
|
|
pseudo_seid_base = 0;
|
|
break;
|
|
case ins_taiko_drum: /* DRUM_OF_EARTHQUAKE */
|
|
Strcpy(resourcename, "sound_Drum_Of_Earthquake");
|
|
pseudo_seid_base = 1;
|
|
break;
|
|
case ins_baritone_sax: /* FIRE_HORN */
|
|
Strcpy(resourcename, "sound_Fire_Horn");
|
|
pseudo_seid_base = 2;
|
|
break;
|
|
case ins_french_horn: /* FROST_HORN */
|
|
Strcpy(resourcename, "sound_Frost_Horn");
|
|
pseudo_seid_base = 3;
|
|
break;
|
|
case ins_melodic_tom: /* LEATHER_DRUM */
|
|
Strcpy(resourcename, "sound_Leather_Drum");
|
|
pseudo_seid_base = 4;
|
|
break;
|
|
case ins_trumpet: /* BUGLE */
|
|
Strcpy(resourcename, "sound_Bugle");
|
|
has_note_variations = true;
|
|
pseudo_seid_base = 5;
|
|
break;
|
|
case ins_cello: /* MAGIC_HARP */
|
|
Strcpy(resourcename, "sound_Magic_Harp");
|
|
has_note_variations = true;
|
|
pseudo_seid_base = 12;
|
|
break;
|
|
case ins_english_horn: /* TOOLED_HORN */
|
|
Strcpy(resourcename, "sound_Tooled_Horn");
|
|
has_note_variations = true;
|
|
pseudo_seid_base = 19;
|
|
break;
|
|
case ins_flute: /* WOODEN_FLUTE */
|
|
Strcpy(resourcename, "sound_Wooden_Flute");
|
|
has_note_variations = true;
|
|
pseudo_seid_base = 26;
|
|
break;
|
|
case ins_orchestral_harp: /* WOODEN_HARP */
|
|
Strcpy(resourcename, "sound_Wooden_Harp");
|
|
has_note_variations = true;
|
|
pseudo_seid_base = 33;
|
|
break;
|
|
case ins_pan_flute: /* MAGIC_FLUTE */
|
|
Strcpy(resourcename, "sound_Magic_Flute");
|
|
has_note_variations = true;
|
|
pseudo_seid_base = 40;
|
|
break;
|
|
}
|
|
pseudo_seid_base += number_of_se_entries; /* get past se_ entries */
|
|
|
|
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') {
|
|
pseudo_seid = pseudo_seid_base;
|
|
if (has_note_variations) {
|
|
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];
|
|
if ([seSound[pseudo_seid] volume] != fvolume)
|
|
[seSound[pseudo_seid] setVolume:fvolume];
|
|
[seSound[pseudo_seid] play];
|
|
if (i < notecount - 1) {
|
|
/* more notes to follow */
|
|
msleep(150);
|
|
[seSound[pseudo_seid] stop];
|
|
}
|
|
}
|
|
}
|
|
c++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#define ACHIEVEMENT_SOUNDS
|
|
|
|
#ifdef ACHIEVEMENT_SOUNDS
|
|
#define ACHIEVEONLY
|
|
#else
|
|
#define ACHIEVEONLY UNUSED
|
|
#endif
|
|
|
|
/* fulfill SOUND_TRIGGER_ACHIEVEMENTS */
|
|
static void
|
|
macsound_achievement(schar ach1 ACHIEVEONLY,
|
|
schar ach2 ACHIEVEONLY,
|
|
int32_t moreinfo UNUSED)
|
|
{
|
|
#ifdef ACHIEVEMENT_SOUNDS
|
|
char resourcename[120];
|
|
uint32_t pseudo_seid, pseudo_seid_base;
|
|
|
|
if (ach1 == 0 && ach2 == 0)
|
|
return;
|
|
|
|
resourcename[0] = '\0';
|
|
pseudo_seid_base = 0;
|
|
if (ach1 == 0) {
|
|
int sa2 = (int) ach2;
|
|
|
|
if (sa2 > sa2_zero_invalid && sa2 < number_of_sa2_entries) {
|
|
switch(sa2) {
|
|
case sa2_splashscreen:
|
|
Strcpy(resourcename, "sa2_splashscreen");
|
|
break;
|
|
case sa2_newgame_nosplash:
|
|
Strcpy(resourcename, "sa2_newgame_nosplash");
|
|
break;
|
|
case sa2_restoregame:
|
|
Strcpy(resourcename, "sa2_restoregame");
|
|
break;
|
|
case sa2_xplevelup:
|
|
Strcpy(resourcename, "sa2_xplevelup");
|
|
break;
|
|
case sa2_xpleveldown:
|
|
Strcpy(resourcename, "sa2_xpleveldown");
|
|
break;
|
|
}
|
|
if (resourcename[0] == '\0')
|
|
return;
|
|
|
|
/* get past se_ entries and the wavemusic entries */
|
|
pseudo_seid_base += (number_of_se_entries + wavemusic_sound_count);
|
|
|
|
pseudo_seid = pseudo_seid_base + sa2;
|
|
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 SOUND_TRIGGER_USERSOUNDS */
|
|
static void
|
|
macsound_play_usersound(char *filename UNUSED, int volume UNUSED, int idx UNUSED)
|
|
{
|
|
|
|
}
|
|
|
|
#ifdef SND_SPEECH
|
|
#define SPEECHONLY
|
|
#else
|
|
#define SPEECHONLY UNUSED
|
|
#endif
|
|
|
|
static void
|
|
macsound_verbal(char *text SPEECHONLY, int32_t gndr SPEECHONLY, int32_t tone_of_voice UNUSED, int32_t vol UNUSED, int32_t moreinfo UNUSED)
|
|
{
|
|
#ifdef SND_SPEECH
|
|
NSMutableString *speechstring;
|
|
AVSpeechUtterance *text2speechutt;
|
|
AVSpeechSynthesizer *synthesizer;
|
|
|
|
synthesizer = [[AVSpeechSynthesizer alloc]init];
|
|
speechstring = [NSMutableString stringWithUTF8String:text];
|
|
text2speechutt = [AVSpeechUtterance speechUtteranceWithString:speechstring];
|
|
text2speechutt.rate = 0.50f;
|
|
text2speechutt.pitchMultiplier = 0.8f;
|
|
text2speechutt.postUtteranceDelay = 0.2f;
|
|
text2speechutt.volume = (float) vol / 100;
|
|
if (gndr > 0)
|
|
text2speechutt.voice = [AVSpeechSynthesisVoice voiceWithIdentifier:@"com.apple.ttsbundle.siri_female_en-GB_compact"];
|
|
else
|
|
text2speechutt.voice = [AVSpeechSynthesisVoice voiceWithIdentifier:@"com.apple.ttsbundle.siri_male_en-GB_compact"];
|
|
|
|
[synthesizer speakUtterance:text2speechutt];
|
|
#endif /* SND_SPEECH */
|
|
}
|
|
|
|
static int
|
|
affiliate(int32_t id, const char *soundname)
|
|
{
|
|
if (!soundname || id <= se_zero_invalid || id >= SIZE(affiliation))
|
|
return 0;
|
|
|
|
if (!affiliation[id]) {
|
|
affiliation[id] = id;
|
|
soundstring[id] = [NSString stringWithUTF8String:soundname];
|
|
seSound[id] = [NSSound soundNamed:soundstring[id]];
|
|
}
|
|
return 1;
|
|
}
|
|
/* end of macsound.m */
|