diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 479e38a2e..77ffb88e2 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -456,11 +456,13 @@ Examples of sound parameter tables: -- Play locationless on all clients { gain = 1.0, -- default + fade = 0.0, -- default, change to a value > 0 to fade the sound in } -- Play locationless to one player { to_player = name, gain = 1.0, -- default + fade = 0.0, -- default, change to a value > 0 to fade the sound in } -- Play locationless to one player, looped { @@ -2587,6 +2589,11 @@ These functions return the leftover itemstack. * `spec` is a `SimpleSoundSpec` * `parameters` is a sound parameter table * `minetest.sound_stop(handle)` +* `minetest.sound_fade(handle, step, gain)` + * `handle` is a handle returned by minetest.sound_play + * `step` determines how fast a sound will fade. + Negative step will lower the sound volume, positive step will increase the sound volume + * `gain` the target gain for the fade. ### Timing * `minetest.after(time, func, ...)` diff --git a/src/client.cpp b/src/client.cpp index 3c5a70f21..3269c573a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -407,6 +407,7 @@ void Client::step(float dtime) // Step environment m_env.step(dtime); + m_sound->step(dtime); /* Get events diff --git a/src/client.h b/src/client.h index 7cbfadd50..e8db7de44 100644 --- a/src/client.h +++ b/src/client.h @@ -328,6 +328,7 @@ public: void handleCommand_ItemDef(NetworkPacket* pkt); void handleCommand_PlaySound(NetworkPacket* pkt); void handleCommand_StopSound(NetworkPacket* pkt); + void handleCommand_FadeSound(NetworkPacket *pkt); void handleCommand_Privileges(NetworkPacket* pkt); void handleCommand_InventoryFormSpec(NetworkPacket* pkt); void handleCommand_DetachedInventory(NetworkPacket* pkt); diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 1be6e5522..bdcb1dfce 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -109,7 +109,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_EYE_OFFSET", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_EyeOffset }, // 0x52 { "TOCLIENT_DELETE_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeleteParticleSpawner }, // 0x53 { "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54 - null_command_handler, + { "TOCLIENT_FADE_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FadeSound }, // 0x55 null_command_handler, null_command_handler, null_command_handler, diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index defc83f31..a895acc84 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -755,21 +755,39 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) void Client::handleCommand_PlaySound(NetworkPacket* pkt) { + /* + [0] u32 server_id + [4] u16 name length + [6] char name[len] + [ 6 + len] f32 gain + [10 + len] u8 type + [11 + len] (f32 * 3) pos + [23 + len] u16 object_id + [25 + len] bool loop + [26 + len] f32 fade + */ + s32 server_id; std::string name; + float gain; u8 type; // 0=local, 1=positional, 2=object v3f pos; u16 object_id; bool loop; + float fade = 0; *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop; + try { + *pkt >> fade; + } catch (SerializationError &e) {}; + // Start playing int client_id = -1; switch(type) { case 0: // local - client_id = m_sound->playSound(name, loop, gain); + client_id = m_sound->playSound(name, loop, gain, fade); break; case 1: // positional client_id = m_sound->playSoundAt(name, loop, gain, pos); @@ -808,6 +826,21 @@ void Client::handleCommand_StopSound(NetworkPacket* pkt) } } +void Client::handleCommand_FadeSound(NetworkPacket *pkt) +{ + s32 sound_id; + float step; + float gain; + + *pkt >> sound_id >> step >> gain; + + UNORDERED_MAP::iterator i = + m_sounds_server_to_client.find(sound_id); + + if (i != m_sounds_server_to_client.end()) + m_sound->fadeSound(i->second, step, gain); +} + void Client::handleCommand_Privileges(NetworkPacket* pkt) { m_privileges.clear(); diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index a1a4f5bfa..70cad85d8 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -153,9 +153,11 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL VERSION 31: Add tile overlay Stop sending TOSERVER_CLIENT_READY + PROTOCOL VERSION 32: + Add fading sounds */ -#define LATEST_PROTOCOL_VERSION 31 +#define LATEST_PROTOCOL_VERSION 32 // Server's supported network protocol range #define SERVER_PROTOCOL_VERSION_MIN 24 @@ -620,6 +622,13 @@ enum ToClientCommand v2f1000 speed */ + TOCLIENT_FADE_SOUND = 0x55, + /* + s32 sound_id + float step + float gain + */ + TOCLIENT_SRP_BYTES_S_B = 0x60, /* Belonging to AUTH_MECHANISM_LEGACY_PASSWORD and AUTH_MECHANISM_SRP. diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 5fe5af58d..8696ad7cb 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -680,6 +680,7 @@ void read_server_sound_params(lua_State *L, int index, if(lua_istable(L, index)){ getfloatfield(L, index, "gain", params.gain); getstringfield(L, index, "to_player", params.to_player); + getfloatfield(L, index, "fade", params.fade); lua_getfield(L, index, "pos"); if(!lua_isnil(L, -1)){ v3f p = read_v3f(L, -1)*BS; @@ -712,6 +713,7 @@ void read_soundspec(lua_State *L, int index, SimpleSoundSpec &spec) } else if(lua_istable(L, index)){ getstringfield(L, index, "name", spec.name); getfloatfield(L, index, "gain", spec.gain); + getfloatfield(L, index, "fade", spec.fade); } else if(lua_isstring(L, index)){ spec.name = lua_tostring(L, index); } diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index a5fbee765..b0f61a8ca 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -77,6 +77,8 @@ void setfloatfield(lua_State *L, int table, const char *fieldname, float value); void setboolfield(lua_State *L, int table, const char *fieldname, bool value); +void setstringfield(lua_State *L, int table, + const char *fieldname, const char *value); v3f checkFloatPos (lua_State *L, int index); v2f check_v2f (lua_State *L, int index); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 7b723d14c..ea993d7b7 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -455,6 +455,16 @@ int ModApiServer::l_sound_stop(lua_State *L) return 0; } +int ModApiServer::l_sound_fade(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + s32 handle = luaL_checkinteger(L, 1); + float step = luaL_checknumber(L, 2); + float gain = luaL_checknumber(L, 3); + getServer(L)->fadeSound(handle, step, gain); + return 0; +} + // is_singleplayer() int ModApiServer::l_is_singleplayer(lua_State *L) { @@ -518,6 +528,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(show_formspec); API_FCT(sound_play); API_FCT(sound_stop); + API_FCT(sound_fade); API_FCT(get_player_information); API_FCT(get_player_privs); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 3a4a917c0..251a0ce89 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -68,6 +68,9 @@ private: // sound_stop(handle) static int l_sound_stop(lua_State *L); + // sound_fade(handle, step, gain) + static int l_sound_fade(lua_State *L); + // get_player_privs(name, text) static int l_get_player_privs(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index 9ef69cb37..190a1baf2 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2100,15 +2100,23 @@ s32 Server::playSound(const SimpleSoundSpec &spec, m_playing_sounds[id] = ServerPlayingSound(); ServerPlayingSound &psound = m_playing_sounds[id]; psound.params = params; + psound.spec = spec; + float gain = params.gain * spec.gain; NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); - pkt << id << spec.name << (float) (spec.gain * params.gain) - << (u8) params.type << pos << params.object << params.loop; + pkt << id << spec.name << gain + << (u8) params.type << pos << params.object + << params.loop << params.fade; - for(std::vector::iterator i = dst_clients.begin(); + // Backwards compability + bool play_sound = gain > 0; + + for (std::vector::iterator i = dst_clients.begin(); i != dst_clients.end(); ++i) { - psound.clients.insert(*i); - m_clients.send(*i, 0, &pkt, true); + if (play_sound || m_clients.getProtocolVersion(*i) >= 32) { + psound.clients.insert(*i); + m_clients.send(*i, 0, &pkt, true); + } } return id; } @@ -2132,6 +2140,52 @@ void Server::stopSound(s32 handle) m_playing_sounds.erase(i); } +void Server::fadeSound(s32 handle, float step, float gain) +{ + // Get sound reference + UNORDERED_MAP::iterator i = + m_playing_sounds.find(handle); + if (i == m_playing_sounds.end()) + return; + + ServerPlayingSound &psound = i->second; + psound.params.gain = gain; + + NetworkPacket pkt(TOCLIENT_FADE_SOUND, 4); + pkt << handle << step << gain; + + // Backwards compability + bool play_sound = gain > 0; + ServerPlayingSound compat_psound = psound; + compat_psound.clients.clear(); + + NetworkPacket compat_pkt(TOCLIENT_STOP_SOUND, 4); + compat_pkt << handle; + + for (UNORDERED_SET::iterator it = psound.clients.begin(); + it != psound.clients.end();) { + if (m_clients.getProtocolVersion(*it) >= 32) { + // Send as reliable + m_clients.send(*it, 0, &pkt, true); + ++it; + } else { + compat_psound.clients.insert(*it); + // Stop old sound + m_clients.send(*it, 0, &compat_pkt, true); + psound.clients.erase(it++); + } + } + + // Remove sound reference + if (!play_sound || psound.clients.size() == 0) + m_playing_sounds.erase(i); + + if (play_sound && compat_psound.clients.size() > 0) { + // Play new sound volume on older clients + playSound(compat_psound.spec, compat_psound.params); + } +} + void Server::sendRemoveNode(v3s16 p, u16 ignore_id, std::vector *far_players, float far_d_nodes) { diff --git a/src/server.h b/src/server.h index 3a082b9a4..5e6211637 100644 --- a/src/server.h +++ b/src/server.h @@ -115,6 +115,7 @@ struct ServerSoundParams u16 object; float max_hear_distance; bool loop; + float fade; ServerSoundParams(): gain(1.0), @@ -123,7 +124,8 @@ struct ServerSoundParams pos(0,0,0), object(0), max_hear_distance(32*BS), - loop(false) + loop(false), + fade(0) {} v3f getPos(ServerEnvironment *env, bool *pos_exists) const; @@ -132,6 +134,7 @@ struct ServerSoundParams struct ServerPlayingSound { ServerSoundParams params; + SimpleSoundSpec spec; UNORDERED_SET clients; // peer ids }; @@ -231,6 +234,7 @@ public: // Envlock s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms); void stopSound(s32 handle); + void fadeSound(s32 handle, float step, float gain); // Envlock std::set getPlayerEffectivePrivs(const std::string &name); diff --git a/src/sound.h b/src/sound.h index 98f7692d5..7bdb6a26b 100644 --- a/src/sound.h +++ b/src/sound.h @@ -34,8 +34,8 @@ public: struct SimpleSoundSpec { - SimpleSoundSpec(const std::string &name = "", float gain = 1.0) - : name(name), gain(gain) + SimpleSoundSpec(const std::string &name = "", float gain = 1.0, float fade = 0.0) + : name(name), gain(gain), fade(fade) { } @@ -43,13 +43,13 @@ struct SimpleSoundSpec std::string name; float gain; + float fade; }; class ISoundManager { public: virtual ~ISoundManager() {} - // Multiple sounds can be loaded per name; when played, the sound // should be chosen randomly from alternatives // Return value determines success/failure @@ -63,16 +63,21 @@ public: // playSound functions return -1 on failure, otherwise a handle to the // sound. If name=="", call should be ignored without error. - virtual int playSound(const std::string &name, bool loop, float volume) = 0; - virtual int playSoundAt( - const std::string &name, bool loop, float volume, v3f pos) = 0; + virtual int playSound(const std::string &name, bool loop, float volume, + float fade = 0) = 0; + virtual int playSoundAt(const std::string &name, bool loop, float volume, + v3f pos) = 0; virtual void stopSound(int sound) = 0; virtual bool soundExists(int sound) = 0; virtual void updateSoundPosition(int sound, v3f pos) = 0; + virtual bool updateSoundGain(int id, float gain) = 0; + virtual float getSoundGain(int id) = 0; + virtual void step(float dtime) = 0; + virtual void fadeSound(int sound, float step, float gain) = 0; int playSound(const SimpleSoundSpec &spec, bool loop) { - return playSound(spec.name, loop, spec.gain); + return playSound(spec.name, loop, spec.gain, spec.fade); } int playSoundAt(const SimpleSoundSpec &spec, bool loop, v3f pos) { @@ -93,7 +98,10 @@ public: } void updateListener(v3f pos, v3f vel, v3f at, v3f up) {} void setListenerGain(float gain) {} - int playSound(const std::string &name, bool loop, float volume) { return 0; } + int playSound(const std::string &name, bool loop, float volume, float fade) + { + return 0; + } int playSoundAt(const std::string &name, bool loop, float volume, v3f pos) { return 0; @@ -101,6 +109,10 @@ public: void stopSound(int sound) {} bool soundExists(int sound) { return false; } void updateSoundPosition(int sound, v3f pos) {} + bool updateSoundGain(int id, float gain) { return false; } + float getSoundGain(int id) { return 0; } + void step(float dtime) { } + void fadeSound(int sound, float step, float gain) { } }; // Global DummySoundManager singleton diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index b9af9e3a9..a425af827 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -274,6 +274,19 @@ private: UNORDERED_MAP > m_buffers; UNORDERED_MAP m_sounds_playing; v3f m_listener_pos; + struct FadeState { + FadeState() {} + FadeState(float step, float current_gain, float target_gain): + step(step), + current_gain(current_gain), + target_gain(target_gain) {} + float step; + float current_gain; + float target_gain; + }; + + UNORDERED_MAP m_sounds_fading; + float m_fade_delay; public: bool m_is_initialized; OpenALSoundManager(OnDemandSoundFetcher *fetcher): @@ -281,6 +294,7 @@ public: m_device(NULL), m_context(NULL), m_next_id(1), + m_fade_delay(0), m_is_initialized(false) { ALCenum error = ALC_NO_ERROR; @@ -349,6 +363,11 @@ public: infostream<<"Audio: Deinitialized."< >::iterator i = @@ -515,6 +534,7 @@ public: addBuffer(name, buf); return false; } + bool loadSoundData(const std::string &name, const std::string &filedata) { @@ -541,7 +561,7 @@ public: alListenerf(AL_GAIN, gain); } - int playSound(const std::string &name, bool loop, float volume) + int playSound(const std::string &name, bool loop, float volume, float fade) { maintain(); if(name == "") @@ -552,8 +572,16 @@ public: < 0) { + handle = playSoundRaw(buf, loop, 0); + fadeSound(handle, fade, volume); + } else { + handle = playSoundRaw(buf, loop, volume); + } + return handle; } + int playSoundAt(const std::string &name, bool loop, float volume, v3f pos) { maintain(); @@ -567,16 +595,55 @@ public: } return playSoundRawAt(buf, loop, volume, pos); } + void stopSound(int sound) { maintain(); deleteSound(sound); } + + void fadeSound(int soundid, float step, float gain) + { + m_sounds_fading[soundid] = FadeState(step, getSoundGain(soundid), gain); + } + + void doFades(float dtime) + { + m_fade_delay += dtime; + + if (m_fade_delay < 0.1f) + return; + + float chkGain = 0; + for (UNORDERED_MAP::iterator i = m_sounds_fading.begin(); + i != m_sounds_fading.end();) { + if (i->second.step < 0.f) + chkGain = -(i->second.current_gain); + else + chkGain = i->second.current_gain; + + if (chkGain < i->second.target_gain) { + i->second.current_gain += (i->second.step * m_fade_delay); + i->second.current_gain = rangelim(i->second.current_gain, 0, 1); + + updateSoundGain(i->first, i->second.current_gain); + ++i; + } else { + if (i->second.target_gain <= 0.f) + stopSound(i->first); + + m_sounds_fading.erase(i++); + } + } + m_fade_delay = 0; + } + bool soundExists(int sound) { maintain(); return (m_sounds_playing.count(sound) != 0); } + void updateSoundPosition(int id, v3f pos) { UNORDERED_MAP::iterator i = m_sounds_playing.find(id); @@ -589,6 +656,29 @@ public: alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0); alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0); } + + bool updateSoundGain(int id, float gain) + { + UNORDERED_MAP::iterator i = m_sounds_playing.find(id); + if (i == m_sounds_playing.end()) + return false; + + PlayingSound *sound = i->second; + alSourcef(sound->source_id, AL_GAIN, gain); + return true; + } + + float getSoundGain(int id) + { + UNORDERED_MAP::iterator i = m_sounds_playing.find(id); + if (i == m_sounds_playing.end()) + return 0; + + PlayingSound *sound = i->second; + ALfloat gain; + alGetSourcef(sound->source_id, AL_GAIN, &gain); + return gain; + } }; ISoundManager *createOpenALSoundManager(OnDemandSoundFetcher *fetcher)