Sound API: Add fading sounds
authorBrandon <brandon@bremaweb.com>
Sun, 10 Jul 2016 05:08:26 +0000 (00:08 -0500)
committerparamat <mat.gregory@virginmedia.com>
Wed, 3 May 2017 02:12:45 +0000 (03:12 +0100)
14 files changed:
doc/lua_api.txt
src/client.cpp
src/client.h
src/network/clientopcodes.cpp
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/script/common/c_content.cpp
src/script/common/c_converter.h
src/script/lua_api/l_server.cpp
src/script/lua_api/l_server.h
src/server.cpp
src/server.h
src/sound.h
src/sound_openal.cpp

index 479e38a2eef0cf081db76f7736564ab3ef47eee3..77ffb88e298b31f911189e4e9bc8edab36d4cc20 100644 (file)
@@ -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, ...)`
index 3c5a70f212968400621bd83d60f3dbe7d16afb45..3269c573a638666be7565d62595eab212e519049 100644 (file)
@@ -407,6 +407,7 @@ void Client::step(float dtime)
 
        // Step environment
        m_env.step(dtime);
+       m_sound->step(dtime);
 
        /*
                Get events
index 7cbfadd50fd36f98d636388c12b8b572c0c9868b..e8db7de44e73ce963bcc0bd72ef5d735f1735e35 100644 (file)
@@ -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);
index 1be6e5522fb3caa99ab759d08365d47ab45df4d8..bdcb1dfce0709fd82f46193f48cfd09957d1dec0 100644 (file)
@@ -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,
index defc83f31027bfe6503e2747b19dca991bef9228..a895acc8419822eab8eea13f48ea52b7bc1b8807 100644 (file)
@@ -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<s32, int>::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();
index a1a4f5bfadf207d49eccae2296e101e0d0c510f4..70cad85d8dfbd14f0a2ca9c45e97a4c81b858502 100644 (file)
@@ -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.
index 5fe5af58d05fb4379914d7cedd4eea235073f716..8696ad7cb71610df677cff167c550989392c3d7a 100644 (file)
@@ -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);
        }
index a5fbee765ffd73d9d5163f8c6374e86e57e381c5..b0f61a8cad554e166be934ce0faa58d4d960d067 100644 (file)
@@ -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);
index 7b723d14cd9d865c39bdb31641607287d3d9232b..ea993d7b7f8e002d73609129e632f8a235e830b3 100644 (file)
@@ -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);
index 3a4a917c0554d4b6b04f26cfae040dd9b50fda8a..251a0ce89e37004a96b1afd5ae9a68d917943b68 100644 (file)
@@ -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);
 
index 9ef69cb373ec991198dbffae39164a624d178723..190a1baf2dbc569d622bbb04a7ed4c3a7d5c45b5 100644 (file)
@@ -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<u16>::iterator i = dst_clients.begin();
+       // Backwards compability
+       bool play_sound = gain > 0;
+
+       for (std::vector<u16>::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<s32, ServerPlayingSound>::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<u16>::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<u16> *far_players, float far_d_nodes)
 {
index 3a082b9a487c321f09df25dcd4a2c4da29907c36..5e6211637b921d42dbf1c70acfb349effebb4183 100644 (file)
@@ -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<u16> clients; // peer ids
 };
 
@@ -231,6 +234,7 @@ public:
        // Envlock
        s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams &params);
        void stopSound(s32 handle);
+       void fadeSound(s32 handle, float step, float gain);
 
        // Envlock
        std::set<std::string> getPlayerEffectivePrivs(const std::string &name);
index 98f7692d58050d6e51493ef43cc10ce6a18d05e8..7bdb6a26bb4afd57cc6250594311907e6566e284 100644 (file)
@@ -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
index b9af9e3a919cd86f66fc8e84108e0d999fd0b962..a425af8274374f34be56380171b5bab5c93ca7fd 100644 (file)
@@ -274,6 +274,19 @@ private:
        UNORDERED_MAP<std::string, std::vector<SoundBuffer*> > m_buffers;
        UNORDERED_MAP<int, PlayingSound*> 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<int, FadeState> 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."<<std::endl;
        }
 
+       void step(float dtime)
+       {
+               doFades(dtime);
+       }
+
        void addBuffer(const std::string &name, SoundBuffer *buf)
        {
                UNORDERED_MAP<std::string, std::vector<SoundBuffer*> >::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:
                                        <<std::endl;
                        return -1;
                }
-               return playSoundRaw(buf, loop, volume);
+               int handle = -1;
+               if (fade > 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<int, FadeState>::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<int, PlayingSound*>::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<int, PlayingSound*>::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<int, PlayingSound*>::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)