Skip to content

Commit d5a88d9

Browse files
committed
Script API: add Game.PlayVoiceClipAsType()
1 parent 7d9aa43 commit d5a88d9

File tree

12 files changed

+140
-61
lines changed

12 files changed

+140
-61
lines changed

Common/ac/dynobj/scriptaudioclip.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,49 @@
1414

1515
#include "ac/dynobj/scriptaudioclip.h"
1616
#include "util/stream.h"
17+
#include "util/string_compat.h"
1718

1819
using namespace AGS::Common;
1920

21+
/*static*/ AudioFileType ScriptAudioClip::GetAudioFileTypeFromExt(const char *ext)
22+
{
23+
if (ags_stricmp(ext, "mp3") == 0)
24+
return eAudioFileMP3;
25+
else if (ags_stricmp(ext, "wav") == 0)
26+
return eAudioFileWAV;
27+
else if (ags_stricmp(ext, "voc") == 0)
28+
return eAudioFileVOC;
29+
else if (ags_stricmp(ext, "mid") == 0)
30+
return eAudioFileMIDI;
31+
else if ((ags_stricmp(ext, "mod") == 0) || (ags_stricmp(ext, "xm") == 0)
32+
|| (ags_stricmp(ext, "s3m") == 0) || (ags_stricmp(ext, "it") == 0))
33+
return eAudioFileMOD;
34+
else if (ags_stricmp(ext, "ogg") == 0)
35+
return eAudioFileOGG;
36+
return eAudioFileUndefined;
37+
}
38+
39+
/*static */ const char *ScriptAudioClip::GetExtFromAudioFileType(AudioFileType filetype)
40+
{
41+
switch (filetype)
42+
{
43+
case eAudioFileOGG:
44+
return "ogg";
45+
case eAudioFileMP3:
46+
return "mp3";
47+
case eAudioFileWAV:
48+
return "wav";
49+
case eAudioFileVOC:
50+
return "voc";
51+
case eAudioFileMIDI:
52+
return "mid";
53+
case eAudioFileMOD:
54+
return "mod";
55+
default:
56+
return "";
57+
}
58+
}
59+
2060
void ScriptAudioClip::ReadFromFile(Stream *in)
2161
{
2262
id = in->ReadInt32();

Common/ac/dynobj/scriptaudioclip.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
namespace AGS { namespace Common { class Stream; } }
2525
using namespace AGS; // FIXME later
2626

27-
enum AudioFileType {
27+
enum AudioFileType
28+
{
29+
eAudioFileUndefined = 0,
2830
eAudioFileOGG = 1,
2931
eAudioFileMP3 = 2,
3032
eAudioFileWAV = 3,
@@ -45,17 +47,21 @@ enum AudioClipBundle
4547
#define LEGACY_AUDIOCLIP_SCRIPTNAMELENGTH 30
4648
#define LEGACY_AUDIOCLIP_FILENAMELENGTH 15
4749

48-
struct ScriptAudioClip {
49-
int id = 0;
50+
struct ScriptAudioClip
51+
{
52+
int id = -1;
5053
Common::String scriptName;
5154
Common::String fileName;
5255
uint8_t bundlingType = kAudioBundle_Undefined;
5356
uint8_t type = AUDIOTYPE_UNDEFINED;
54-
AudioFileType fileType = eAudioFileOGG;
57+
AudioFileType fileType = eAudioFileUndefined;
5558
char defaultRepeat = 0;
5659
short defaultPriority = 50;
5760
short defaultVolume = 100;
5861

62+
static AudioFileType GetAudioFileTypeFromExt(const char *ext);
63+
static const char *GetExtFromAudioFileType(AudioFileType filetype);
64+
5965
void ReadFromFile(Common::Stream *in);
6066
};
6167

Common/core/assetmanager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ struct AssetPath
7272
String Filter;
7373

7474
AssetPath(const String &name = "", const String &filter = "") : Name(name), Filter(filter) {}
75+
76+
operator bool() const { return !Name.IsEmpty(); }
7577
};
7678

7779
// AssetLibEntry describes AssetLibrary registered in the AssetManager,

Common/game/main_game_file.cpp

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -425,29 +425,17 @@ void BuildAudioClipArray(const std::vector<String> &assets, std::vector<ScriptAu
425425
int temp_number;
426426
char temp_extension[10];
427427

428-
// FIXME: use audio type constants instead of obscure numeric literals
429428
for (const String &asset : assets)
430429
{
431430
if (sscanf(asset.GetCStr(), "%5s%d.%3s", temp_name, &temp_number, temp_extension) != 3)
432431
continue;
433432

434-
ScriptAudioClip clip;
435-
if (ags_stricmp(temp_extension, "mp3") == 0)
436-
clip.fileType = eAudioFileMP3;
437-
else if (ags_stricmp(temp_extension, "wav") == 0)
438-
clip.fileType = eAudioFileWAV;
439-
else if (ags_stricmp(temp_extension, "voc") == 0)
440-
clip.fileType = eAudioFileVOC;
441-
else if (ags_stricmp(temp_extension, "mid") == 0)
442-
clip.fileType = eAudioFileMIDI;
443-
else if ((ags_stricmp(temp_extension, "mod") == 0) || (ags_stricmp(temp_extension, "xm") == 0)
444-
|| (ags_stricmp(temp_extension, "s3m") == 0) || (ags_stricmp(temp_extension, "it") == 0))
445-
clip.fileType = eAudioFileMOD;
446-
else if (ags_stricmp(temp_extension, "ogg") == 0)
447-
clip.fileType = eAudioFileOGG;
448-
else
433+
AudioFileType file_type = ScriptAudioClip::GetAudioFileTypeFromExt(temp_extension);
434+
if (file_type == eAudioFileUndefined)
449435
continue;
450436

437+
ScriptAudioClip clip;
438+
clip.fileType = file_type;
451439
if (ags_stricmp(temp_name, "music") == 0)
452440
{
453441
clip.scriptName.Format("aMusic%d", temp_number);

Editor/AGS.Editor/Resources/agsdefns.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3258,6 +3258,8 @@ builtin struct Game {
32583258
import static readonly attribute bool InBlockingWait;
32593259
#endif // SCRIPT_API_v362
32603260
#ifdef SCRIPT_API_v363
3261+
/// Play speech voice-over in non-blocking mode, using certain AudioType settings, and optionally putting it on a particular channel.
3262+
import static AudioChannel* PlayVoiceClipAsType(Character*, int cue, AudioType type, int chan=SCR_NO_VALUE, AudioPriority=SCR_NO_VALUE, RepeatStyle=SCR_NO_VALUE);
32613263
/// Gets/sets game's running speed, in frames per second.
32623264
import static attribute int Speed;
32633265
/// Gets number of game's ticks (updates) passed since the game start.

Engine/ac/game.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,29 @@ int Game_InBlockingWait()
944944
return IsInWaitMode();
945945
}
946946

947+
ScriptAudioChannel *Game_PlayVoiceClip(CharacterInfo *ch, int sndid, bool as_speech)
948+
{
949+
if (!play_voice_nonblocking(ch ? ch->index_id : -1 /* narrator */, sndid, as_speech))
950+
return nullptr;
951+
return &scrAudioChannel[SCHAN_SPEECH];
952+
}
953+
954+
ScriptAudioChannel *Game_PlayVoiceClipAsType(CharacterInfo *ch, int sndid, int audio_type, int chan, int priority, int repeat)
955+
{
956+
if (audio_type <= AUDIOTYPE_SPEECH || static_cast<uint32_t>(audio_type) >= game.audioClipTypes.size())
957+
quitprintf("!AudioClip.PlayAsType: invalid audio type %d, the range is %u - %u",
958+
audio_type, AUDIOTYPE_SPEECH + 1, static_cast<uint32_t>(game.audioClipTypes.size() - 1));
959+
if (chan != SCR_NO_VALUE && (chan < NUM_SPEECH_CHANS || chan >= game.numGameChannels))
960+
quitprintf("!AudioClip.PlayAsType: invalid channel %d, the range is %d - %d",
961+
chan, NUM_SPEECH_CHANS, game.numGameChannels - 1);
962+
if (chan == SCR_NO_VALUE)
963+
chan = -1;
964+
965+
if (!play_voice_clip_as_type(ch ? ch->index_id : -1 /* narrator */, sndid, audio_type, chan, priority, repeat))
966+
return nullptr;
967+
return &scrAudioChannel[SCHAN_SPEECH];
968+
}
969+
947970
void Game_PrecacheSprite(int sprnum)
948971
{
949972
const auto tp_start = FastClock::now();
@@ -2100,7 +2123,12 @@ RuntimeScriptValue Sc_Game_IsPluginLoaded(const RuntimeScriptValue *params, int3
21002123

21012124
RuntimeScriptValue Sc_Game_PlayVoiceClip(const RuntimeScriptValue *params, int32_t param_count)
21022125
{
2103-
API_SCALL_OBJ_POBJ_PINT_PBOOL(ScriptAudioChannel, ccDynamicAudio, PlayVoiceClip, CharacterInfo);
2126+
API_SCALL_OBJ_POBJ_PINT_PBOOL(ScriptAudioChannel, ccDynamicAudio, Game_PlayVoiceClip, CharacterInfo);
2127+
}
2128+
2129+
RuntimeScriptValue Sc_Game_PlayVoiceClipAsType(const RuntimeScriptValue *params, int32_t param_count)
2130+
{
2131+
API_SCALL_OBJ_POBJ_PINT5(ScriptAudioChannel, ccDynamicAudio, Game_PlayVoiceClipAsType, CharacterInfo);
21042132
}
21052133

21062134
RuntimeScriptValue Sc_Game_GetCamera(const RuntimeScriptValue *params, int32_t param_count)
@@ -2212,7 +2240,8 @@ void RegisterGameAPI()
22122240
{ "Game::StopSound^1", API_FN_PAIR(StopAllSounds) },
22132241
{ "Game::IsPluginLoaded", Sc_Game_IsPluginLoaded, pl_is_plugin_loaded },
22142242
{ "Game::ChangeSpeechVox", API_FN_PAIR(Game_ChangeSpeechVox) },
2215-
{ "Game::PlayVoiceClip", Sc_Game_PlayVoiceClip, PlayVoiceClip },
2243+
{ "Game::PlayVoiceClip", API_FN_PAIR(Game_PlayVoiceClip) },
2244+
{ "Game::PlayVoiceClipAsType", API_FN_PAIR(Game_PlayVoiceClipAsType) },
22162245
{ "Game::SimulateKeyPress", API_FN_PAIR(Game_SimulateKeyPress) },
22172246
{ "Game::ResetDoOnceOnly", API_FN_PAIR(Game_ResetDoOnceOnly) },
22182247
{ "Game::PrecacheSprite", API_FN_PAIR(Game_PrecacheSprite) },

Engine/ac/gamestate.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,11 @@ bool GamePlayState::ShouldPlayVoiceSpeech() const
449449
(play.speech_mode != kSpeech_TextOnly) && (play.voice_avail);
450450
}
451451

452+
bool GamePlayState::ShouldPlayVoiceSpeechNonBlocking() const
453+
{
454+
return !play.fast_forward && (play.voice_avail);
455+
}
456+
452457
void GamePlayState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameStateSvgVersion svg_ver, RestoredData &r_data)
453458
{
454459
score = in->ReadInt32();

Engine/ac/gamestate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ struct GamePlayState
468468
bool IsNonBlockingVoiceSpeech() const;
469469
// Speech helpers
470470
bool ShouldPlayVoiceSpeech() const;
471+
bool ShouldPlayVoiceSpeechNonBlocking() const;
471472

472473
//
473474
// Serialization

Engine/ac/global_audio.cpp

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -446,13 +446,6 @@ int IsMusicVoxAvailable () {
446446

447447
extern ScriptAudioChannel scrAudioChannel[MAX_GAME_CHANNELS];
448448

449-
ScriptAudioChannel *PlayVoiceClip(CharacterInfo *ch, int sndid, bool as_speech)
450-
{
451-
if (!play_voice_nonblocking(ch ? ch->index_id : -1 /* narrator */, sndid, as_speech))
452-
return NULL;
453-
return &scrAudioChannel[SCHAN_SPEECH];
454-
}
455-
456449
// Construct an asset name for the voice-over clip for the given character and cue id
457450
static String get_cue_filename(int charid, int sndid, bool old_style)
458451
{
@@ -477,15 +470,11 @@ static String get_cue_filename(int charid, int sndid, bool old_style)
477470
return Path::ConcatPaths(get_voice_assetpath(), asset_filename);
478471
}
479472

480-
// Play voice-over clip on the common channel;
481-
// voice_name should be bare clip name without extension
482-
static bool play_voice_clip_on_channel(const String &voice_name)
473+
static AssetPath find_voice_clip(const String &voice_name)
483474
{
484-
stop_and_destroy_channel(SCHAN_SPEECH);
485-
486475
// TODO: perhaps a better algorithm, allow any extension / sound format?
487476
// e.g. make a hashmap matching a voice name to a asset name
488-
std::array<const char*, 3> exts = {{ "mp3", "ogg", "wav" }};
477+
std::array<const char *, 3> exts = { { "mp3", "ogg", "wav" } };
489478
AssetPath apath = get_voice_over_assetpath(voice_name);
490479
bool found = false;
491480
for (auto *ext : exts)
@@ -498,9 +487,22 @@ static bool play_voice_clip_on_channel(const String &voice_name)
498487

499488
if (!found) {
500489
debug_script_warn("Speech file not found: '%s'", voice_name.GetCStr());
501-
return false;
490+
return AssetPath();
502491
}
503492

493+
return apath;
494+
}
495+
496+
// Play voice-over clip on the common channel;
497+
// voice_name should be bare clip name without extension
498+
static bool play_voice_clip_on_channel(const String &voice_name)
499+
{
500+
stop_and_destroy_channel(SCHAN_SPEECH);
501+
502+
AssetPath apath = find_voice_clip(voice_name);
503+
if (!apath)
504+
return false;
505+
504506
std::unique_ptr<SoundClip> voice_clip(load_sound_clip(apath, "", false));
505507
if (voice_clip != nullptr) {
506508
voice_clip->fileName = apath.Name;
@@ -590,16 +592,34 @@ bool play_voice_speech(int charid, int sndid)
590592
bool play_voice_nonblocking(int charid, int sndid, bool as_speech)
591593
{
592594
// don't play voice if we're skipping a cutscene
593-
if (!play.ShouldPlayVoiceSpeech())
595+
if (!play.ShouldPlayVoiceSpeechNonBlocking())
594596
return false;
595597
// don't play voice if there's a blocking speech with voice-over already
596-
if (play.IsBlockingVoiceSpeech())
598+
if (as_speech && play.IsBlockingVoiceSpeech())
597599
return false;
598600

599601
String voice_file = get_cue_filename(charid, sndid, !game.options[OPT_VOICECLIPNAMERULE]);
600602
return play_voice_clip_impl(voice_file, as_speech, false);
601603
}
602604

605+
const ScriptAudioChannel *play_voice_clip_as_type(int charid, int sndid, int type, int chan, int priority, int repeat)
606+
{
607+
// don't play voice if we're skipping a cutscene
608+
if (!play.ShouldPlayVoiceSpeechNonBlocking())
609+
return nullptr;
610+
611+
String voice_file = get_cue_filename(charid, sndid, !game.options[OPT_VOICECLIPNAMERULE]);
612+
AssetPath apath = find_voice_clip(voice_file);
613+
if (!apath)
614+
return nullptr;
615+
616+
ScriptAudioClip sc_clip; // pass temp dummy clip
617+
sc_clip.fileName = apath.Name;
618+
sc_clip.fileType = ScriptAudioClip::GetAudioFileTypeFromExt(Path::GetFileExtension(apath.Name).GetCStr());
619+
sc_clip.bundlingType = kAudioBundle_SpeechVox;
620+
return play_audio_clip(AudioPlayback(&sc_clip, type), chan, priority, repeat, 0, false);
621+
}
622+
603623
void stop_voice_speech()
604624
{
605625
if (!play.speech_has_voice)

Engine/ac/global_audio.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,14 @@ int IsMusicVoxAvailable ();
5151

5252
struct CharacterInfo;
5353
struct ScriptAudioChannel;
54-
// Starts voice-over playback and returns audio channel it is played on;
55-
// as_speech flag determines whether engine should apply speech-related logic
56-
// as well, such as temporary volume reduction.
57-
ScriptAudioChannel *PlayVoiceClip(CharacterInfo *ch, int sndid, bool as_speech);
5854

5955
//=============================================================================
6056
// Play voice-over for the active blocking speech and initialize relevant data
6157
bool play_voice_speech(int charid, int sndid);
6258
// Play voice-over clip in non-blocking manner
6359
bool play_voice_nonblocking(int charid, int sndid, bool as_speech);
60+
// Play voice-over clip in non-blocking manner, as a given audio type, optionally selecting an explicit channel
61+
const ScriptAudioChannel *play_voice_clip_as_type(int charid, int sndid, int type, int chan, int priority, int repeat);
6462
// Stop voice-over for the active speech and reset relevant data
6563
void stop_voice_speech();
6664
// Stop non-blocking voice-over and revert audio volumes if necessary

0 commit comments

Comments
 (0)