Improved and fixed the playback of alternative sound files

Added a new mode to play_sfall_sound script function.

Minor code corrections.
This commit is contained in:
NovaRain
2020-03-17 13:06:34 +08:00
parent eb777e6d19
commit 288a8fedfd
9 changed files with 184 additions and 98 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ get/set_proto_data are used to manipulate the in memory copies of the .pro files
the list_xxx functions can be used to loop over all items on a map. list_begin takes an argument telling sfall what you want to list. (Defined in sfall.h) It returns a list pointer, which you iterate through with list_next. Finally, when you've finished with the list use list_end on it. Not calling list_end will result in a memory leak. Alternatively, use list_as_array to get the whole list at once as a temp array variable, which can be looped over using len_array and which you don't need to remember to free afterwards.
play_sfall_sound and stop_sfall_sound are used to play mp3/wav/wma files. The path given is relative to the Fallout folder. Specify loop as 1 to loop the file continuously, or 0 otherwise. If you don't wish to loop, play_sfall_sound returns 0. If you do loop, it returns an id which can be passed back to stop_sfall_sound when you want to stop the effect. All sounds effects will be stopped on game reload, looping or not. These functions do not require 'AllowDShowSound' to be set to 1 in ddraw.ini.
play_sfall_sound and stop_sfall_sound are used to play mp3/wav/wma files. The path given is relative to the Fallout folder. Specify mode as 1 to loop the file continuously, 2 to replace the current background game music with playing the specified file in loop mode, or 0 to play the file once. If you don't wish to loop, play_sfall_sound returns 0. If you do loop, it returns an id which can be passed back to stop_sfall_sound when you want to stop the effect. All sounds effects will be stopped on game reload, looping or not. These functions do not require 'AllowDShowSound' to be set to 1 in ddraw.ini.
arrays are created and manipulated with the xxx_array functions. An array must first be created with create_array or temp_array, specifying how many data elements the array can hold. You can store any of ints, floats and strings in an array, and can mix all 3 in a single array. The id returned by create/temp_array can then be used with the other array functions. Arrays are shared between all scripts. (i.e. you can call create_array from one script, and then use the returned id from another script.) They are also saved across savegames. You must remember to free any arrays you create with create_array when you are done with them, or you will leak memory. arrays created with temp_array will be automatically freed at the end of the frame. These functions are safe, in that supplying a bad id or trying to access out of range elements will not crash the script. create_array is the only function that returns a permanent array, all other functions which return arrays (string_split, list_as_array etc,) all return temp arrays. You can use fix_array to make a temp array permanent.
+2 -2
View File
@@ -296,8 +296,8 @@
0x8228 - int get_attack_type
0x822b - int play_sfall_sound(string file, bool loop)
0x822c - void stop_sfall_sound(int id)
0x822b - int play_sfall_sound(string file, int mode)
0x822c - void stop_sfall_sound(int soundID)
0x8235 - array string_split(string string, string split)
0x8237 - int atoi(string string)
+1
View File
@@ -154,6 +154,7 @@ WRAP_WATCOM_FUNC3(long, register_object_turn_towards, GameObject*, object, long,
WRAP_WATCOM_FUNC2(long, roll_random, long, minValue, long, maxValue)
WRAP_WATCOM_FUNC2(long, scr_new, long*, scriptID, long, sType)
WRAP_WATCOM_FUNC1(long, scr_remove, long, scriptID)
WRAP_WATCOM_FUNC1(void, set_focus_func, void*, func)
WRAP_WATCOM_FUNC2(long, skill_dec_point_force, GameObject*, critter, long, skill)
WRAP_WATCOM_FUNC2(long, skill_inc_point_force, GameObject*, critter, long, skill)
WRAP_WATCOM_FUNC1(long, skill_is_tagged, long, skill)
+3
View File
@@ -10,6 +10,7 @@ VARA(art, Art, 11)
VAR_(art_name, DWORD)
VAR_(art_vault_guy_num, DWORD)
VAR_(art_vault_person_nums, DWORD)
VAR_(background_volume, DWORD)
VAR_(bckgnd, BYTE*)
VAR_(black_palette, DWORD)
VAR_(BlueColor, BYTE)
@@ -110,6 +111,7 @@ VAR_(main_window, DWORD)
VAR_(map_elevation, DWORD)
VAR_(map_global_vars, long*) // array
VAR_(master_db_handle, PathNode*)
VAR_(master_volume, DWORD)
VAR_(max, DWORD)
VAR_(maxScriptNum, long)
VAR_(Meet_Frank_Horrigan, bool)
@@ -175,6 +177,7 @@ VAR_(scriptListInfo, ScriptListInfoItem*) // dynamic array
VARA(skill_data, SkillInfo, SKILL_count)
VARC(skldxwin, DWORD)
VAR_(slot_cursor, DWORD)
VAR_(sndfx_volume, DWORD)
VAR_(sneak_working, DWORD) // DWORD var
VAR_(sound_music_path1, char*)
VAR_(sound_music_path2, char*)
+1 -1
View File
@@ -217,7 +217,7 @@ static long LoadArraysOld(HANDLE h) {
varN.flags = 0;
varN.val.resize(var.len);
for (size_t j = 0; j < var.len; j++) {
switch (var.types[j]) {
switch ((DataType)var.types[j]) {
case DataType::INT:
varN.val[j].set(*(long*)(&var.data[var.datalen * j]));
break;
+3 -1
View File
@@ -616,7 +616,9 @@ void sf_get_attack_type(OpcodeContext& ctx) {
}
void sf_play_sfall_sound(OpcodeContext& ctx) {
DWORD soundID = Sound::PlaySfallSound(ctx.arg(0).strValue(), ctx.arg(1).asBool());
DWORD soundID = 0;
long mode = ctx.arg(1).rawValue();
if (mode >= 0) soundID = Sound::PlaySfallSound(ctx.arg(0).strValue(), mode);
ctx.setReturn(soundID);
}
+169 -91
View File
@@ -30,6 +30,26 @@ namespace sfall
#define SAFERELEASE(a) { if (a) { a->Release(); } }
enum SoundType : unsigned long {
sfx_loop = 0, // sfall
sfx_single = 1, // sfall
game_sfx = 2,
game_master = 3
};
enum SoundMode : long {
single_play = 0, // uses sfx volume
loop_play = 1, // uses sfx volume
music_play = 2, // uses background volume
engine_music_play = -1 // used when playing game music in an alternative format (not used from scripts)
};
enum SoundFlags : unsigned long {
looping = 0x10000000,
restore = 0x40000000, // restore background game music on stop play
engine = 0x80000000
};
struct sDSSound {
DWORD id; // should be first
IGraphBuilder *pGraph;
@@ -46,7 +66,7 @@ DWORD playID = 0;
DWORD loopID = 0;
static HWND soundwindow = 0;
static sDSSound* musicLoopPtr = nullptr;
static sDSSound* backgroundMusic = nullptr; // currently playing sfall background music
//static char playingMusicFile[256];
static void FreeSound(sDSSound* sound) {
@@ -64,7 +84,7 @@ static void WipeSounds() {
for (size_t i = 0; i < loopingSounds.size(); i++) FreeSound(loopingSounds[i]);
playingSounds.clear();
loopingSounds.clear();
musicLoopPtr = nullptr;
backgroundMusic = nullptr;
playID = 0;
loopID = 0;
}
@@ -73,7 +93,8 @@ LRESULT CALLBACK SoundWndProc(HWND wnd, UINT msg, WPARAM w, LPARAM l) {
if (msg == WM_APP) {
DWORD id = l;
sDSSound* sound = nullptr;
if (id & 0x80000000) {
long index = -1;
if (id & SoundFlags::looping) {
for (size_t i = 0; i < loopingSounds.size(); i++) {
if (loopingSounds[i]->id == id) {
sound = loopingSounds[i];
@@ -84,28 +105,24 @@ LRESULT CALLBACK SoundWndProc(HWND wnd, UINT msg, WPARAM w, LPARAM l) {
for (size_t i = 0; i < playingSounds.size(); i++) {
if (playingSounds[i]->id == id) {
sound = playingSounds[i];
index = i;
break;
}
}
}
if (!sound) return 0;
LONG e = 0;
LONG_PTR p1 = 0, p2 = 0;
while (!FAILED(sound->pEvent->GetEvent(&e, &p1, &p2, 0))) {
if (sound && !FAILED(sound->pEvent->GetEvent(&e, &p1, &p2, 0))) {
sound->pEvent->FreeEventParams(e, p1, p2);
if (e == EC_COMPLETE) {
if (id & 0x80000000) {
if (id & SoundFlags::looping) {
LONGLONG pos = 0;
sound->pSeek->SetPositions(&pos, AM_SEEKING_AbsolutePositioning, 0, AM_SEEKING_NoPositioning);
sound->pControl->Run();
} else {
for (size_t i = 0; i < playingSounds.size(); i++) {
if (playingSounds[i] == sound) {
FreeSound(sound);
playingSounds.erase(playingSounds.begin() + i);
return 0;
}
}
playingSounds.erase(playingSounds.begin() + index);
}
}
}
@@ -119,7 +136,8 @@ static void CreateSndWnd() {
if (Graphics::mode == 0) CoInitialize(0);
WNDCLASSEX wcx;
memset(&wcx, 0, sizeof(wcx));
std::memset(&wcx, 0, sizeof(wcx));
wcx.cbSize = sizeof(wcx);
wcx.lpfnWndProc = SoundWndProc;
wcx.hInstance = GetModuleHandleA(0);
@@ -130,16 +148,28 @@ static void CreateSndWnd() {
dlogr(" Done", DL_INIT);
}
void __stdcall PauseSfallSound(sDSSound* ptr) {
ptr->pControl->Pause();
void __stdcall PauseSfallSound(sDSSound* sound) {
sound->pControl->Pause();
}
void __stdcall ResumeSfallSound(sDSSound* ptr) {
ptr->pControl->Run();
void __stdcall ResumeSfallSound(sDSSound* sound) {
sound->pControl->Run();
}
void __stdcall PauseAllSfallSound() {
for (sDSSound* sound : loopingSounds) sound->pControl->Pause();
for (sDSSound* sound : playingSounds) sound->pControl->Pause();
}
void __stdcall ResumeAllSfallSound() {
for (sDSSound* sound : loopingSounds) sound->pControl->Run();
for (sDSSound* sound : playingSounds) sound->pControl->Run();
}
static long CalculateVolumeDB(long masterVolume, long passVolume) {
const int volOffset = -100; // maximum volume
if (masterVolume <= 0 || passVolume <= 0) return -9999; // mute
const int volOffset = -100; // adjust the maximum volume
const int minVolume = -2048;
float multiply = (32767.0f / masterVolume);
@@ -148,32 +178,34 @@ static long CalculateVolumeDB(long masterVolume, long passVolume) {
return static_cast<long>(minVolume - volume) + volOffset;
}
static void __cdecl SfallSoundVolume(sDSSound* sound, int type, long passVolume) {
long loopVolume, sfxVolume, masterVolume = *(DWORD*)FO_VAR_master_volume;
/*
master_volume: sound=0 type=game_master passVolume=background_volume
background_volume: sound=backgroundMusic type=background_loop passVolume=background_volume
sfx_volume: sound=0 type=game_sfx passVolume=sndfx_volume
*/
static void __cdecl SfallSoundVolume(sDSSound* sound, SoundType type, long passVolume) {
long volume, sfxVolume, masterVolume = fo::var::master_volume;
if (masterVolume > 0 && passVolume > 0) {
loopVolume = CalculateVolumeDB(masterVolume, passVolume);
if (type = 2) sfxVolume = CalculateVolumeDB(masterVolume, *(DWORD*)FO_VAR_sndfx_volume);
} else {
if (masterVolume == 0) {
loopVolume = sfxVolume = -9999; // mute
} else if (type = 0) { // for music
if (musicLoopPtr) {
Sound::StopSfallSound(musicLoopPtr->id);
musicLoopPtr = nullptr;
}
return;
}
}
volume = CalculateVolumeDB(masterVolume, passVolume);
if (type == SoundType::game_master) sfxVolume = CalculateVolumeDB(masterVolume, fo::var::sndfx_volume);
if (sound) {
sound->pAudio->put_Volume(loopVolume);
sound->pAudio->put_Volume(volume);
} else {
for (DWORD i = 0; i < loopingSounds.size(); i++) {
loopingSounds[i]->pAudio->put_Volume(loopVolume);
if (type == SoundType::game_sfx) sfxVolume = volume;
bool sfx_or_master = (type == SoundType::game_sfx || type == SoundType::game_master);
for (size_t i = 0; i < loopingSounds.size(); i++) {
if (sfx_or_master) {
if ((loopingSounds[i]->id & 0xF0000000) == SoundFlags::looping) { // only for sfx loop mode
loopingSounds[i]->pAudio->put_Volume(sfxVolume);
continue;
} else
if (type == SoundType::game_sfx) continue;
}
if (type = 2) { // sfx
for (DWORD i = 0; i < playingSounds.size(); i++) {
loopingSounds[i]->pAudio->put_Volume(volume);
}
if (sfx_or_master) {
for (size_t i = 0; i < playingSounds.size(); i++) {
playingSounds[i]->pAudio->put_Volume(sfxVolume);
}
}
@@ -181,27 +213,29 @@ static void __cdecl SfallSoundVolume(sDSSound* sound, int type, long passVolume)
}
static bool IsMute(bool type) {
//if (*(DWORD*)FO_VAR_master_volume == 0) return true;
//if (fo::var::master_volume == 0) return true;
int value;
if (type) {
value = *(DWORD*)FO_VAR_background_volume;
value = fo::var::background_volume;
} else {
value = *(DWORD*)FO_VAR_sndfx_volume;
value = fo::var::sndfx_volume;
}
return (value == 0);
}
static sDSSound* PlayingSound(wchar_t* path, bool loop) {
/*
single_play: mode 0 - single sound playback (with sound overlay)
loop_play: mode 1 - loop sound playback (with sound overlay)
music_play: mode 2 - loop sound playback with the background game music turned off
*/
static sDSSound* PlayingSound(wchar_t* path, SoundMode mode) {
if (!soundwindow) CreateSndWnd();
if (IsMute(loop)) return nullptr;
bool isLoop = (mode != SoundMode::single_play);
if (IsMute(isLoop)) return nullptr;
sDSSound* sound = new sDSSound();
DWORD id = (loop) ? ++loopID : ++playID;
if (loop) id |= 0x80000000;
sound->id = id;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC, IID_IGraphBuilder, (void**)&sound->pGraph);
if (hr != S_OK) {
dlog_f("Error CoCreateInstance: %d", DL_INIT, hr);
@@ -209,24 +243,38 @@ static sDSSound* PlayingSound(wchar_t* path, bool loop) {
}
sound->pGraph->QueryInterface(IID_IMediaControl, (void**)&sound->pControl);
if (loop)
if (isLoop)
sound->pGraph->QueryInterface(IID_IMediaSeeking, (void**)&sound->pSeek);
else
sound->pSeek = nullptr;
sound->pGraph->QueryInterface(IID_IMediaEventEx, (void**)&sound->pEvent);
sound->pEvent->SetNotifyWindow((OAHWND)soundwindow, WM_APP, id);
sound->pGraph->QueryInterface(IID_IBasicAudio, (void**)&sound->pAudio);
sound->id = (isLoop) ? ++loopID : ++playID;
if (isLoop) sound->id |= SoundFlags::looping; // sfx loop sound
if (mode == SoundMode::music_play) {
sound->id |= SoundFlags::restore; // restore background game music on stop
if (backgroundMusic)
Sound::StopSfallSound(backgroundMusic->id);
else {
__asm call fo::funcoffs::gsound_background_stop_;
}
backgroundMusic = sound;
}
else if (mode == SoundMode::engine_music_play) sound->id |= SoundFlags::engine; // engine play
sound->pEvent->SetNotifyWindow((OAHWND)soundwindow, WM_APP, sound->id);
sound->pControl->RenderFile(path);
sound->pControl->Run();
if (loop) {
if (isLoop) {
loopingSounds.push_back(sound);
SfallSoundVolume(sound, 0, *(DWORD*)FO_VAR_background_volume); // music
SfallSoundVolume(sound, SoundType::sfx_loop, (mode == SoundMode::loop_play) ? fo::var::sndfx_volume : fo::var::background_volume);
} else {
playingSounds.push_back(sound);
SfallSoundVolume(sound, 1, *(DWORD*)FO_VAR_sndfx_volume);
SfallSoundVolume(sound, SoundType::sfx_single, fo::var::sndfx_volume);
}
return sound;
}
@@ -249,19 +297,19 @@ static bool __cdecl SoundFileLoad(DWORD called, const char* path) {
}
bool music = (called == 0x45092B); // from gsound_background_play_
if (music && musicLoopPtr != nullptr) {
if (music && backgroundMusic != nullptr) {
//if (found && strcmp(path, playingMusicFile) == 0) return true; // don't stop music
Sound::StopSfallSound(musicLoopPtr->id);
musicLoopPtr = nullptr;
Sound::StopSfallSound(backgroundMusic->id);
backgroundMusic = nullptr;
}
if (!isExist) return false;
if (music) {
//strcpy_s(playingMusicFile, path);
musicLoopPtr = PlayingSound(buf, true); // music loop
if (!musicLoopPtr) return false;
backgroundMusic = PlayingSound(buf, SoundMode::engine_music_play); // background music loop
if (!backgroundMusic) return false;
} else {
if (!PlayingSound(buf, false)) return false;
if (!PlayingSound(buf, SoundMode::single_play)) return false;
}
return true;
}
@@ -277,21 +325,24 @@ static void __fastcall MakeMusicPath(const char* file) {
SoundFileLoad(0x45092B, pathBuf);
}
DWORD Sound::PlaySfallSound(const char* path, bool loop) {
DWORD Sound::PlaySfallSound(const char* path, long mode) {
wchar_t buf[256];
mbstowcs_s(0, buf, path, 256);
sDSSound* sound = PlayingSound(buf, loop);
return (loop && sound) ? sound->id : 0;
sDSSound* sound = PlayingSound(buf, (SoundMode)mode);
return (mode && sound) ? sound->id : 0;
}
void __stdcall Sound::StopSfallSound(DWORD id) {
if (!id) return;
if (!(id & 0xFFFFFF)) return;
for (size_t i = 0; i < loopingSounds.size(); i++) {
if (loopingSounds[i]->id == id) {
sDSSound* sound = loopingSounds[i];
sound->pControl->Stop();
FreeSound(sound);
loopingSounds.erase(loopingSounds.begin() + i);
if (!(id & SoundFlags::engine) && id & SoundFlags::restore) {
__asm call fo::funcoffs::gsound_background_restart_last_;
}
return;
}
}
@@ -333,16 +384,16 @@ static void __declspec(naked) gsound_background_play_hook() {
static void __declspec(naked) gmovie_play_hook_stop() {
__asm {
mov eax, musicLoopPtr;
mov eax, backgroundMusic;
test eax, eax;
jz skip;
mov eax, [eax]; // musicLoopPtr->id
mov eax, [eax]; // backgroundMusic->id
push ecx;
push edx;
push eax;
call Sound::StopSfallSound;
xor eax, eax;
mov musicLoopPtr, eax;
mov backgroundMusic, eax;
pop edx;
pop ecx;
retn;
@@ -353,35 +404,31 @@ skip:
static void __declspec(naked) gmovie_play_hook_pause() {
__asm {
mov eax, musicLoopPtr;
test eax, eax;
jz skip;
push ecx;
push edx;
push eax;
call PauseSfallSound;
call PauseAllSfallSound;
pop edx;
pop ecx;
retn;
skip:
cmp dword ptr backgroundMusic, 0;
jnz skip;
jmp fo::funcoffs::gsound_background_pause_;
skip:
retn;
}
}
static void __declspec(naked) gmovie_play_hook_unpause() {
__asm {
mov eax, musicLoopPtr;
test eax, eax;
jz skip;
push ecx;
push edx;
push eax;
call ResumeSfallSound;
call ResumeAllSfallSound;
pop edx;
pop ecx;
retn;
skip:
cmp dword ptr backgroundMusic, 0;
jnz skip;
jmp fo::funcoffs::gsound_background_unpause_;
skip:
retn;
}
}
@@ -389,13 +436,13 @@ static void __declspec(naked) gsound_background_volume_set_hack() {
__asm {
mov dword ptr ds:[FO_VAR_background_volume], eax;
push ecx;
mov ecx, musicLoopPtr;
mov ecx, backgroundMusic;
test ecx, ecx;
jz skip;
push edx;
push eax;
push 0;
push ecx;
push 0; // SoundType::sfx_loop
push ecx; // set volume for background music
call SfallSoundVolume;
add esp, 8;
pop eax;
@@ -413,8 +460,8 @@ static void __declspec(naked) gsound_master_volume_set_hack() {
push ecx;
push edx;
push dword ptr ds:[FO_VAR_background_volume];
push 2;
push 0;
push 3; // SoundType::game_master
push 0; // set volume for all sounds
call SfallSoundVolume;
add esp, 12;
pop edx;
@@ -424,6 +471,33 @@ static void __declspec(naked) gsound_master_volume_set_hack() {
}
}
static void __declspec(naked) gsound_set_sfx_volume_hack() {
__asm {
push ecx;
push edx;
mov dword ptr ds:[FO_VAR_sndfx_volume], eax;
push eax;
push 2; // SoundType::game_sfx
push 0; // set volume for all sounds
call SfallSoundVolume;
add esp, 12;
pop edx;
pop ecx;
retn;
}
}
static void SoundLostFocus() {
long isActive;
__asm mov isActive, eax;
if (isActive) {
ResumeAllSfallSound();
} else {
PauseAllSfallSound();
}
}
///////////////////////////////////////////////////////////////////////////////
static char attackerSnd[9] = {0};
@@ -478,14 +552,18 @@ void Sound::init() {
LoadGameHook::OnGameReset() += WipeSounds;
LoadGameHook::OnBeforeGameClose() += WipeSounds;
int allowDShowSound = GetConfigInt("Sound", "AllowDShowSound", 0);
if (allowDShowSound > 0) {
MakeJump(0x4AD499, soundLoad_hack);
HookCalls(gmovie_play_hook_stop, {0x44E80A, 0x445280}); // only play looping music
HookCalls(gmovie_play_hook_pause, {0x44E816});
HookCalls(gmovie_play_hook_unpause, {0x44EA84});
HookCall(0x44E816, gmovie_play_hook_pause);
HookCall(0x44EA84, gmovie_play_hook_unpause);
MakeCall(0x450525, gsound_background_volume_set_hack);
MakeCall(0x4503CA, gsound_master_volume_set_hack, 1);
MakeCall(0x45042C, gsound_set_sfx_volume_hack);
// Pause and resume sound playback when the game loses focus
fo::func::set_focus_func(SoundLostFocus);
if (int allowDShowSound = GetConfigInt("Sound", "AllowDShowSound", 0) > 0) {
MakeJump(0x4AD499, soundLoad_hack);
HookCalls(gmovie_play_hook_stop, {0x44E80A, 0x445280}); // only play looping music
if (allowDShowSound > 1) {
HookCall(0x450851, gsound_background_play_hook);
}
+1 -1
View File
@@ -29,7 +29,7 @@ public:
void init();
void exit() override;
static DWORD PlaySfallSound(const char* path, bool loop);
static DWORD PlaySfallSound(const char* path, long mode);
static void __stdcall StopSfallSound(DWORD id);
};
+2
View File
@@ -24,7 +24,9 @@
#include <cassert>
#include <string>
#include <list>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include "SafeWrite.h"