mirror of
https://github.com/minetest/minetest.git
synced 2024-08-19 04:54:18 +02:00
Sound refactor and improvements (#12764)
This commit is contained in:
parent
8e1af25738
commit
edcbfa31c9
@ -83,6 +83,8 @@ SmallJoker:
|
||||
DS:
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_bigfoot.png
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_jukebox.png
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_racecar.png
|
||||
games/devtest/mods/soundstuff/sounds/soundstuff_sinus.ogg
|
||||
games/devtest/mods/testtools/textures/testtools_branding_iron.png
|
||||
|
||||
License of Minetest source code
|
||||
|
@ -5,3 +5,14 @@ function core.setting_get_pos(name)
|
||||
end
|
||||
return core.string_to_pos(value)
|
||||
end
|
||||
|
||||
|
||||
-- old non-method sound functions
|
||||
|
||||
function core.sound_stop(handle, ...)
|
||||
return handle:stop(...)
|
||||
end
|
||||
|
||||
function core.sound_fade(handle, ...)
|
||||
return handle:fade(...)
|
||||
end
|
||||
|
@ -28,6 +28,8 @@ local basepath = core.get_builtin_path()
|
||||
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
||||
DIR_DELIM .. "pack" .. DIR_DELIM
|
||||
|
||||
dofile(menupath .. DIR_DELIM .. "misc.lua")
|
||||
|
||||
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
|
||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
|
||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
|
||||
|
6
builtin/mainmenu/misc.lua
Normal file
6
builtin/mainmenu/misc.lua
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
-- old non-method sound function
|
||||
|
||||
function core.sound_stop(handle, ...)
|
||||
return handle:stop(...)
|
||||
end
|
@ -754,9 +754,9 @@ Call these functions only at load time!
|
||||
* `minetest.sound_play(spec, parameters)`: returns a handle
|
||||
* `spec` is a `SimpleSoundSpec`
|
||||
* `parameters` is a sound parameter table
|
||||
* `minetest.sound_stop(handle)`
|
||||
* `handle:stop()` or `minetest.sound_stop(handle)`
|
||||
* `handle` is a handle returned by `minetest.sound_play`
|
||||
* `minetest.sound_fade(handle, step, gain)`
|
||||
* `handle:fade(step, gain)` or `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
|
||||
|
197
doc/lua_api.md
197
doc/lua_api.md
@ -994,16 +994,21 @@ Only Ogg Vorbis files are supported.
|
||||
For positional playing of sounds, only single-channel (mono) files are
|
||||
supported. Otherwise OpenAL will play them non-positionally.
|
||||
|
||||
Mods should generally prefix their sounds with `modname_`, e.g. given
|
||||
Mods should generally prefix their sound files with `modname_`, e.g. given
|
||||
the mod name "`foomod`", a sound could be called:
|
||||
|
||||
foomod_foosound.ogg
|
||||
|
||||
Sounds are referred to by their name with a dot, a single digit and the
|
||||
file extension stripped out. When a sound is played, the actual sound file
|
||||
is chosen randomly from the matching sounds.
|
||||
Sound group
|
||||
-----------
|
||||
|
||||
When playing the sound `foomod_foosound`, the sound is chosen randomly
|
||||
A sound group is the set of all sound files, whose filenames are of the following
|
||||
format:
|
||||
`<sound-group name>[.<single digit>].ogg`
|
||||
When a sound-group is played, one the files in the group is chosen at random.
|
||||
Sound files can only be referred to by their sound-group name.
|
||||
|
||||
Example: When playing the sound `foomod_foosound`, the sound is chosen randomly
|
||||
from the available ones of the following files:
|
||||
|
||||
* `foomod_foosound.ogg`
|
||||
@ -1012,62 +1017,10 @@ from the available ones of the following files:
|
||||
* (...)
|
||||
* `foomod_foosound.9.ogg`
|
||||
|
||||
Examples of sound parameter tables:
|
||||
|
||||
```lua
|
||||
-- Play locationless on all clients
|
||||
{
|
||||
gain = 1.0, -- default
|
||||
fade = 0.0, -- default, change to a value > 0 to fade the sound in
|
||||
pitch = 1.0, -- default
|
||||
}
|
||||
-- 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
|
||||
pitch = 1.0, -- default
|
||||
}
|
||||
-- Play locationless to one player, looped
|
||||
{
|
||||
to_player = name,
|
||||
gain = 1.0, -- default
|
||||
loop = true,
|
||||
}
|
||||
-- Play at a location
|
||||
{
|
||||
pos = {x = 1, y = 2, z = 3},
|
||||
gain = 1.0, -- default
|
||||
max_hear_distance = 32, -- default, uses a Euclidean metric
|
||||
}
|
||||
-- Play connected to an object, looped
|
||||
{
|
||||
object = <an ObjectRef>,
|
||||
gain = 1.0, -- default
|
||||
max_hear_distance = 32, -- default, uses a Euclidean metric
|
||||
loop = true,
|
||||
}
|
||||
-- Play at a location, heard by anyone *but* the given player
|
||||
{
|
||||
pos = {x = 32, y = 0, z = 100},
|
||||
max_hear_distance = 40,
|
||||
exclude_player = name,
|
||||
}
|
||||
```
|
||||
|
||||
Looped sounds must either be connected to an object or played locationless to
|
||||
one player using `to_player = name`.
|
||||
|
||||
A positional sound will only be heard by players that are within
|
||||
`max_hear_distance` of the sound position, at the start of the sound.
|
||||
|
||||
`exclude_player = name` can be applied to locationless, positional and object-
|
||||
bound sounds to exclude a single player from hearing them.
|
||||
|
||||
`SimpleSoundSpec`
|
||||
-----------------
|
||||
|
||||
Specifies a sound name, gain (=volume) and pitch.
|
||||
Specifies a sound name, gain (=volume), pitch and fade.
|
||||
This is either a string or a table.
|
||||
|
||||
In string form, you just specify the sound name or
|
||||
@ -1075,11 +1028,25 @@ the empty string for no sound.
|
||||
|
||||
Table form has the following fields:
|
||||
|
||||
* `name`: Sound name
|
||||
* `gain`: Volume (`1.0` = 100%)
|
||||
* `pitch`: Pitch (`1.0` = 100%)
|
||||
* `name`:
|
||||
Sound-group name.
|
||||
If == `""`, no sound is played.
|
||||
* `gain`:
|
||||
Volume (`1.0` = 100%), must be non-negative.
|
||||
At the end, OpenAL clamps sound gain to a maximum of `1.0`. By setting gain for
|
||||
a positional sound higher than `1.0`, one can increase the radius inside which
|
||||
maximal gain is reached.
|
||||
Furthermore, gain of positional sounds doesn't increase inside a 1 node radius.
|
||||
The gain given here describes the gain at a distance of 3 nodes.
|
||||
* `pitch`:
|
||||
Applies a pitch-shift to the sound.
|
||||
Each factor of `2.0` results in a pitch-shift of +12 semitones.
|
||||
Must be positive.
|
||||
* `fade`:
|
||||
If > `0.0`, the sound is faded in, with this value in gain per second, until
|
||||
`gain` is reached.
|
||||
|
||||
`gain` and `pitch` are optional and default to `1.0`.
|
||||
`gain`, `pitch` and `fade` are optional and default to `1.0`, `1.0` and `0.0`.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -1090,10 +1057,105 @@ Examples:
|
||||
* `{name = "default_place_node", gain = 0.5}`: 50% volume
|
||||
* `{name = "default_place_node", gain = 0.9, pitch = 1.1}`: 90% volume, 110% pitch
|
||||
|
||||
Special sound files
|
||||
-------------------
|
||||
Sound parameter table
|
||||
---------------------
|
||||
|
||||
These sound files are played back by the engine if provided.
|
||||
Table used to specify how a sound is played:
|
||||
|
||||
```lua
|
||||
{
|
||||
gain = 1.0,
|
||||
-- Scales the gain specified in `SimpleSoundSpec`.
|
||||
|
||||
pitch = 1.0,
|
||||
-- Overwrites the pitch specified in `SimpleSoundSpec`.
|
||||
|
||||
fade = 0.0,
|
||||
-- Overwrites the fade specified in `SimpleSoundSpec`.
|
||||
|
||||
start_time = 0.0,
|
||||
-- Start with a time-offset into the sound.
|
||||
-- The behavior is as if the sound was already playing for this many seconds.
|
||||
-- Negative values are relative to the sound's length, so the sound reaches
|
||||
-- its end in `-start_time` seconds.
|
||||
-- It is unspecified what happens if `loop` is false and `start_time` is
|
||||
-- smaller than minus the sound's length.
|
||||
|
||||
loop = false,
|
||||
-- If true, sound is played in a loop.
|
||||
|
||||
pos = {x = 1, y = 2, z = 3},
|
||||
-- Play sound at a position.
|
||||
-- Can't be used together with `object`.
|
||||
|
||||
object = <an ObjectRef>,
|
||||
-- Attach the sound to an object.
|
||||
-- Can't be used together with `pos`.
|
||||
|
||||
to_player = name,
|
||||
-- Only play for this player.
|
||||
-- Can't be used together with `exclude_player`.
|
||||
|
||||
exclude_player = name,
|
||||
-- Don't play sound for this player.
|
||||
-- Can't be used together with `to_player`.
|
||||
|
||||
max_hear_distance = 32,
|
||||
-- Only play for players that are at most this far away when the sound
|
||||
-- starts playing.
|
||||
-- Needs `pos` or `object` to be set.
|
||||
-- `32` is the default.
|
||||
}
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```lua
|
||||
-- Play locationless on all clients
|
||||
{
|
||||
gain = 1.0, -- default
|
||||
fade = 0.0, -- default
|
||||
pitch = 1.0, -- default
|
||||
}
|
||||
-- Play locationless to one player
|
||||
{
|
||||
to_player = name,
|
||||
gain = 1.0, -- default
|
||||
fade = 0.0, -- default
|
||||
pitch = 1.0, -- default
|
||||
}
|
||||
-- Play locationless to one player, looped
|
||||
{
|
||||
to_player = name,
|
||||
gain = 1.0, -- default
|
||||
loop = true,
|
||||
}
|
||||
-- Play at a location, start the sound at offset 5 seconds
|
||||
{
|
||||
pos = {x = 1, y = 2, z = 3},
|
||||
gain = 1.0, -- default
|
||||
max_hear_distance = 32, -- default
|
||||
start_time = 5.0,
|
||||
}
|
||||
-- Play connected to an object, looped
|
||||
{
|
||||
object = <an ObjectRef>,
|
||||
gain = 1.0, -- default
|
||||
max_hear_distance = 32, -- default
|
||||
loop = true,
|
||||
}
|
||||
-- Play at a location, heard by anyone *but* the given player
|
||||
{
|
||||
pos = {x = 32, y = 0, z = 100},
|
||||
max_hear_distance = 40,
|
||||
exclude_player = name,
|
||||
}
|
||||
```
|
||||
|
||||
Special sound-groups
|
||||
--------------------
|
||||
|
||||
These sound-groups are played back by the engine if provided.
|
||||
|
||||
* `player_damage`: Played when the local player takes damage (gain = 0.5)
|
||||
* `player_falling_damage`: Played when the local player takes
|
||||
@ -8804,7 +8866,10 @@ Used by `minetest.register_node`.
|
||||
|
||||
footstep = <SimpleSoundSpec>,
|
||||
-- If walkable, played when object walks on it. If node is
|
||||
-- climbable or a liquid, played when object moves through it
|
||||
-- climbable or a liquid, played when object moves through it.
|
||||
-- Sound is played at the base of the object's collision-box.
|
||||
-- Gain is multiplied by `0.6`.
|
||||
-- For local player, it's played position-less, with normal gain.
|
||||
|
||||
dig = <SimpleSoundSpec> or "__group",
|
||||
-- While digging node.
|
||||
|
@ -81,7 +81,7 @@ Filesystem
|
||||
* `core.sound_play(spec, looped)` -> handle
|
||||
* `spec` = `SimpleSoundSpec` (see `lua_api.md`)
|
||||
* `looped` = bool
|
||||
* `core.sound_stop(handle)`
|
||||
* `handle:stop()` or `core.sound_stop(handle)`
|
||||
* `core.get_video_drivers()`
|
||||
* get list of video drivers supported by engine (not all modes are guaranteed to work)
|
||||
* returns list of available video drivers' settings name and 'friendly' display name
|
||||
|
@ -3,3 +3,4 @@ local path = minetest.get_modpath("soundstuff") .. "/"
|
||||
dofile(path .. "sound_event_items.lua")
|
||||
dofile(path .. "jukebox.lua")
|
||||
dofile(path .. "bigfoot.lua")
|
||||
dofile(path .. "racecar.lua")
|
||||
|
@ -14,6 +14,7 @@ local meta_keys = {
|
||||
"sparam.gain",
|
||||
"sparam.pitch",
|
||||
"sparam.fade",
|
||||
"sparam.start_time",
|
||||
"sparam.loop",
|
||||
"sparam.pos",
|
||||
"sparam.object",
|
||||
@ -39,6 +40,7 @@ local function get_all_metadata(meta)
|
||||
gain = meta:get_string("sparam.gain"),
|
||||
pitch = meta:get_string("sparam.pitch"),
|
||||
fade = meta:get_string("sparam.fade"),
|
||||
start_time = meta:get_string("sparam.start_time"),
|
||||
loop = meta:get_string("sparam.loop"),
|
||||
pos = meta:get_string("sparam.pos"),
|
||||
object = meta:get_string("sparam.object"),
|
||||
@ -86,7 +88,7 @@ local function show_formspec(pos, player)
|
||||
|
||||
fs_add([[
|
||||
formspec_version[6]
|
||||
size[14,11]
|
||||
size[14,12]
|
||||
]])
|
||||
|
||||
-- SimpleSoundSpec
|
||||
@ -110,23 +112,25 @@ local function show_formspec(pos, player)
|
||||
-- sound parameter table
|
||||
fs_add(string.format([[
|
||||
container[5.5,0.5]
|
||||
box[-0.1,-0.1;4.2,10.2;#EBEBEB20]
|
||||
box[-0.1,-0.1;4.2,10.7;#EBEBEB20]
|
||||
style[*;font=mono,bold]
|
||||
label[0,0.25;sound parameter table]
|
||||
style[*;font=mono]
|
||||
field[0.00,1;1,0.75;sparam.gain;gain;%s]
|
||||
field[1.25,1;1,0.75;sparam.pitch;pitch;%s]
|
||||
field[2.50,1;1,0.75;sparam.fade;fade;%s]
|
||||
field[0,2.25;4,0.75;sparam.loop;loop;%s]
|
||||
field[0,3.50;4,0.75;sparam.pos;pos;%s]
|
||||
field[0,4.75;4,0.75;sparam.object;object;%s]
|
||||
field[0,6.00;4,0.75;sparam.to_player;to_player;%s]
|
||||
field[0,7.25;4,0.75;sparam.exclude_player;exclude_player;%s]
|
||||
field[0,8.50;4,0.75;sparam.max_hear_distance;max_hear_distance;%s]
|
||||
field[0,2.25;4,0.75;sparam.start_time;start_time;%s]
|
||||
field[0,3.50;4,0.75;sparam.loop;loop;%s]
|
||||
field[0,4.75;4,0.75;sparam.pos;pos;%s]
|
||||
field[0,6.00;4,0.75;sparam.object;object;%s]
|
||||
field[0,7.25;4,0.75;sparam.to_player;to_player;%s]
|
||||
field[0,8.50;4,0.75;sparam.exclude_player;exclude_player;%s]
|
||||
field[0,9.75;4,0.75;sparam.max_hear_distance;max_hear_distance;%s]
|
||||
container_end[]
|
||||
field_close_on_enter[sparam.gain;false]
|
||||
field_close_on_enter[sparam.pitch;false]
|
||||
field_close_on_enter[sparam.fade;false]
|
||||
field_close_on_enter[sparam.start_time;false]
|
||||
field_close_on_enter[sparam.loop;false]
|
||||
field_close_on_enter[sparam.pos;false]
|
||||
field_close_on_enter[sparam.object;false]
|
||||
@ -134,9 +138,10 @@ local function show_formspec(pos, player)
|
||||
field_close_on_enter[sparam.exclude_player;false]
|
||||
field_close_on_enter[sparam.max_hear_distance;false]
|
||||
tooltip[sparam.object;Get a name with the Branding Iron.]
|
||||
]], F(md.sparam.gain), F(md.sparam.pitch), F(md.sparam.fade), F(md.sparam.loop),
|
||||
F(md.sparam.pos), F(md.sparam.object), F(md.sparam.to_player),
|
||||
F(md.sparam.exclude_player), F(md.sparam.max_hear_distance)))
|
||||
]], F(md.sparam.gain), F(md.sparam.pitch), F(md.sparam.fade),
|
||||
F(md.sparam.start_time), F(md.sparam.loop), F(md.sparam.pos),
|
||||
F(md.sparam.object), F(md.sparam.to_player), F(md.sparam.exclude_player),
|
||||
F(md.sparam.max_hear_distance)))
|
||||
|
||||
-- fade
|
||||
fs_add(string.format([[
|
||||
@ -187,7 +192,7 @@ local function show_formspec(pos, player)
|
||||
|
||||
-- save and quit button
|
||||
fs_add([[
|
||||
button_exit[10.75,10;3,0.75;btn_save_quit;Save & Quit]
|
||||
button_exit[10.75,11;3,0.75;btn_save_quit;Save & Quit]
|
||||
]])
|
||||
|
||||
minetest.show_formspec(player:get_player_name(), "soundstuff:jukebox@"..pos:to_string(),
|
||||
@ -210,6 +215,7 @@ minetest.register_node("soundstuff:jukebox", {
|
||||
meta:set_string("sparam.gain", "")
|
||||
meta:set_string("sparam.pitch", "")
|
||||
meta:set_string("sparam.fade", "")
|
||||
meta:set_string("sparam.start_time", "")
|
||||
meta:set_string("sparam.loop", "")
|
||||
meta:set_string("sparam.pos", pos:to_string())
|
||||
meta:set_string("sparam.object", "")
|
||||
@ -267,6 +273,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
gain = tonumber(md.sparam.gain),
|
||||
pitch = tonumber(md.sparam.pitch),
|
||||
fade = tonumber(md.sparam.fade),
|
||||
start_time = tonumber(md.sparam.start_time),
|
||||
loop = minetest.is_yes(md.sparam.loop),
|
||||
pos = vector.from_string(md.sparam.pos),
|
||||
object = testtools.get_branded_object(md.sparam.object),
|
||||
@ -280,10 +287,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
"[soundstuff:jukebox] Playing sound: minetest.sound_play(%s, %s, %s)",
|
||||
string.format("{name=\"%s\", gain=%s, pitch=%s, fade=%s}",
|
||||
sss.name, sss.gain, sss.pitch, sss.fade),
|
||||
string.format("{gain=%s, pitch=%s, fade=%s, loop=%s, pos=%s, "
|
||||
string.format("{gain=%s, pitch=%s, fade=%s, start_time=%s, loop=%s, pos=%s, "
|
||||
.."object=%s, to_player=\"%s\", exclude_player=\"%s\", max_hear_distance=%s}",
|
||||
sparam.gain, sparam.pitch, sparam.fade, sparam.loop, sparam.pos,
|
||||
sparam.object and "<objref>", sparam.to_player, sparam.exclude_player,
|
||||
sparam.gain, sparam.pitch, sparam.fade, sparam.start_time,
|
||||
sparam.loop, sparam.pos, sparam.object and "<objref>",
|
||||
sparam.to_player, sparam.exclude_player,
|
||||
sparam.max_hear_distance),
|
||||
tostring(ephemeral)))
|
||||
|
||||
|
31
games/devtest/mods/soundstuff/racecar.lua
Normal file
31
games/devtest/mods/soundstuff/racecar.lua
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
local drive_speed = 20
|
||||
local drive_distance = 30
|
||||
|
||||
minetest.register_entity("soundstuff:racecar", {
|
||||
initial_properties = {
|
||||
physical = false,
|
||||
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
|
||||
selectionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
|
||||
visual = "upright_sprite",
|
||||
visual_size = {x = 1, y = 1, z = 1},
|
||||
textures = {"soundstuff_racecar.png", "soundstuff_racecar.png^[transformFX"},
|
||||
static_save = false,
|
||||
},
|
||||
|
||||
on_activate = function(self, _staticdata, _dtime_s)
|
||||
self.min_x = self.object:get_pos().x - drive_distance * 0.5
|
||||
self.max_x = self.min_x + drive_distance
|
||||
self.vel = vector.new(drive_speed, 0, 0)
|
||||
end,
|
||||
|
||||
on_step = function(self, _dtime, _moveresult)
|
||||
local pos = self.object:get_pos()
|
||||
if pos.x < self.min_x then
|
||||
self.vel = vector.new(drive_speed, 0, 0)
|
||||
elseif pos.x > self.max_x then
|
||||
self.vel = vector.new(-drive_speed, 0, 0)
|
||||
end
|
||||
self.object:set_velocity(self.vel)
|
||||
end,
|
||||
})
|
BIN
games/devtest/mods/soundstuff/sounds/soundstuff_sinus.ogg
Normal file
BIN
games/devtest/mods/soundstuff/sounds/soundstuff_sinus.ogg
Normal file
Binary file not shown.
BIN
games/devtest/mods/soundstuff/textures/soundstuff_racecar.png
Normal file
BIN
games/devtest/mods/soundstuff/textures/soundstuff_racecar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 B |
@ -1,8 +1,9 @@
|
||||
set(sound_SRCS "")
|
||||
set(sound_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sound.cpp)
|
||||
|
||||
if(USE_SOUND)
|
||||
set(sound_SRCS ${sound_SRCS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/sound_openal.cpp)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/sound_openal.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/sound_openal_internal.cpp)
|
||||
set(SOUND_INCLUDE_DIRS
|
||||
${OPENAL_INCLUDE_DIR}
|
||||
${VORBIS_INCLUDE_DIR}
|
||||
|
@ -30,7 +30,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "settings.h"
|
||||
#include "wieldmesh.h"
|
||||
#include "noise.h" // easeCurve
|
||||
#include "sound.h"
|
||||
#include "mtevent.h"
|
||||
#include "nodedef.h"
|
||||
#include "util/numeric.h"
|
||||
|
@ -374,6 +374,11 @@ Client::~Client()
|
||||
if (m_mod_storage_database)
|
||||
m_mod_storage_database->endSave();
|
||||
delete m_mod_storage_database;
|
||||
|
||||
// Free sound ids
|
||||
for (auto &csp : m_sounds_client_to_server)
|
||||
m_sound->freeId(csp.first);
|
||||
m_sounds_client_to_server.clear();
|
||||
}
|
||||
|
||||
void Client::connect(Address address, bool is_local_server)
|
||||
@ -703,12 +708,13 @@ void Client::step(float dtime)
|
||||
*/
|
||||
{
|
||||
for (auto &m_sounds_to_object : m_sounds_to_objects) {
|
||||
int client_id = m_sounds_to_object.first;
|
||||
sound_handle_t client_id = m_sounds_to_object.first;
|
||||
u16 object_id = m_sounds_to_object.second;
|
||||
ClientActiveObject *cao = m_env.getActiveObject(object_id);
|
||||
if (!cao)
|
||||
continue;
|
||||
m_sound->updateSoundPosition(client_id, cao->getPosition());
|
||||
m_sound->updateSoundPosVel(client_id, cao->getPosition() * (1.0f/BS),
|
||||
cao->getVelocity() * (1.0f/BS));
|
||||
}
|
||||
}
|
||||
|
||||
@ -719,18 +725,20 @@ void Client::step(float dtime)
|
||||
if(m_removed_sounds_check_timer >= 2.32) {
|
||||
m_removed_sounds_check_timer = 0;
|
||||
// Find removed sounds and clear references to them
|
||||
std::vector<sound_handle_t> removed_client_ids = m_sound->pollRemovedSounds();
|
||||
std::vector<s32> removed_server_ids;
|
||||
for (std::unordered_map<s32, int>::iterator i = m_sounds_server_to_client.begin();
|
||||
i != m_sounds_server_to_client.end();) {
|
||||
s32 server_id = i->first;
|
||||
int client_id = i->second;
|
||||
++i;
|
||||
if(!m_sound->soundExists(client_id)) {
|
||||
for (sound_handle_t client_id : removed_client_ids) {
|
||||
auto client_to_server_id_it = m_sounds_client_to_server.find(client_id);
|
||||
if (client_to_server_id_it == m_sounds_client_to_server.end())
|
||||
continue;
|
||||
s32 server_id = client_to_server_id_it->second;
|
||||
m_sound->freeId(client_id);
|
||||
m_sounds_client_to_server.erase(client_to_server_id_it);
|
||||
if (server_id != -1) {
|
||||
m_sounds_server_to_client.erase(server_id);
|
||||
m_sounds_client_to_server.erase(client_id);
|
||||
m_sounds_to_objects.erase(client_id);
|
||||
removed_server_ids.push_back(server_id);
|
||||
}
|
||||
m_sounds_to_objects.erase(client_id);
|
||||
}
|
||||
|
||||
// Sync to server
|
||||
@ -800,9 +808,13 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
|
||||
};
|
||||
name = removeStringEnd(filename, sound_ext);
|
||||
if (!name.empty()) {
|
||||
TRACESTREAM(<< "Client: Attempting to load sound "
|
||||
<< "file \"" << filename << "\"" << std::endl);
|
||||
return m_sound->loadSoundData(name, data);
|
||||
TRACESTREAM(<< "Client: Attempting to load sound file \""
|
||||
<< filename << "\"" << std::endl);
|
||||
if (!m_sound->loadSoundData(filename, std::string(data)))
|
||||
return false;
|
||||
// "name[.num].ogg" is in group "name"
|
||||
m_sound->addSoundToGroup(filename, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *model_ext[] = {
|
||||
@ -1205,7 +1217,7 @@ void Client::sendGotBlocks(const std::vector<v3s16> &blocks)
|
||||
Send(&pkt);
|
||||
}
|
||||
|
||||
void Client::sendRemovedSounds(std::vector<s32> &soundList)
|
||||
void Client::sendRemovedSounds(const std::vector<s32> &soundList)
|
||||
{
|
||||
size_t server_ids = soundList.size();
|
||||
assert(server_ids <= 0xFFFF);
|
||||
|
@ -70,6 +70,7 @@ class NetworkPacket;
|
||||
namespace con {
|
||||
class Connection;
|
||||
}
|
||||
using sound_handle_t = int;
|
||||
|
||||
enum LocalClientState {
|
||||
LC_Created,
|
||||
@ -468,7 +469,7 @@ private:
|
||||
void startAuth(AuthMechanism chosen_auth_mechanism);
|
||||
void sendDeletedBlocks(std::vector<v3s16> &blocks);
|
||||
void sendGotBlocks(const std::vector<v3s16> &blocks);
|
||||
void sendRemovedSounds(std::vector<s32> &soundList);
|
||||
void sendRemovedSounds(const std::vector<s32> &soundList);
|
||||
|
||||
bool canSendChatMessage() const;
|
||||
|
||||
@ -564,11 +565,12 @@ private:
|
||||
// Sounds
|
||||
float m_removed_sounds_check_timer = 0.0f;
|
||||
// Mapping from server sound ids to our sound ids
|
||||
std::unordered_map<s32, int> m_sounds_server_to_client;
|
||||
std::unordered_map<s32, sound_handle_t> m_sounds_server_to_client;
|
||||
// And the other way!
|
||||
std::unordered_map<int, s32> m_sounds_client_to_server;
|
||||
// This takes ownership for the sound handles.
|
||||
std::unordered_map<sound_handle_t, s32> m_sounds_client_to_server;
|
||||
// Relation of client id to object id
|
||||
std::unordered_map<int, u16> m_sounds_to_objects;
|
||||
std::unordered_map<sound_handle_t, u16> m_sounds_to_objects;
|
||||
|
||||
// Privileges
|
||||
std::unordered_set<std::string> m_privileges;
|
||||
|
@ -47,7 +47,8 @@ public:
|
||||
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
|
||||
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
|
||||
virtual bool collideWithObjects() const { return false; }
|
||||
virtual const v3f getPosition() const { return v3f(0.0f); }
|
||||
virtual const v3f getPosition() const { return v3f(0.0f); } // in BS-space
|
||||
virtual const v3f getVelocity() const { return v3f(0.0f); } // in BS-space
|
||||
virtual scene::ISceneNode *getSceneNode() const
|
||||
{ return NULL; }
|
||||
virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const
|
||||
|
@ -1179,11 +1179,12 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
|
||||
v3s16 node_below_pos = floatToInt(foot_pos + v3f(0.0f, -0.5f, 0.0f),
|
||||
1.0f);
|
||||
MapNode n = m_env->getMap().getNode(node_below_pos);
|
||||
SimpleSoundSpec spec = ndef->get(n).sound_footstep;
|
||||
SoundSpec spec = ndef->get(n).sound_footstep;
|
||||
// Reduce footstep gain, as non-local-player footsteps are
|
||||
// somehow louder.
|
||||
spec.gain *= 0.6f;
|
||||
m_client->sound()->playSoundAt(spec, foot_pos * BS);
|
||||
// The footstep-sound doesn't travel with the object. => vel=0
|
||||
m_client->sound()->playSoundAt(0, spec, foot_pos, v3f(0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,9 @@ public:
|
||||
|
||||
virtual bool getSelectionBox(aabb3f *toset) const;
|
||||
|
||||
const v3f getPosition() const;
|
||||
const v3f getPosition() const override final;
|
||||
|
||||
const v3f getVelocity() const override final { return m_velocity; }
|
||||
|
||||
inline const v3f &getRotation() const { return m_rotation; }
|
||||
|
||||
|
@ -260,31 +260,25 @@ class SoundMaker
|
||||
const NodeDefManager *m_ndef;
|
||||
|
||||
public:
|
||||
bool makes_footstep_sound;
|
||||
float m_player_step_timer;
|
||||
float m_player_jump_timer;
|
||||
bool makes_footstep_sound = true;
|
||||
float m_player_step_timer = 0.0f;
|
||||
float m_player_jump_timer = 0.0f;
|
||||
|
||||
SimpleSoundSpec m_player_step_sound;
|
||||
SimpleSoundSpec m_player_leftpunch_sound;
|
||||
SoundSpec m_player_step_sound;
|
||||
SoundSpec m_player_leftpunch_sound;
|
||||
// Second sound made on left punch, currently used for item 'use' sound
|
||||
SimpleSoundSpec m_player_leftpunch_sound2;
|
||||
SimpleSoundSpec m_player_rightpunch_sound;
|
||||
SoundSpec m_player_leftpunch_sound2;
|
||||
SoundSpec m_player_rightpunch_sound;
|
||||
|
||||
SoundMaker(ISoundManager *sound, const NodeDefManager *ndef) :
|
||||
m_sound(sound),
|
||||
m_ndef(ndef),
|
||||
makes_footstep_sound(true),
|
||||
m_player_step_timer(0.0f),
|
||||
m_player_jump_timer(0.0f)
|
||||
{
|
||||
}
|
||||
m_sound(sound), m_ndef(ndef) {}
|
||||
|
||||
void playPlayerStep()
|
||||
{
|
||||
if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
|
||||
m_player_step_timer = 0.03;
|
||||
if (makes_footstep_sound)
|
||||
m_sound->playSound(m_player_step_sound);
|
||||
m_sound->playSound(0, m_player_step_sound);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,7 +286,7 @@ public:
|
||||
{
|
||||
if (m_player_jump_timer <= 0.0f) {
|
||||
m_player_jump_timer = 0.2f;
|
||||
m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
|
||||
m_sound->playSound(0, SoundSpec("player_jump", 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,33 +311,33 @@ public:
|
||||
static void cameraPunchLeft(MtEvent *e, void *data)
|
||||
{
|
||||
SoundMaker *sm = (SoundMaker *)data;
|
||||
sm->m_sound->playSound(sm->m_player_leftpunch_sound);
|
||||
sm->m_sound->playSound(sm->m_player_leftpunch_sound2);
|
||||
sm->m_sound->playSound(0, sm->m_player_leftpunch_sound);
|
||||
sm->m_sound->playSound(0, sm->m_player_leftpunch_sound2);
|
||||
}
|
||||
|
||||
static void cameraPunchRight(MtEvent *e, void *data)
|
||||
{
|
||||
SoundMaker *sm = (SoundMaker *)data;
|
||||
sm->m_sound->playSound(sm->m_player_rightpunch_sound);
|
||||
sm->m_sound->playSound(0, sm->m_player_rightpunch_sound);
|
||||
}
|
||||
|
||||
static void nodeDug(MtEvent *e, void *data)
|
||||
{
|
||||
SoundMaker *sm = (SoundMaker *)data;
|
||||
NodeDugEvent *nde = (NodeDugEvent *)e;
|
||||
sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
|
||||
sm->m_sound->playSound(0, sm->m_ndef->get(nde->n).sound_dug);
|
||||
}
|
||||
|
||||
static void playerDamage(MtEvent *e, void *data)
|
||||
{
|
||||
SoundMaker *sm = (SoundMaker *)data;
|
||||
sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
|
||||
sm->m_sound->playSound(0, SoundSpec("player_damage", 0.5));
|
||||
}
|
||||
|
||||
static void playerFallingDamage(MtEvent *e, void *data)
|
||||
{
|
||||
SoundMaker *sm = (SoundMaker *)data;
|
||||
sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
|
||||
sm->m_sound->playSound(0, SoundSpec("player_falling_damage", 0.5));
|
||||
}
|
||||
|
||||
void registerReceiver(MtEventManager *mgr)
|
||||
@ -365,42 +359,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// Locally stored sounds don't need to be preloaded because of this
|
||||
class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
|
||||
{
|
||||
std::set<std::string> m_fetched;
|
||||
private:
|
||||
void paths_insert(std::set<std::string> &dst_paths,
|
||||
const std::string &base,
|
||||
const std::string &name)
|
||||
{
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
|
||||
dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
|
||||
}
|
||||
public:
|
||||
void fetchSounds(const std::string &name,
|
||||
std::set<std::string> &dst_paths,
|
||||
std::set<std::string> &dst_datas)
|
||||
{
|
||||
if (m_fetched.count(name))
|
||||
return;
|
||||
|
||||
m_fetched.insert(name);
|
||||
|
||||
paths_insert(dst_paths, porting::path_share, name);
|
||||
paths_insert(dst_paths, porting::path_user, name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef s32 SamplerLayer_t;
|
||||
|
||||
@ -936,7 +894,6 @@ private:
|
||||
IWritableItemDefManager *itemdef_manager = nullptr;
|
||||
NodeDefManager *nodedef_manager = nullptr;
|
||||
|
||||
GameOnDemandSoundFetcher soundfetcher; // useful when testing
|
||||
std::unique_ptr<ISoundManager> sound_manager;
|
||||
SoundMaker *soundmaker = nullptr;
|
||||
|
||||
@ -1278,10 +1235,13 @@ void Game::run()
|
||||
if (m_is_paused)
|
||||
dtime = 0.0f;
|
||||
|
||||
if (!was_paused && m_is_paused)
|
||||
if (!was_paused && m_is_paused) {
|
||||
pauseAnimation();
|
||||
else if (was_paused && !m_is_paused)
|
||||
sound_manager->pauseAll();
|
||||
} else if (was_paused && !m_is_paused) {
|
||||
resumeAnimation();
|
||||
sound_manager->resumeAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_is_paused)
|
||||
@ -1397,11 +1357,13 @@ bool Game::initSound()
|
||||
#if USE_SOUND
|
||||
if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
|
||||
infostream << "Attempting to use OpenAL audio" << std::endl;
|
||||
sound_manager.reset(createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher));
|
||||
sound_manager = createOpenALSoundManager(g_sound_manager_singleton.get(),
|
||||
std::make_unique<SoundFallbackPathProvider>());
|
||||
if (!sound_manager)
|
||||
infostream << "Failed to initialize OpenAL audio" << std::endl;
|
||||
} else
|
||||
} else {
|
||||
infostream << "Sound disabled." << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!sound_manager) {
|
||||
@ -3194,10 +3156,13 @@ void Game::updateCamera(f32 dtime)
|
||||
void Game::updateSound(f32 dtime)
|
||||
{
|
||||
// Update sound listener
|
||||
LocalPlayer *player = client->getEnv().getLocalPlayer();
|
||||
ClientActiveObject *parent = player->getParent();
|
||||
v3s16 camera_offset = camera->getOffset();
|
||||
sound_manager->updateListener(
|
||||
camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
|
||||
v3f(0, 0, 0), // velocity
|
||||
(1.0f/BS) * camera->getCameraNode()->getPosition()
|
||||
+ intToFloat(camera_offset, 1.0f),
|
||||
(1.0f/BS) * (parent ? parent->getVelocity() : player->getSpeed()),
|
||||
camera->getDirection(),
|
||||
camera->getCameraNode()->getUpVector());
|
||||
|
||||
@ -3215,8 +3180,6 @@ void Game::updateSound(f32 dtime)
|
||||
}
|
||||
}
|
||||
|
||||
LocalPlayer *player = client->getEnv().getLocalPlayer();
|
||||
|
||||
// Tell the sound maker whether to make footstep sounds
|
||||
soundmaker->makes_footstep_sound = player->makes_footstep_sound;
|
||||
|
||||
@ -3332,7 +3295,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
|
||||
|
||||
runData.punching = false;
|
||||
|
||||
soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
|
||||
soundmaker->m_player_leftpunch_sound = SoundSpec();
|
||||
soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
|
||||
selected_def.sound_use : selected_def.sound_use_air;
|
||||
|
||||
@ -3530,7 +3493,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
|
||||
// Placing animation (always shown for feedback)
|
||||
camera->setDigging(1);
|
||||
|
||||
soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
|
||||
soundmaker->m_player_rightpunch_sound = SoundSpec();
|
||||
|
||||
// If the wielded item has node placement prediction,
|
||||
// make that happen
|
||||
|
97
src/client/sound.cpp
Normal file
97
src/client/sound.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2023 DS
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "sound.h"
|
||||
|
||||
#include "filesys.h"
|
||||
#include "log.h"
|
||||
#include "porting.h"
|
||||
#include "util/numeric.h"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::vector<std::string> SoundFallbackPathProvider::
|
||||
getLocalFallbackPathsForSoundname(const std::string &name)
|
||||
{
|
||||
std::vector<std::string> paths;
|
||||
|
||||
// only try each name once
|
||||
if (m_done_names.count(name))
|
||||
return paths;
|
||||
m_done_names.insert(name);
|
||||
|
||||
addThePaths(name, paths);
|
||||
|
||||
// remove duplicates
|
||||
std::sort(paths.begin(), paths.end());
|
||||
auto end = std::unique(paths.begin(), paths.end());
|
||||
paths.erase(end, paths.end());
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
void SoundFallbackPathProvider::addAllAlternatives(const std::string &common,
|
||||
std::vector<std::string> &paths)
|
||||
{
|
||||
paths.reserve(paths.size() + 11);
|
||||
for (auto &&ext : {".ogg", ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg",
|
||||
".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg", }) {
|
||||
paths.push_back(common + ext);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundFallbackPathProvider::addThePaths(const std::string &name,
|
||||
std::vector<std::string> &paths)
|
||||
{
|
||||
addAllAlternatives(porting::path_share + DIR_DELIM + "sounds" + DIR_DELIM + name, paths);
|
||||
addAllAlternatives(porting::path_user + DIR_DELIM + "sounds" + DIR_DELIM + name, paths);
|
||||
}
|
||||
|
||||
void ISoundManager::reportRemovedSound(sound_handle_t id)
|
||||
{
|
||||
if (id <= 0)
|
||||
return;
|
||||
|
||||
freeId(id);
|
||||
m_removed_sounds.push_back(id);
|
||||
}
|
||||
|
||||
sound_handle_t ISoundManager::allocateId(u32 num_owners)
|
||||
{
|
||||
while (m_occupied_ids.find(m_next_id) != m_occupied_ids.end()
|
||||
|| m_next_id == SOUND_HANDLE_T_MAX) {
|
||||
m_next_id = static_cast<sound_handle_t>(
|
||||
myrand() % static_cast<u32>(SOUND_HANDLE_T_MAX - 1) + 1);
|
||||
}
|
||||
sound_handle_t id = m_next_id++;
|
||||
m_occupied_ids.emplace(id, num_owners);
|
||||
return id;
|
||||
}
|
||||
|
||||
void ISoundManager::freeId(sound_handle_t id, u32 num_owners)
|
||||
{
|
||||
auto it = m_occupied_ids.find(id);
|
||||
if (it == m_occupied_ids.end())
|
||||
return;
|
||||
if (it->second <= num_owners)
|
||||
m_occupied_ids.erase(it);
|
||||
else
|
||||
it->second -= num_owners;
|
||||
}
|
@ -19,72 +19,168 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include "irr_v3d.h"
|
||||
#include "../sound.h"
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
class OnDemandSoundFetcher
|
||||
struct SoundSpec;
|
||||
|
||||
class SoundFallbackPathProvider
|
||||
{
|
||||
public:
|
||||
virtual void fetchSounds(const std::string &name,
|
||||
std::set<std::string> &dst_paths,
|
||||
std::set<std::string> &dst_datas) = 0;
|
||||
virtual ~SoundFallbackPathProvider() = default;
|
||||
std::vector<std::string> getLocalFallbackPathsForSoundname(const std::string &name);
|
||||
protected:
|
||||
virtual void addThePaths(const std::string &name, std::vector<std::string> &paths);
|
||||
// adds <common>.ogg, <common>.1.ogg, ..., <common>.9.ogg to paths
|
||||
void addAllAlternatives(const std::string &common, std::vector<std::string> &paths);
|
||||
private:
|
||||
std::unordered_set<std::string> m_done_names;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* IDs for playing sounds.
|
||||
* 0 is for sounds that are never modified after creation.
|
||||
* Negative numbers are invalid.
|
||||
* Positive numbers are allocated via allocateId and are manually reference-counted.
|
||||
*/
|
||||
using sound_handle_t = int;
|
||||
|
||||
constexpr sound_handle_t SOUND_HANDLE_T_MAX = std::numeric_limits<sound_handle_t>::max();
|
||||
|
||||
class ISoundManager
|
||||
{
|
||||
private:
|
||||
std::unordered_map<sound_handle_t, u32> m_occupied_ids;
|
||||
sound_handle_t m_next_id = 1;
|
||||
std::vector<sound_handle_t> m_removed_sounds;
|
||||
|
||||
protected:
|
||||
void reportRemovedSound(sound_handle_t id);
|
||||
|
||||
public:
|
||||
virtual ~ISoundManager() = default;
|
||||
|
||||
// Multiple sounds can be loaded per name; when played, the sound
|
||||
// should be chosen randomly from alternatives
|
||||
// Return value determines success/failure
|
||||
virtual bool loadSoundFile(
|
||||
const std::string &name, const std::string &filepath) = 0;
|
||||
virtual bool loadSoundData(
|
||||
const std::string &name, const std::string &filedata) = 0;
|
||||
/**
|
||||
* Removes finished sounds, steps streamed sounds, and does similar tasks.
|
||||
* Should not be called while paused.
|
||||
* @param dtime In seconds.
|
||||
*/
|
||||
virtual void step(f32 dtime) = 0;
|
||||
/**
|
||||
* Pause all sound playback.
|
||||
*/
|
||||
virtual void pauseAll() = 0;
|
||||
/**
|
||||
* Resume sound playback after pause.
|
||||
*/
|
||||
virtual void resumeAll() = 0;
|
||||
|
||||
virtual void updateListener(
|
||||
const v3f &pos, const v3f &vel, const v3f &at, const v3f &up) = 0;
|
||||
virtual void setListenerGain(float gain) = 0;
|
||||
/**
|
||||
* @param pos In node-space.
|
||||
* @param vel In node-space.
|
||||
* @param at Vector in node-space pointing forwards.
|
||||
* @param up Vector in node-space pointing upwards, orthogonal to `at`.
|
||||
*/
|
||||
virtual void updateListener(const v3f &pos, const v3f &vel, const v3f &at,
|
||||
const v3f &up) = 0;
|
||||
virtual void setListenerGain(f32 gain) = 0;
|
||||
|
||||
// playSound functions return -1 on failure, otherwise a handle to the
|
||||
// sound. If name=="", call should be ignored without error.
|
||||
virtual int playSound(const SimpleSoundSpec &spec) = 0;
|
||||
virtual int playSoundAt(const SimpleSoundSpec &spec, const 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;
|
||||
/**
|
||||
* Adds a sound to load from a file (only OggVorbis).
|
||||
* @param name The name of the sound. Must be unique, otherwise call fails.
|
||||
* @param filepath The path for
|
||||
* @return true on success, false on failure (ie. sound was already added or
|
||||
* file does not exist).
|
||||
*/
|
||||
virtual bool loadSoundFile(const std::string &name, const std::string &filepath) = 0;
|
||||
/**
|
||||
* Same as `loadSoundFile`, but reads the OggVorbis file from memory.
|
||||
*/
|
||||
virtual bool loadSoundData(const std::string &name, std::string &&filedata) = 0;
|
||||
/**
|
||||
* Adds sound with name sound_name to group `group_name`. Creates the group
|
||||
* if non-existent.
|
||||
* @param sound_name The name of the sound, as used in `loadSoundData`.
|
||||
* @param group_name The name of the sound group.
|
||||
*/
|
||||
virtual void addSoundToGroup(const std::string &sound_name,
|
||||
const std::string &group_name) = 0;
|
||||
|
||||
/**
|
||||
* Plays a random sound from a sound group (position-less).
|
||||
* @param id Id for new sound. Move semantics apply if id > 0.
|
||||
*/
|
||||
virtual void playSound(sound_handle_t id, const SoundSpec &spec) = 0;
|
||||
/**
|
||||
* Same as `playSound`, but at a position.
|
||||
* @param pos In node-space.
|
||||
* @param vel In node-space.
|
||||
*/
|
||||
virtual void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos,
|
||||
const v3f &vel) = 0;
|
||||
/**
|
||||
* Request the sound to be stopped.
|
||||
* The id should be freed afterwards.
|
||||
*/
|
||||
virtual void stopSound(sound_handle_t sound) = 0;
|
||||
virtual void fadeSound(sound_handle_t sound, f32 step, f32 target_gain) = 0;
|
||||
/**
|
||||
* Update position and velocity of positional sound.
|
||||
* @param pos In node-space.
|
||||
* @param vel In node-space.
|
||||
*/
|
||||
virtual void updateSoundPosVel(sound_handle_t sound, const v3f &pos,
|
||||
const v3f &vel) = 0;
|
||||
|
||||
/**
|
||||
* Get and reset the list of sounds that were stopped.
|
||||
*/
|
||||
std::vector<sound_handle_t> pollRemovedSounds()
|
||||
{
|
||||
std::vector<sound_handle_t> ret;
|
||||
std::swap(m_removed_sounds, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a positive id.
|
||||
* The id will be returned again until freeId is called.
|
||||
* @param num_owners Owner-counter for id. Set this to 2, if you want to play
|
||||
* a sound and store the id also otherwhere.
|
||||
*/
|
||||
sound_handle_t allocateId(u32 num_owners);
|
||||
|
||||
/**
|
||||
* Free an id allocated via allocateId.
|
||||
* @param num_owners How much the owner-counter should be decreased. Id can
|
||||
* be reused when counter reaches 0.
|
||||
*/
|
||||
void freeId(sound_handle_t id, u32 num_owners = 1);
|
||||
};
|
||||
|
||||
class DummySoundManager : public ISoundManager
|
||||
class DummySoundManager final : public ISoundManager
|
||||
{
|
||||
public:
|
||||
virtual bool loadSoundFile(const std::string &name, const std::string &filepath)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual bool loadSoundData(const std::string &name, const std::string &filedata)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up)
|
||||
{
|
||||
}
|
||||
void setListenerGain(float gain) {}
|
||||
void step(f32 dtime) override {}
|
||||
void pauseAll() override {}
|
||||
void resumeAll() override {}
|
||||
|
||||
int playSound(const SimpleSoundSpec &spec) { return -1; }
|
||||
int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) { return -1; }
|
||||
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) {}
|
||||
void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up) override {}
|
||||
void setListenerGain(f32 gain) override {}
|
||||
|
||||
bool loadSoundFile(const std::string &name, const std::string &filepath) override { return true; }
|
||||
bool loadSoundData(const std::string &name, std::string &&filedata) override { return true; }
|
||||
void addSoundToGroup(const std::string &sound_name, const std::string &group_name) override {};
|
||||
|
||||
void playSound(sound_handle_t id, const SoundSpec &spec) override { reportRemovedSound(id); }
|
||||
void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos,
|
||||
const v3f &vel) override { reportRemovedSound(id); }
|
||||
void stopSound(sound_handle_t sound) override {}
|
||||
void fadeSound(sound_handle_t sound, f32 step, f32 target_gain) override {}
|
||||
void updateSoundPosVel(sound_handle_t sound, const v3f &pos, const v3f &vel) override {}
|
||||
};
|
||||
|
@ -22,703 +22,10 @@ with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||
*/
|
||||
|
||||
#include "sound_openal.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <al.h>
|
||||
#include <alc.h>
|
||||
//#include <alext.h>
|
||||
#elif defined(__APPLE__)
|
||||
#define OPENAL_DEPRECATED
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
//#include <OpenAL/alext.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <AL/alext.h>
|
||||
#endif
|
||||
#include <cmath>
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#include <cassert>
|
||||
#include "log.h"
|
||||
#include "util/numeric.h" // myrand()
|
||||
#include "porting.h"
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#define BUFFER_SIZE 30000
|
||||
#include "sound_openal_internal.h"
|
||||
|
||||
std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
|
||||
|
||||
typedef std::unique_ptr<ALCdevice, void (*)(ALCdevice *p)> unique_ptr_alcdevice;
|
||||
typedef std::unique_ptr<ALCcontext, void(*)(ALCcontext *p)> unique_ptr_alccontext;
|
||||
|
||||
static void delete_alcdevice(ALCdevice *p)
|
||||
{
|
||||
if (p)
|
||||
alcCloseDevice(p);
|
||||
}
|
||||
|
||||
static void delete_alccontext(ALCcontext *p)
|
||||
{
|
||||
if (p) {
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(p);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *alErrorString(ALenum err)
|
||||
{
|
||||
switch (err) {
|
||||
case AL_NO_ERROR:
|
||||
return "no error";
|
||||
case AL_INVALID_NAME:
|
||||
return "invalid name";
|
||||
case AL_INVALID_ENUM:
|
||||
return "invalid enum";
|
||||
case AL_INVALID_VALUE:
|
||||
return "invalid value";
|
||||
case AL_INVALID_OPERATION:
|
||||
return "invalid operation";
|
||||
case AL_OUT_OF_MEMORY:
|
||||
return "out of memory";
|
||||
default:
|
||||
return "<unknown OpenAL error>";
|
||||
}
|
||||
}
|
||||
|
||||
static ALenum warn_if_error(ALenum err, const char *desc)
|
||||
{
|
||||
if(err == AL_NO_ERROR)
|
||||
return err;
|
||||
warningstream<<desc<<": "<<alErrorString(err)<<std::endl;
|
||||
return err;
|
||||
}
|
||||
|
||||
void f3_set(ALfloat *f3, v3f v)
|
||||
{
|
||||
f3[0] = v.X;
|
||||
f3[1] = v.Y;
|
||||
f3[2] = v.Z;
|
||||
}
|
||||
|
||||
struct SoundBuffer
|
||||
{
|
||||
ALenum format;
|
||||
ALsizei freq;
|
||||
ALuint buffer_id;
|
||||
std::vector<char> buffer;
|
||||
};
|
||||
|
||||
SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile,
|
||||
const std::string &filename_for_logging)
|
||||
{
|
||||
int endian = 0; // 0 for Little-Endian, 1 for Big-Endian
|
||||
int bitStream;
|
||||
long bytes;
|
||||
char array[BUFFER_SIZE]; // Local fixed size array
|
||||
vorbis_info *pInfo;
|
||||
|
||||
SoundBuffer *snd = new SoundBuffer;
|
||||
|
||||
// Get some information about the OGG file
|
||||
pInfo = ov_info(oggFile, -1);
|
||||
|
||||
// Check the number of channels... always use 16-bit samples
|
||||
if(pInfo->channels == 1)
|
||||
snd->format = AL_FORMAT_MONO16;
|
||||
else
|
||||
snd->format = AL_FORMAT_STEREO16;
|
||||
|
||||
// The frequency of the sampling rate
|
||||
snd->freq = pInfo->rate;
|
||||
|
||||
// Keep reading until all is read
|
||||
do
|
||||
{
|
||||
// Read up to a buffer's worth of decoded sound data
|
||||
bytes = ov_read(oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream);
|
||||
|
||||
if(bytes < 0)
|
||||
{
|
||||
ov_clear(oggFile);
|
||||
infostream << "Audio: Error decoding "
|
||||
<< filename_for_logging << std::endl;
|
||||
delete snd;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Append to end of buffer
|
||||
snd->buffer.insert(snd->buffer.end(), array, array + bytes);
|
||||
} while (bytes > 0);
|
||||
|
||||
alGenBuffers(1, &snd->buffer_id);
|
||||
alBufferData(snd->buffer_id, snd->format,
|
||||
&(snd->buffer[0]), snd->buffer.size(),
|
||||
snd->freq);
|
||||
|
||||
ALenum error = alGetError();
|
||||
|
||||
if(error != AL_NO_ERROR){
|
||||
infostream << "Audio: OpenAL error: " << alErrorString(error)
|
||||
<< "preparing sound buffer" << std::endl;
|
||||
}
|
||||
|
||||
//infostream << "Audio file "
|
||||
// << filename_for_logging << " loaded" << std::endl;
|
||||
|
||||
// Clean up!
|
||||
ov_clear(oggFile);
|
||||
|
||||
return snd;
|
||||
}
|
||||
|
||||
SoundBuffer *load_ogg_from_file(const std::string &path)
|
||||
{
|
||||
OggVorbis_File oggFile;
|
||||
|
||||
// Try opening the given file.
|
||||
// This requires libvorbis >= 1.3.2, as
|
||||
// previous versions expect a non-const char *
|
||||
if (ov_fopen(path.c_str(), &oggFile) != 0) {
|
||||
infostream << "Audio: Error opening " << path
|
||||
<< " for decoding" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return load_opened_ogg_file(&oggFile, path);
|
||||
}
|
||||
|
||||
struct BufferSource {
|
||||
const char *buf;
|
||||
size_t cur_offset;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
size_t buffer_sound_read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
|
||||
{
|
||||
BufferSource *s = (BufferSource *)datasource;
|
||||
size_t copied_size = MYMIN(s->len - s->cur_offset, size);
|
||||
memcpy(ptr, s->buf + s->cur_offset, copied_size);
|
||||
s->cur_offset += copied_size;
|
||||
return copied_size;
|
||||
}
|
||||
|
||||
int buffer_sound_seek_func(void *datasource, ogg_int64_t offset, int whence)
|
||||
{
|
||||
BufferSource *s = (BufferSource *)datasource;
|
||||
if (whence == SEEK_SET) {
|
||||
if (offset < 0 || (size_t)MYMAX(offset, 0) >= s->len) {
|
||||
// offset out of bounds
|
||||
return -1;
|
||||
}
|
||||
s->cur_offset = offset;
|
||||
return 0;
|
||||
} else if (whence == SEEK_CUR) {
|
||||
if ((size_t)MYMIN(-offset, 0) > s->cur_offset
|
||||
|| s->cur_offset + offset > s->len) {
|
||||
// offset out of bounds
|
||||
return -1;
|
||||
}
|
||||
s->cur_offset += offset;
|
||||
return 0;
|
||||
}
|
||||
// invalid whence param (SEEK_END doesn't have to be supported)
|
||||
return -1;
|
||||
}
|
||||
|
||||
long BufferSourceell_func(void *datasource)
|
||||
{
|
||||
BufferSource *s = (BufferSource *)datasource;
|
||||
return s->cur_offset;
|
||||
}
|
||||
|
||||
static ov_callbacks g_buffer_ov_callbacks = {
|
||||
&buffer_sound_read_func,
|
||||
&buffer_sound_seek_func,
|
||||
nullptr,
|
||||
&BufferSourceell_func
|
||||
};
|
||||
|
||||
SoundBuffer *load_ogg_from_buffer(const std::string &buf, const std::string &id_for_log)
|
||||
{
|
||||
OggVorbis_File oggFile;
|
||||
|
||||
BufferSource s;
|
||||
s.buf = buf.c_str();
|
||||
s.cur_offset = 0;
|
||||
s.len = buf.size();
|
||||
|
||||
if (ov_open_callbacks(&s, &oggFile, nullptr, 0, g_buffer_ov_callbacks) != 0) {
|
||||
infostream << "Audio: Error opening " << id_for_log
|
||||
<< " for decoding" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return load_opened_ogg_file(&oggFile, id_for_log);
|
||||
}
|
||||
|
||||
struct PlayingSound
|
||||
{
|
||||
ALuint source_id;
|
||||
bool loop;
|
||||
};
|
||||
|
||||
class SoundManagerSingleton
|
||||
{
|
||||
public:
|
||||
unique_ptr_alcdevice m_device;
|
||||
unique_ptr_alccontext m_context;
|
||||
public:
|
||||
SoundManagerSingleton() :
|
||||
m_device(nullptr, delete_alcdevice),
|
||||
m_context(nullptr, delete_alccontext)
|
||||
{
|
||||
}
|
||||
|
||||
bool init()
|
||||
{
|
||||
if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice))) {
|
||||
errorstream << "Audio: Global Initialization: Failed to open device" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(m_context = unique_ptr_alccontext(
|
||||
alcCreateContext(m_device.get(), nullptr), delete_alccontext))) {
|
||||
errorstream << "Audio: Global Initialization: Failed to create context" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!alcMakeContextCurrent(m_context.get())) {
|
||||
errorstream << "Audio: Global Initialization: Failed to make current context" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
|
||||
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
errorstream << "Audio: Global Initialization: OpenAL Error " << alGetError() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
infostream << "Audio: Global Initialized: OpenAL " << alGetString(AL_VERSION)
|
||||
<< ", using " << alcGetString(m_device.get(), ALC_DEVICE_SPECIFIER)
|
||||
<< std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
~SoundManagerSingleton()
|
||||
{
|
||||
infostream << "Audio: Global Deinitialized." << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
class OpenALSoundManager: public ISoundManager
|
||||
{
|
||||
private:
|
||||
OnDemandSoundFetcher *m_fetcher;
|
||||
ALCdevice *m_device;
|
||||
ALCcontext *m_context;
|
||||
u16 m_last_used_id = 0; // only access within getFreeId() !
|
||||
std::unordered_map<std::string, std::vector<SoundBuffer*>> m_buffers;
|
||||
std::unordered_map<int, PlayingSound*> m_sounds_playing;
|
||||
struct FadeState {
|
||||
FadeState() = default;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
std::unordered_map<int, FadeState> m_sounds_fading;
|
||||
public:
|
||||
OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher):
|
||||
m_fetcher(fetcher),
|
||||
m_device(smg->m_device.get()),
|
||||
m_context(smg->m_context.get())
|
||||
{
|
||||
infostream << "Audio: Initialized: OpenAL " << std::endl;
|
||||
}
|
||||
|
||||
~OpenALSoundManager()
|
||||
{
|
||||
infostream << "Audio: Deinitializing..." << std::endl;
|
||||
|
||||
std::unordered_set<int> source_del_list;
|
||||
|
||||
for (const auto &sp : m_sounds_playing)
|
||||
source_del_list.insert(sp.first);
|
||||
|
||||
for (const auto &id : source_del_list)
|
||||
deleteSound(id);
|
||||
|
||||
for (auto &buffer : m_buffers) {
|
||||
for (SoundBuffer *sb : buffer.second) {
|
||||
alDeleteBuffers(1, &sb->buffer_id);
|
||||
|
||||
ALenum error = alGetError();
|
||||
if (error != AL_NO_ERROR) {
|
||||
warningstream << "Audio: Failed to free stream for "
|
||||
<< buffer.first << ": " << alErrorString(error) << std::endl;
|
||||
}
|
||||
|
||||
delete sb;
|
||||
}
|
||||
buffer.second.clear();
|
||||
}
|
||||
m_buffers.clear();
|
||||
|
||||
infostream << "Audio: Deinitialized." << std::endl;
|
||||
}
|
||||
|
||||
u16 getFreeId()
|
||||
{
|
||||
u16 startid = m_last_used_id;
|
||||
while (!isFreeId(++m_last_used_id)) {
|
||||
if (m_last_used_id == startid)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_last_used_id;
|
||||
}
|
||||
|
||||
inline bool isFreeId(int id) const
|
||||
{
|
||||
return id > 0 && m_sounds_playing.find(id) == m_sounds_playing.end();
|
||||
}
|
||||
|
||||
void step(float dtime)
|
||||
{
|
||||
doFades(dtime);
|
||||
}
|
||||
|
||||
void addBuffer(const std::string &name, SoundBuffer *buf)
|
||||
{
|
||||
std::unordered_map<std::string, std::vector<SoundBuffer*>>::iterator i =
|
||||
m_buffers.find(name);
|
||||
if(i != m_buffers.end()){
|
||||
i->second.push_back(buf);
|
||||
return;
|
||||
}
|
||||
std::vector<SoundBuffer*> bufs;
|
||||
bufs.push_back(buf);
|
||||
m_buffers[name] = std::move(bufs);
|
||||
}
|
||||
|
||||
SoundBuffer* getBuffer(const std::string &name)
|
||||
{
|
||||
std::unordered_map<std::string, std::vector<SoundBuffer*>>::iterator i =
|
||||
m_buffers.find(name);
|
||||
if(i == m_buffers.end())
|
||||
return nullptr;
|
||||
std::vector<SoundBuffer*> &bufs = i->second;
|
||||
int j = myrand() % bufs.size();
|
||||
return bufs[j];
|
||||
}
|
||||
|
||||
PlayingSound* createPlayingSound(SoundBuffer *buf, bool loop,
|
||||
float volume, float pitch)
|
||||
{
|
||||
infostream << "OpenALSoundManager: Creating playing sound" << std::endl;
|
||||
assert(buf);
|
||||
PlayingSound *sound = new PlayingSound;
|
||||
assert(sound);
|
||||
warn_if_error(alGetError(), "before createPlayingSound");
|
||||
alGenSources(1, &sound->source_id);
|
||||
alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
|
||||
alSourcei(sound->source_id, AL_SOURCE_RELATIVE, true);
|
||||
alSource3f(sound->source_id, AL_POSITION, 0, 0, 0);
|
||||
alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
|
||||
alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
|
||||
volume = std::fmax(0.0f, volume);
|
||||
alSourcef(sound->source_id, AL_GAIN, volume);
|
||||
alSourcef(sound->source_id, AL_PITCH, pitch);
|
||||
alSourcePlay(sound->source_id);
|
||||
warn_if_error(alGetError(), "createPlayingSound");
|
||||
return sound;
|
||||
}
|
||||
|
||||
PlayingSound* createPlayingSoundAt(SoundBuffer *buf, bool loop,
|
||||
float volume, v3f pos, float pitch)
|
||||
{
|
||||
infostream << "OpenALSoundManager: Creating positional playing sound"
|
||||
<< std::endl;
|
||||
assert(buf);
|
||||
PlayingSound *sound = new PlayingSound;
|
||||
|
||||
warn_if_error(alGetError(), "before createPlayingSoundAt");
|
||||
alGenSources(1, &sound->source_id);
|
||||
alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
|
||||
alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
|
||||
alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
|
||||
alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
|
||||
// Use alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED) and set reference
|
||||
// distance to clamp gain at <1 node distance, to avoid excessive
|
||||
// volume when closer
|
||||
alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 10.0f);
|
||||
alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
|
||||
// Multiply by 3 to compensate for reducing AL_REFERENCE_DISTANCE from
|
||||
// the previous value of 30 to the new value of 10
|
||||
volume = std::fmax(0.0f, volume * 3.0f);
|
||||
alSourcef(sound->source_id, AL_GAIN, volume);
|
||||
alSourcef(sound->source_id, AL_PITCH, pitch);
|
||||
alSourcePlay(sound->source_id);
|
||||
warn_if_error(alGetError(), "createPlayingSoundAt");
|
||||
return sound;
|
||||
}
|
||||
|
||||
int playSoundRaw(SoundBuffer *buf, bool loop, float volume, float pitch)
|
||||
{
|
||||
assert(buf);
|
||||
PlayingSound *sound = createPlayingSound(buf, loop, volume, pitch);
|
||||
if (!sound)
|
||||
return -1;
|
||||
|
||||
int handle = getFreeId();
|
||||
m_sounds_playing[handle] = sound;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void deleteSound(int id)
|
||||
{
|
||||
auto i = m_sounds_playing.find(id);
|
||||
if(i == m_sounds_playing.end())
|
||||
return;
|
||||
PlayingSound *sound = i->second;
|
||||
|
||||
alDeleteSources(1, &sound->source_id);
|
||||
|
||||
delete sound;
|
||||
m_sounds_playing.erase(id);
|
||||
}
|
||||
|
||||
/* If buffer does not exist, consult the fetcher */
|
||||
SoundBuffer* getFetchBuffer(const std::string &name)
|
||||
{
|
||||
SoundBuffer *buf = getBuffer(name);
|
||||
if(buf)
|
||||
return buf;
|
||||
if(!m_fetcher)
|
||||
return nullptr;
|
||||
std::set<std::string> paths;
|
||||
std::set<std::string> datas;
|
||||
m_fetcher->fetchSounds(name, paths, datas);
|
||||
for (const std::string &path : paths) {
|
||||
loadSoundFile(name, path);
|
||||
}
|
||||
for (const std::string &data : datas) {
|
||||
loadSoundData(name, data);
|
||||
}
|
||||
return getBuffer(name);
|
||||
}
|
||||
|
||||
// Remove stopped sounds
|
||||
void maintain()
|
||||
{
|
||||
if (!m_sounds_playing.empty()) {
|
||||
verbosestream << "OpenALSoundManager::maintain(): "
|
||||
<< m_sounds_playing.size() <<" playing sounds, "
|
||||
<< m_buffers.size() <<" sound names loaded"<<std::endl;
|
||||
}
|
||||
std::unordered_set<int> del_list;
|
||||
for (const auto &sp : m_sounds_playing) {
|
||||
int id = sp.first;
|
||||
PlayingSound *sound = sp.second;
|
||||
// If not playing, remove it
|
||||
{
|
||||
ALint state;
|
||||
alGetSourcei(sound->source_id, AL_SOURCE_STATE, &state);
|
||||
if(state != AL_PLAYING){
|
||||
del_list.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!del_list.empty())
|
||||
verbosestream<<"OpenALSoundManager::maintain(): deleting "
|
||||
<<del_list.size()<<" playing sounds"<<std::endl;
|
||||
for (int i : del_list) {
|
||||
deleteSound(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Interface */
|
||||
|
||||
bool loadSoundFile(const std::string &name,
|
||||
const std::string &filepath)
|
||||
{
|
||||
SoundBuffer *buf = load_ogg_from_file(filepath);
|
||||
if (buf)
|
||||
addBuffer(name, buf);
|
||||
return !!buf;
|
||||
}
|
||||
|
||||
bool loadSoundData(const std::string &name,
|
||||
const std::string &filedata)
|
||||
{
|
||||
SoundBuffer *buf = load_ogg_from_buffer(filedata, name);
|
||||
if (buf)
|
||||
addBuffer(name, buf);
|
||||
return !!buf;
|
||||
}
|
||||
|
||||
void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up)
|
||||
{
|
||||
alListener3f(AL_POSITION, pos.X, pos.Y, pos.Z);
|
||||
alListener3f(AL_VELOCITY, vel.X, vel.Y, vel.Z);
|
||||
ALfloat f[6];
|
||||
f3_set(f, at);
|
||||
f3_set(f+3, -up);
|
||||
alListenerfv(AL_ORIENTATION, f);
|
||||
warn_if_error(alGetError(), "updateListener");
|
||||
}
|
||||
|
||||
void setListenerGain(float gain)
|
||||
{
|
||||
alListenerf(AL_GAIN, gain);
|
||||
}
|
||||
|
||||
int playSound(const SimpleSoundSpec &spec)
|
||||
{
|
||||
maintain();
|
||||
if (spec.name.empty())
|
||||
return 0;
|
||||
SoundBuffer *buf = getFetchBuffer(spec.name);
|
||||
if(!buf){
|
||||
infostream << "OpenALSoundManager: \"" << spec.name << "\" not found."
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int handle = -1;
|
||||
if (spec.fade > 0) {
|
||||
handle = playSoundRaw(buf, spec.loop, 0.0f, spec.pitch);
|
||||
fadeSound(handle, spec.fade, spec.gain);
|
||||
} else {
|
||||
handle = playSoundRaw(buf, spec.loop, spec.gain, spec.pitch);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos)
|
||||
{
|
||||
maintain();
|
||||
if (spec.name.empty())
|
||||
return 0;
|
||||
SoundBuffer *buf = getFetchBuffer(spec.name);
|
||||
if (!buf) {
|
||||
infostream << "OpenALSoundManager: \"" << spec.name << "\" not found."
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
PlayingSound *sound = createPlayingSoundAt(buf, spec.loop, spec.gain, pos, spec.pitch);
|
||||
if (!sound)
|
||||
return -1;
|
||||
int handle = getFreeId();
|
||||
m_sounds_playing[handle] = sound;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void stopSound(int sound)
|
||||
{
|
||||
maintain();
|
||||
deleteSound(sound);
|
||||
}
|
||||
|
||||
void fadeSound(int soundid, float step, float gain)
|
||||
{
|
||||
// Ignore the command if step isn't valid.
|
||||
if (step == 0 || soundid < 0)
|
||||
return;
|
||||
|
||||
float current_gain = getSoundGain(soundid);
|
||||
step = gain - current_gain > 0 ? abs(step) : -abs(step);
|
||||
if (m_sounds_fading.find(soundid) != m_sounds_fading.end()) {
|
||||
auto current_fade = m_sounds_fading[soundid];
|
||||
// Do not replace the fade if it's equivalent.
|
||||
if (current_fade.target_gain == gain && current_fade.step == step)
|
||||
return;
|
||||
m_sounds_fading.erase(soundid);
|
||||
}
|
||||
gain = rangelim(gain, 0, 1);
|
||||
m_sounds_fading[soundid] = FadeState(step, current_gain, gain);
|
||||
}
|
||||
|
||||
void doFades(float dtime)
|
||||
{
|
||||
for (auto i = m_sounds_fading.begin(); i != m_sounds_fading.end();) {
|
||||
FadeState& fade = i->second;
|
||||
assert(fade.step != 0);
|
||||
fade.current_gain += (fade.step * dtime);
|
||||
|
||||
if (fade.step < 0.f)
|
||||
fade.current_gain = std::max(fade.current_gain, fade.target_gain);
|
||||
else
|
||||
fade.current_gain = std::min(fade.current_gain, fade.target_gain);
|
||||
|
||||
if (fade.current_gain <= 0.f)
|
||||
stopSound(i->first);
|
||||
else
|
||||
updateSoundGain(i->first, fade.current_gain);
|
||||
|
||||
// The increment must happen during the erase call, or else it'll segfault.
|
||||
if (fade.current_gain == fade.target_gain)
|
||||
m_sounds_fading.erase(i++);
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
bool soundExists(int sound)
|
||||
{
|
||||
maintain();
|
||||
return (m_sounds_playing.count(sound) != 0);
|
||||
}
|
||||
|
||||
void updateSoundPosition(int id, v3f pos)
|
||||
{
|
||||
auto i = m_sounds_playing.find(id);
|
||||
if (i == m_sounds_playing.end())
|
||||
return;
|
||||
PlayingSound *sound = i->second;
|
||||
|
||||
alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
|
||||
alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
|
||||
alSource3f(sound->source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 10.0f);
|
||||
}
|
||||
|
||||
bool updateSoundGain(int id, float gain)
|
||||
{
|
||||
auto 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)
|
||||
{
|
||||
auto 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;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
|
||||
{
|
||||
auto smg = std::make_shared<SoundManagerSingleton>();
|
||||
@ -728,7 +35,8 @@ std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
|
||||
return smg;
|
||||
}
|
||||
|
||||
ISoundManager *createOpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher)
|
||||
std::unique_ptr<ISoundManager> createOpenALSoundManager(SoundManagerSingleton *smg,
|
||||
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider)
|
||||
{
|
||||
return new OpenALSoundManager(smg, fetcher);
|
||||
return std::make_unique<OpenALSoundManager>(smg, std::move(fallback_path_provider));
|
||||
};
|
||||
|
@ -19,13 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "sound.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class SoundManagerSingleton;
|
||||
extern std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
|
||||
|
||||
std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton();
|
||||
ISoundManager *createOpenALSoundManager(
|
||||
SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher);
|
||||
std::unique_ptr<ISoundManager> createOpenALSoundManager(
|
||||
SoundManagerSingleton *smg,
|
||||
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider);
|
||||
|
1125
src/client/sound_openal_internal.cpp
Normal file
1125
src/client/sound_openal_internal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
613
src/client/sound_openal_internal.h
Normal file
613
src/client/sound_openal_internal.h
Normal file
@ -0,0 +1,613 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2022 DS
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
OpenAL support based on work by:
|
||||
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log.h"
|
||||
#include "porting.h"
|
||||
#include "sound_openal.h"
|
||||
#include "util/basic_macros.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <al.h>
|
||||
#include <alc.h>
|
||||
//#include <alext.h>
|
||||
#elif defined(__APPLE__)
|
||||
#define OPENAL_DEPRECATED
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
//#include <OpenAL/alext.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <AL/alext.h>
|
||||
#endif
|
||||
#include <vorbis/vorbisfile.h>
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* The coordinate space for sounds (sound-space):
|
||||
* ----------------------------------------------
|
||||
*
|
||||
* * The functions from ISoundManager (see sound.h) take spatial vectors in node-space.
|
||||
* * All other `v3f`s here are, if not told otherwise, in sound-space, which is
|
||||
* defined as node-space mirrored along the x-axis.
|
||||
* (This is needed because OpenAL uses a right-handed coordinate system.)
|
||||
* * Use `swap_handedness()` to convert between those two coordinate spaces.
|
||||
*
|
||||
*
|
||||
* How sounds are loaded:
|
||||
* ----------------------
|
||||
*
|
||||
* * Step 1:
|
||||
* `loadSoundFile` or `loadSoundFile` is called. This adds an unopen sound with
|
||||
* the given name to `m_sound_datas_unopen`.
|
||||
* Unopen / lazy sounds (`ISoundDataUnopen`) are ogg-vorbis files that we did not yet
|
||||
* start to decode. (Decoding an unopen sound does not fail under normal circumstances
|
||||
* (because we check whether the file exists at least), if it does fail anyways,
|
||||
* we should notify the user.)
|
||||
* * Step 2:
|
||||
* `addSoundToGroup` is called, to add the name from step 1 to a group. If the
|
||||
* group does not yet exist, a new one is created. A group can later be played.
|
||||
* (The mapping is stored in `m_sound_groups`.)
|
||||
* * Step 3:
|
||||
* `playSound` or `playSoundAt` is called.
|
||||
* * Step 3.1:
|
||||
* If the group with the name `spec.name` does not exist, and `spec.use_local_fallback`
|
||||
* is true, a new group is created using the user's sound-pack.
|
||||
* * Step 3.2:
|
||||
* We choose one random sound name from the given group.
|
||||
* * Step 3.3:
|
||||
* We open the sound (see `openSingleSound`).
|
||||
* If the sound is already open (in `m_sound_datas_open`), we take that one.
|
||||
* Otherwise we open it by calling `ISoundDataUnopen::open`. We choose (by
|
||||
* sound length), whether it's a single-buffer (`SoundDataOpenBuffer`) or
|
||||
* streamed (`SoundDataOpenStream`) sound.
|
||||
* Single-buffer sounds are always completely loaded. Streamed sounds can be
|
||||
* partially loaded.
|
||||
* The sound is erased from `m_sound_datas_unopen` and added to `m_sound_datas_open`.
|
||||
* Open sounds are kept forever.
|
||||
* * Step 3.4:
|
||||
* We create the new `PlayingSound`. It has a `shared_ptr` to its open sound.
|
||||
* If the open sound is streaming, the playing sound needs to be stepped using
|
||||
* `PlayingSound::stepStream` for enqueuing buffers. For this purpose, the sound
|
||||
* is added to `m_sounds_streaming` (as `weak_ptr`).
|
||||
* If the sound is fading, it is added to `m_sounds_fading` for regular fade-stepping.
|
||||
* The sound is also added to `m_sounds_playing`, so that one can access it
|
||||
* via its sound handle.
|
||||
* * Step 4:
|
||||
* Streaming sounds are updated. For details see [Streaming of sounds].
|
||||
* * Step 5:
|
||||
* At deinitialization, we can just let the destructors do their work.
|
||||
* Sound sources are deleted (and with this also stopped) by ~PlayingSound.
|
||||
* Buffers can't be deleted while sound sources using them exist, because
|
||||
* PlayingSound has a shared_ptr to its ISoundData.
|
||||
*
|
||||
*
|
||||
* Streaming of sounds:
|
||||
* --------------------
|
||||
*
|
||||
* In each "bigstep", all streamed sounds are stepStream()ed. This means a
|
||||
* sound can be stepped at any point in time in the bigstep's interval.
|
||||
*
|
||||
* In the worst case, a sound is stepped at the start of one bigstep and in the
|
||||
* end of the next bigstep. So between two stepStream()-calls lie at most
|
||||
* 2 * STREAM_BIGSTEP_TIME seconds.
|
||||
* As there are always 2 sound buffers enqueued, at least one untouched full buffer
|
||||
* is still available after the first stepStream().
|
||||
* If we take a MIN_STREAM_BUFFER_LENGTH > 2 * STREAM_BIGSTEP_TIME, we can hence
|
||||
* not run into an empty queue.
|
||||
*
|
||||
* The MIN_STREAM_BUFFER_LENGTH needs to be a little bigger because of dtime jitter,
|
||||
* other sounds that may have taken long to stepStream(), and sounds being played
|
||||
* faster due to Doppler effect.
|
||||
*
|
||||
*/
|
||||
|
||||
// constants
|
||||
|
||||
// in seconds
|
||||
constexpr f32 REMOVE_DEAD_SOUNDS_INTERVAL = 2.0f;
|
||||
// maximum length in seconds that a sound can have without being streamed
|
||||
constexpr f32 SOUND_DURATION_MAX_SINGLE = 3.0f;
|
||||
// minimum time in seconds of a single buffer in a streamed sound
|
||||
constexpr f32 MIN_STREAM_BUFFER_LENGTH = 1.0f;
|
||||
// duration in seconds of one bigstep
|
||||
constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
|
||||
|
||||
static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f,
|
||||
"See [Streaming of sounds].");
|
||||
static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f,
|
||||
"There's no benefit in streaming if we can't queue more than 2 buffers.");
|
||||
|
||||
|
||||
/**
|
||||
* RAII wrapper for openal sound buffers.
|
||||
*/
|
||||
struct RAIIALSoundBuffer final
|
||||
{
|
||||
RAIIALSoundBuffer() noexcept = default;
|
||||
explicit RAIIALSoundBuffer(ALuint buffer) noexcept : m_buffer(buffer) {};
|
||||
|
||||
~RAIIALSoundBuffer() noexcept { reset(0); }
|
||||
|
||||
DISABLE_CLASS_COPY(RAIIALSoundBuffer)
|
||||
|
||||
RAIIALSoundBuffer(RAIIALSoundBuffer &&other) noexcept : m_buffer(other.release()) {}
|
||||
RAIIALSoundBuffer &operator=(RAIIALSoundBuffer &&other) noexcept;
|
||||
|
||||
ALuint get() noexcept { return m_buffer; }
|
||||
|
||||
ALuint release() noexcept { return std::exchange(m_buffer, 0); }
|
||||
|
||||
void reset(ALuint buf) noexcept;
|
||||
|
||||
static RAIIALSoundBuffer generate() noexcept;
|
||||
|
||||
private:
|
||||
// According to openal specification:
|
||||
// > Deleting buffer name 0 is a legal NOP.
|
||||
//
|
||||
// and:
|
||||
// > [...] the NULL buffer (i.e., 0) which can always be queued.
|
||||
ALuint m_buffer = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* For vorbisfile to read from our buffer instead of from a file.
|
||||
*/
|
||||
struct OggVorbisBufferSource {
|
||||
std::string buf;
|
||||
size_t cur_offset = 0;
|
||||
|
||||
static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource) noexcept;
|
||||
static int seek_func(void *datasource, ogg_int64_t offset, int whence) noexcept;
|
||||
static int close_func(void *datasource) noexcept;
|
||||
static long tell_func(void *datasource) noexcept;
|
||||
|
||||
static const ov_callbacks s_ov_callbacks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Metadata of an Ogg-Vorbis file, used for decoding.
|
||||
* We query this information once and store it in this struct.
|
||||
*/
|
||||
struct OggFileDecodeInfo {
|
||||
std::string name_for_logging;
|
||||
bool is_stereo;
|
||||
ALenum format; // AL_FORMAT_MONO16 or AL_FORMAT_STEREO16
|
||||
size_t bytes_per_sample;
|
||||
ALsizei freq;
|
||||
ALuint length_samples = 0;
|
||||
f32 length_seconds = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* RAII wrapper for OggVorbis_File.
|
||||
*/
|
||||
struct RAIIOggFile {
|
||||
bool m_needs_clear = false;
|
||||
OggVorbis_File m_file;
|
||||
|
||||
RAIIOggFile() = default;
|
||||
|
||||
DISABLE_CLASS_COPY(RAIIOggFile)
|
||||
|
||||
~RAIIOggFile() noexcept
|
||||
{
|
||||
if (m_needs_clear)
|
||||
ov_clear(&m_file);
|
||||
}
|
||||
|
||||
OggVorbis_File *get() { return &m_file; }
|
||||
|
||||
std::optional<OggFileDecodeInfo> getDecodeInfo(const std::string &filename_for_logging);
|
||||
|
||||
/**
|
||||
* Main function for loading ogg vorbis sounds.
|
||||
* Loads exactly the specified interval of PCM-data, and creates an OpenAL
|
||||
* buffer with it.
|
||||
*
|
||||
* @param decode_info Cached meta information of the file.
|
||||
* @param pcm_start First sample in the interval.
|
||||
* @param pcm_end One after last sample of the interval (=> exclusive).
|
||||
* @return An AL sound buffer, or a 0-buffer on failure.
|
||||
*/
|
||||
RAIIALSoundBuffer loadBuffer(const OggFileDecodeInfo &decode_info, ALuint pcm_start,
|
||||
ALuint pcm_end);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class for the openal device and context
|
||||
*/
|
||||
class SoundManagerSingleton
|
||||
{
|
||||
public:
|
||||
struct AlcDeviceDeleter {
|
||||
void operator()(ALCdevice *p)
|
||||
{
|
||||
alcCloseDevice(p);
|
||||
}
|
||||
};
|
||||
|
||||
struct AlcContextDeleter {
|
||||
void operator()(ALCcontext *p)
|
||||
{
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(p);
|
||||
}
|
||||
};
|
||||
|
||||
using unique_ptr_alcdevice = std::unique_ptr<ALCdevice, AlcDeviceDeleter>;
|
||||
using unique_ptr_alccontext = std::unique_ptr<ALCcontext, AlcContextDeleter>;
|
||||
|
||||
unique_ptr_alcdevice m_device;
|
||||
unique_ptr_alccontext m_context;
|
||||
|
||||
public:
|
||||
bool init();
|
||||
|
||||
~SoundManagerSingleton();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stores sound pcm data buffers.
|
||||
*/
|
||||
struct ISoundDataOpen
|
||||
{
|
||||
OggFileDecodeInfo m_decode_info;
|
||||
|
||||
explicit ISoundDataOpen(const OggFileDecodeInfo &decode_info) :
|
||||
m_decode_info(decode_info) {}
|
||||
|
||||
virtual ~ISoundDataOpen() = default;
|
||||
|
||||
/**
|
||||
* Iff the data is streaming, there is more than one buffer.
|
||||
* @return Whether it's streaming data.
|
||||
*/
|
||||
virtual bool isStreaming() const noexcept = 0;
|
||||
|
||||
/**
|
||||
* Load a buffer containing data starting at the given offset. Or just get it
|
||||
* if it was already loaded.
|
||||
*
|
||||
* This function returns multiple values:
|
||||
* * `buffer`: The OpenAL buffer.
|
||||
* * `buffer_end`: The offset (in the file) where `buffer` ends (exclusive).
|
||||
* * `offset_in_buffer`: Offset relative to `buffer`'s start where the requested
|
||||
* `offset` is.
|
||||
* `offset_in_buffer == 0` is guaranteed if some loaded buffer ends at
|
||||
* `offset`.
|
||||
*
|
||||
* @param offset The start of the buffer.
|
||||
* @return `{buffer, buffer_end, offset_in_buffer}` or `{0, sound_data_end, 0}`
|
||||
* if `offset` is invalid.
|
||||
*/
|
||||
virtual std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) = 0;
|
||||
|
||||
static std::shared_ptr<ISoundDataOpen> fromOggFile(std::unique_ptr<RAIIOggFile> oggfile,
|
||||
const std::string &filename_for_logging);
|
||||
};
|
||||
|
||||
/**
|
||||
* Will be opened lazily when first used.
|
||||
*/
|
||||
struct ISoundDataUnopen
|
||||
{
|
||||
virtual ~ISoundDataUnopen() = default;
|
||||
|
||||
// Note: The ISoundDataUnopen is moved (see &&). It is not meant to be kept
|
||||
// after opening.
|
||||
virtual std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sound file is in a memory buffer.
|
||||
*/
|
||||
struct SoundDataUnopenBuffer final : ISoundDataUnopen
|
||||
{
|
||||
std::string m_buffer;
|
||||
|
||||
explicit SoundDataUnopenBuffer(std::string &&buffer) : m_buffer(std::move(buffer)) {}
|
||||
|
||||
std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sound file is in file system.
|
||||
*/
|
||||
struct SoundDataUnopenFile final : ISoundDataUnopen
|
||||
{
|
||||
std::string m_path;
|
||||
|
||||
explicit SoundDataUnopenFile(const std::string &path) : m_path(path) {}
|
||||
|
||||
std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Non-streaming opened sound data.
|
||||
* All data is completely loaded in one buffer.
|
||||
*/
|
||||
struct SoundDataOpenBuffer final : ISoundDataOpen
|
||||
{
|
||||
RAIIALSoundBuffer m_buffer;
|
||||
|
||||
SoundDataOpenBuffer(std::unique_ptr<RAIIOggFile> oggfile,
|
||||
const OggFileDecodeInfo &decode_info);
|
||||
|
||||
bool isStreaming() const noexcept override { return false; }
|
||||
|
||||
std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) override
|
||||
{
|
||||
if (offset >= m_decode_info.length_samples)
|
||||
return {0, m_decode_info.length_samples, 0};
|
||||
return {m_buffer.get(), m_decode_info.length_samples, offset};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Streaming opened sound data.
|
||||
*
|
||||
* Uses a sorted list of contiguous sound data regions (`ContiguousBuffers`s) for
|
||||
* efficient seeking.
|
||||
*/
|
||||
struct SoundDataOpenStream final : ISoundDataOpen
|
||||
{
|
||||
/**
|
||||
* An OpenAL buffer that goes until `m_end` (exclusive).
|
||||
*/
|
||||
struct SoundBufferUntil final
|
||||
{
|
||||
ALuint m_end;
|
||||
RAIIALSoundBuffer m_buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* A sorted non-empty vector of contiguous buffers.
|
||||
* The start (inclusive) of each buffer is the end of its predecessor, or
|
||||
* `m_start` for the first buffer.
|
||||
*/
|
||||
struct ContiguousBuffers final
|
||||
{
|
||||
ALuint m_start;
|
||||
std::vector<SoundBufferUntil> m_buffers;
|
||||
};
|
||||
|
||||
std::unique_ptr<RAIIOggFile> m_oggfile;
|
||||
// A sorted vector of non-overlapping, non-contiguous `ContiguousBuffers`s.
|
||||
std::vector<ContiguousBuffers> m_bufferss;
|
||||
|
||||
SoundDataOpenStream(std::unique_ptr<RAIIOggFile> oggfile,
|
||||
const OggFileDecodeInfo &decode_info);
|
||||
|
||||
bool isStreaming() const noexcept override { return true; }
|
||||
|
||||
std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) override;
|
||||
|
||||
private:
|
||||
// offset must be before after_it's m_start and after (after_it-1)'s last m_end
|
||||
// new buffer will be inserted into m_bufferss before after_it
|
||||
// returns same as getOrLoadBufferAt
|
||||
std::tuple<ALuint, ALuint, ALuint> loadBufferAt(ALuint offset,
|
||||
std::vector<ContiguousBuffers>::iterator after_it);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A sound that is currently played.
|
||||
* Can be streaming.
|
||||
* Can be fading.
|
||||
*/
|
||||
class PlayingSound final
|
||||
{
|
||||
struct FadeState {
|
||||
f32 step;
|
||||
f32 target_gain;
|
||||
};
|
||||
|
||||
ALuint m_source_id;
|
||||
std::shared_ptr<ISoundDataOpen> m_data;
|
||||
ALuint m_next_sample_pos = 0;
|
||||
bool m_looping;
|
||||
bool m_is_positional;
|
||||
bool m_stopped_means_dead = true;
|
||||
std::optional<FadeState> m_fade_state = std::nullopt;
|
||||
|
||||
public:
|
||||
PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data, bool loop,
|
||||
f32 volume, f32 pitch, f32 start_time,
|
||||
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
||||
|
||||
~PlayingSound() noexcept
|
||||
{
|
||||
alDeleteSources(1, &m_source_id);
|
||||
}
|
||||
|
||||
DISABLE_CLASS_COPY(PlayingSound)
|
||||
|
||||
// return false means streaming finished
|
||||
bool stepStream();
|
||||
|
||||
// retruns true if it wasn't fading already
|
||||
bool fade(f32 step, f32 target_gain) noexcept;
|
||||
|
||||
// returns true if more fade is needed later
|
||||
bool doFade(f32 dtime) noexcept;
|
||||
|
||||
void updatePosVel(const v3f &pos, const v3f &vel) noexcept;
|
||||
|
||||
void setGain(f32 gain) noexcept;
|
||||
|
||||
f32 getGain() noexcept;
|
||||
|
||||
void setPitch(f32 pitch) noexcept { alSourcef(m_source_id, AL_PITCH, pitch); }
|
||||
|
||||
bool isStreaming() const noexcept { return m_data->isStreaming(); }
|
||||
|
||||
void play() noexcept { alSourcePlay(m_source_id); }
|
||||
|
||||
// returns one of AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED
|
||||
ALint getState() noexcept
|
||||
{
|
||||
ALint state;
|
||||
alGetSourcei(m_source_id, AL_SOURCE_STATE, &state);
|
||||
return state;
|
||||
}
|
||||
|
||||
bool isDead() noexcept
|
||||
{
|
||||
// streaming sounds can (but should not) stop because the queue runs empty
|
||||
return m_stopped_means_dead && getState() == AL_STOPPED;
|
||||
}
|
||||
|
||||
void pause() noexcept
|
||||
{
|
||||
// this is a NOP if state != AL_PLAYING
|
||||
alSourcePause(m_source_id);
|
||||
}
|
||||
|
||||
void resume() noexcept
|
||||
{
|
||||
if (getState() == AL_PAUSED)
|
||||
play();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* The public ISoundManager interface
|
||||
*/
|
||||
|
||||
class OpenALSoundManager final : public ISoundManager
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<SoundFallbackPathProvider> m_fallback_path_provider;
|
||||
|
||||
ALCdevice *m_device;
|
||||
ALCcontext *m_context;
|
||||
|
||||
// time in seconds until which removeDeadSounds will be called again
|
||||
f32 m_time_until_dead_removal = REMOVE_DEAD_SOUNDS_INTERVAL;
|
||||
|
||||
// loaded sounds
|
||||
std::unordered_map<std::string, std::unique_ptr<ISoundDataUnopen>> m_sound_datas_unopen;
|
||||
std::unordered_map<std::string, std::shared_ptr<ISoundDataOpen>> m_sound_datas_open;
|
||||
// sound groups
|
||||
std::unordered_map<std::string, std::vector<std::string>> m_sound_groups;
|
||||
|
||||
// currently playing sounds
|
||||
std::unordered_map<sound_handle_t, std::shared_ptr<PlayingSound>> m_sounds_playing;
|
||||
|
||||
// streamed sounds
|
||||
std::vector<std::weak_ptr<PlayingSound>> m_sounds_streaming_current_bigstep;
|
||||
std::vector<std::weak_ptr<PlayingSound>> m_sounds_streaming_next_bigstep;
|
||||
// time left until current bigstep finishes
|
||||
f32 m_stream_timer = STREAM_BIGSTEP_TIME;
|
||||
|
||||
std::vector<std::weak_ptr<PlayingSound>> m_sounds_fading;
|
||||
|
||||
// if true, all sounds will be directly paused after creation
|
||||
bool m_is_paused = false;
|
||||
|
||||
private:
|
||||
void stepStreams(f32 dtime);
|
||||
void doFades(f32 dtime);
|
||||
|
||||
/**
|
||||
* Gives the open sound for a loaded sound.
|
||||
* Opens the sound if currently unopened.
|
||||
*
|
||||
* @param sound_name Name of the sound.
|
||||
* @return The open sound.
|
||||
*/
|
||||
std::shared_ptr<ISoundDataOpen> openSingleSound(const std::string &sound_name);
|
||||
|
||||
/**
|
||||
* Gets a random sound name from a group.
|
||||
*
|
||||
* @param group_name The name of the sound group.
|
||||
* @return The name of a sound in the group, or "" on failure. Getting the
|
||||
* sound with `openSingleSound` directly afterwards will not fail.
|
||||
*/
|
||||
std::string getLoadedSoundNameFromGroup(const std::string &group_name);
|
||||
|
||||
/**
|
||||
* Same as `getLoadedSoundNameFromGroup`, but if sound does not exist, try to
|
||||
* load from local files.
|
||||
*/
|
||||
std::string getOrLoadLoadedSoundNameFromGroup(const std::string &group_name);
|
||||
|
||||
std::shared_ptr<PlayingSound> createPlayingSound(const std::string &sound_name,
|
||||
bool loop, f32 volume, f32 pitch, f32 start_time,
|
||||
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
||||
|
||||
void playSoundGeneric(sound_handle_t id, const std::string &group_name, bool loop,
|
||||
f32 volume, f32 fade, f32 pitch, bool use_local_fallback, f32 start_time,
|
||||
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
||||
|
||||
/**
|
||||
* Deletes sounds that are dead (=finished).
|
||||
*
|
||||
* @return Number of removed sounds.
|
||||
*/
|
||||
int removeDeadSounds();
|
||||
|
||||
public:
|
||||
OpenALSoundManager(SoundManagerSingleton *smg,
|
||||
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider);
|
||||
|
||||
~OpenALSoundManager() override;
|
||||
|
||||
DISABLE_CLASS_COPY(OpenALSoundManager)
|
||||
|
||||
/* Interface */
|
||||
|
||||
void step(f32 dtime) override;
|
||||
void pauseAll() override;
|
||||
void resumeAll() override;
|
||||
|
||||
void updateListener(const v3f &pos_, const v3f &vel_, const v3f &at_, const v3f &up_) override;
|
||||
void setListenerGain(f32 gain) override;
|
||||
|
||||
bool loadSoundFile(const std::string &name, const std::string &filepath) override;
|
||||
bool loadSoundData(const std::string &name, std::string &&filedata) override;
|
||||
void addSoundToGroup(const std::string &sound_name, const std::string &group_name) override;
|
||||
|
||||
void playSound(sound_handle_t id, const SoundSpec &spec) override;
|
||||
void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
|
||||
const v3f &vel_) override;
|
||||
void stopSound(sound_handle_t sound) override;
|
||||
void fadeSound(sound_handle_t soundid, f32 step, f32 target_gain) override;
|
||||
void updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_) override;
|
||||
};
|
@ -32,7 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "guiMainMenu.h"
|
||||
#include "sound.h"
|
||||
#include "client/sound_openal.h"
|
||||
#include "client/clouds.h"
|
||||
#include "httpfetch.h"
|
||||
#include "log.h"
|
||||
#include "client/fontengine.h"
|
||||
@ -97,28 +96,15 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
|
||||
/******************************************************************************/
|
||||
/** MenuMusicFetcher */
|
||||
/******************************************************************************/
|
||||
void MenuMusicFetcher::fetchSounds(const std::string &name,
|
||||
std::set<std::string> &dst_paths,
|
||||
std::set<std::string> &dst_datas)
|
||||
void MenuMusicFetcher::addThePaths(const std::string &name,
|
||||
std::vector<std::string> &paths)
|
||||
{
|
||||
if(m_fetched.count(name))
|
||||
return;
|
||||
m_fetched.insert(name);
|
||||
std::vector<fs::DirListNode> list;
|
||||
// Reusable local function
|
||||
auto add_paths = [&dst_paths](const std::string name, const std::string base = "") {
|
||||
dst_paths.insert(base + name + ".ogg");
|
||||
for (int i = 0; i < 10; i++)
|
||||
dst_paths.insert(base + name + "." + itos(i) + ".ogg");
|
||||
};
|
||||
// Allow full paths
|
||||
if (name.find(DIR_DELIM_CHAR) != std::string::npos) {
|
||||
add_paths(name);
|
||||
addAllAlternatives(name, paths);
|
||||
} else {
|
||||
std::string share_prefix = porting::path_share + DIR_DELIM;
|
||||
add_paths(name, share_prefix + "sounds" + DIR_DELIM);
|
||||
std::string user_prefix = porting::path_user + DIR_DELIM;
|
||||
add_paths(name, user_prefix + "sounds" + DIR_DELIM);
|
||||
addAllAlternatives(porting::path_share + DIR_DELIM + "sounds" + DIR_DELIM + name, paths);
|
||||
addAllAlternatives(porting::path_user + DIR_DELIM + "sounds" + DIR_DELIM + name, paths);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,8 +137,10 @@ GUIEngine::GUIEngine(JoystickController *joystick,
|
||||
|
||||
// create soundmanager
|
||||
#if USE_SOUND
|
||||
if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get())
|
||||
m_sound_manager.reset(createOpenALSoundManager(g_sound_manager_singleton.get(), &m_soundfetcher));
|
||||
if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
|
||||
m_sound_manager = createOpenALSoundManager(g_sound_manager_singleton.get(),
|
||||
std::make_unique<MenuMusicFetcher>());
|
||||
}
|
||||
#endif
|
||||
if (!m_sound_manager)
|
||||
m_sound_manager = std::make_unique<DummySoundManager>();
|
||||
@ -318,11 +306,12 @@ void GUIEngine::run()
|
||||
/******************************************************************************/
|
||||
GUIEngine::~GUIEngine()
|
||||
{
|
||||
m_sound_manager.reset();
|
||||
|
||||
// deinitialize script first. gc destructors might depend on other stuff
|
||||
infostream << "GUIEngine: Deinitializing scripting" << std::endl;
|
||||
m_script.reset();
|
||||
|
||||
m_sound_manager.reset();
|
||||
|
||||
m_irr_toplefttext->setText(L"");
|
||||
|
||||
//clean up texture pointers
|
||||
@ -608,16 +597,3 @@ void GUIEngine::updateTopLeftTextSize()
|
||||
m_irr_toplefttext = gui::StaticText::add(m_rendering_engine->get_gui_env(),
|
||||
m_toplefttext, rect, false, true, 0, -1);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
s32 GUIEngine::playSound(const SimpleSoundSpec &spec)
|
||||
{
|
||||
s32 handle = m_sound_manager->playSound(spec);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
void GUIEngine::stopSound(s32 handle)
|
||||
{
|
||||
m_sound_manager->stopSound(handle);
|
||||
}
|
||||
|
@ -115,30 +115,20 @@ private:
|
||||
std::vector<video::ITexture*> m_to_delete;
|
||||
};
|
||||
|
||||
/** GUIEngine specific implementation of OnDemandSoundFetcher */
|
||||
class MenuMusicFetcher: public OnDemandSoundFetcher
|
||||
/** GUIEngine specific implementation of SoundFallbackPathProvider */
|
||||
class MenuMusicFetcher final : public SoundFallbackPathProvider
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* get sound file paths according to sound name
|
||||
* @param name sound name
|
||||
* @param dst_paths receives possible paths to sound files
|
||||
* @param dst_datas receives binary sound data (not used here)
|
||||
*/
|
||||
void fetchSounds(const std::string &name,
|
||||
std::set<std::string> &dst_paths,
|
||||
std::set<std::string> &dst_datas);
|
||||
|
||||
private:
|
||||
/** set of fetched sound names */
|
||||
std::set<std::string> m_fetched;
|
||||
protected:
|
||||
void addThePaths(const std::string &name,
|
||||
std::vector<std::string> &paths) override;
|
||||
};
|
||||
|
||||
/** implementation of main menu based uppon formspecs */
|
||||
class GUIEngine {
|
||||
/** grant ModApiMainMenu access to private members */
|
||||
friend class ModApiMainMenu;
|
||||
friend class ModApiSound;
|
||||
friend class ModApiMainMenuSound;
|
||||
friend class MainMenuSoundHandle;
|
||||
|
||||
public:
|
||||
/**
|
||||
@ -197,8 +187,6 @@ private:
|
||||
MainMenuData *m_data = nullptr;
|
||||
/** texture source */
|
||||
std::unique_ptr<ISimpleTextureSource> m_texture_source;
|
||||
/** sound fetcher, used by sound manager*/
|
||||
MenuMusicFetcher m_soundfetcher{};
|
||||
/** sound manager*/
|
||||
std::unique_ptr<ISoundManager> m_sound_manager;
|
||||
|
||||
@ -296,11 +284,4 @@ private:
|
||||
bool m_clouds_enabled = true;
|
||||
/** data used to draw clouds */
|
||||
clouddata m_cloud;
|
||||
|
||||
/** start playing a sound and return handle */
|
||||
s32 playSound(const SimpleSoundSpec &spec);
|
||||
/** stop playing a sound started with playSound() */
|
||||
void stopSound(s32 handle);
|
||||
|
||||
|
||||
};
|
||||
|
@ -4816,7 +4816,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
||||
if ((s.ftype == f_TabHeader) &&
|
||||
(s.fid == event.GUIEvent.Caller->getID())) {
|
||||
if (!s.sound.empty() && m_sound_manager)
|
||||
m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f));
|
||||
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
|
||||
s.send = true;
|
||||
acceptInput();
|
||||
s.send = false;
|
||||
@ -4861,7 +4861,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
||||
|
||||
if (s.ftype == f_Button || s.ftype == f_CheckBox) {
|
||||
if (!s.sound.empty() && m_sound_manager)
|
||||
m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f));
|
||||
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
|
||||
|
||||
s.send = true;
|
||||
if (s.is_exit) {
|
||||
@ -4886,7 +4886,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
||||
}
|
||||
}
|
||||
if (!s.sound.empty() && m_sound_manager)
|
||||
m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f));
|
||||
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
|
||||
s.send = true;
|
||||
acceptInput(quit_mode_no);
|
||||
|
||||
@ -4904,7 +4904,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
||||
s.fdefault.clear();
|
||||
} else if (s.ftype == f_Unknown || s.ftype == f_HyperText) {
|
||||
if (!s.sound.empty() && m_sound_manager)
|
||||
m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f));
|
||||
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
|
||||
s.send = true;
|
||||
acceptInput();
|
||||
s.send = false;
|
||||
|
@ -117,10 +117,10 @@ void ItemDefinition::reset()
|
||||
delete tool_capabilities;
|
||||
tool_capabilities = NULL;
|
||||
groups.clear();
|
||||
sound_place = SimpleSoundSpec();
|
||||
sound_place_failed = SimpleSoundSpec();
|
||||
sound_use = SimpleSoundSpec();
|
||||
sound_use_air = SimpleSoundSpec();
|
||||
sound_place = SoundSpec();
|
||||
sound_place_failed = SoundSpec();
|
||||
sound_use = SoundSpec();
|
||||
sound_use_air = SoundSpec();
|
||||
range = -1;
|
||||
node_placement_prediction.clear();
|
||||
place_param2 = 0;
|
||||
@ -158,8 +158,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
|
||||
os << serializeString16(node_placement_prediction);
|
||||
|
||||
// Version from ContentFeatures::serialize to keep in sync
|
||||
sound_place.serialize(os, protocol_version);
|
||||
sound_place_failed.serialize(os, protocol_version);
|
||||
sound_place.serializeSimple(os, protocol_version);
|
||||
sound_place_failed.serializeSimple(os, protocol_version);
|
||||
|
||||
writeF32(os, range);
|
||||
os << serializeString16(palette_image);
|
||||
@ -171,8 +171,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
|
||||
|
||||
os << place_param2;
|
||||
|
||||
sound_use.serialize(os, protocol_version);
|
||||
sound_use_air.serialize(os, protocol_version);
|
||||
sound_use.serializeSimple(os, protocol_version);
|
||||
sound_use_air.serializeSimple(os, protocol_version);
|
||||
}
|
||||
|
||||
void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
|
||||
@ -212,8 +212,8 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
|
||||
|
||||
node_placement_prediction = deSerializeString16(is);
|
||||
|
||||
sound_place.deSerialize(is, protocol_version);
|
||||
sound_place_failed.deSerialize(is, protocol_version);
|
||||
sound_place.deSerializeSimple(is, protocol_version);
|
||||
sound_place_failed.deSerializeSimple(is, protocol_version);
|
||||
|
||||
range = readF32(is);
|
||||
palette_image = deSerializeString16(is);
|
||||
@ -228,8 +228,8 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
|
||||
|
||||
place_param2 = readU8(is); // 0 if missing
|
||||
|
||||
sound_use.deSerialize(is, protocol_version);
|
||||
sound_use_air.deSerialize(is, protocol_version);
|
||||
sound_use.deSerializeSimple(is, protocol_version);
|
||||
sound_use_air.deSerializeSimple(is, protocol_version);
|
||||
} catch(SerializationError &e) {};
|
||||
}
|
||||
|
||||
|
@ -78,9 +78,9 @@ struct ItemDefinition
|
||||
// May be NULL. If non-NULL, deleted by destructor
|
||||
ToolCapabilities *tool_capabilities;
|
||||
ItemGroupList groups;
|
||||
SimpleSoundSpec sound_place;
|
||||
SimpleSoundSpec sound_place_failed;
|
||||
SimpleSoundSpec sound_use, sound_use_air;
|
||||
SoundSpec sound_place;
|
||||
SoundSpec sound_place_failed;
|
||||
SoundSpec sound_use, sound_use_air;
|
||||
f32 range;
|
||||
|
||||
// Client shall immediately place this node when player places the item.
|
||||
|
@ -805,57 +805,74 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt)
|
||||
void Client::handleCommand_PlaySound(NetworkPacket* pkt)
|
||||
{
|
||||
/*
|
||||
[0] u32 server_id
|
||||
[0] s32 server_id
|
||||
[4] u16 name length
|
||||
[6] char name[len]
|
||||
[ 6 + len] f32 gain
|
||||
[10 + len] u8 type
|
||||
[11 + len] (f32 * 3) pos
|
||||
[10 + len] u8 type (SoundLocation)
|
||||
[11 + len] v3f pos (in BS-space)
|
||||
[23 + len] u16 object_id
|
||||
[25 + len] bool loop
|
||||
[26 + len] f32 fade
|
||||
[30 + len] f32 pitch
|
||||
[34 + len] bool ephemeral
|
||||
[35 + len] f32 start_time (in seconds)
|
||||
*/
|
||||
|
||||
s32 server_id;
|
||||
|
||||
SimpleSoundSpec spec;
|
||||
SoundLocation type; // 0=local, 1=positional, 2=object
|
||||
SoundSpec spec;
|
||||
SoundLocation type;
|
||||
v3f pos;
|
||||
u16 object_id;
|
||||
bool ephemeral = false;
|
||||
|
||||
*pkt >> server_id >> spec.name >> spec.gain >> (u8 &)type >> pos >> object_id >> spec.loop;
|
||||
pos *= 1.0f/BS;
|
||||
|
||||
try {
|
||||
*pkt >> spec.fade;
|
||||
*pkt >> spec.pitch;
|
||||
*pkt >> ephemeral;
|
||||
*pkt >> spec.start_time;
|
||||
} catch (PacketError &e) {};
|
||||
|
||||
// Generate a new id
|
||||
sound_handle_t client_id = (ephemeral && object_id == 0) ? 0 : m_sound->allocateId(2);
|
||||
|
||||
// Start playing
|
||||
int client_id = -1;
|
||||
switch(type) {
|
||||
case SoundLocation::Local:
|
||||
client_id = m_sound->playSound(spec);
|
||||
m_sound->playSound(client_id, spec);
|
||||
break;
|
||||
case SoundLocation::Position:
|
||||
client_id = m_sound->playSoundAt(spec, pos);
|
||||
m_sound->playSoundAt(client_id, spec, pos, v3f(0.0f));
|
||||
break;
|
||||
case SoundLocation::Object:
|
||||
{
|
||||
case SoundLocation::Object: {
|
||||
ClientActiveObject *cao = m_env.getActiveObject(object_id);
|
||||
if (cao)
|
||||
pos = cao->getPosition();
|
||||
client_id = m_sound->playSoundAt(spec, pos);
|
||||
v3f vel(0.0f);
|
||||
if (cao) {
|
||||
pos = cao->getPosition() * (1.0f/BS);
|
||||
vel = cao->getVelocity() * (1.0f/BS);
|
||||
}
|
||||
m_sound->playSoundAt(client_id, spec, pos, vel);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Unknown SoundLocation, instantly remove sound
|
||||
if (client_id != 0)
|
||||
m_sound->freeId(client_id, 2);
|
||||
if (!ephemeral)
|
||||
sendRemovedSounds({server_id});
|
||||
return;
|
||||
}
|
||||
|
||||
if (client_id != -1) {
|
||||
// for ephemeral sounds, server_id is not meaningful
|
||||
if (!ephemeral) {
|
||||
if (client_id != 0) {
|
||||
// Note: m_sounds_client_to_server takes 1 ownership
|
||||
// For ephemeral sounds, server_id is not meaningful
|
||||
if (ephemeral) {
|
||||
m_sounds_client_to_server[client_id] = -1;
|
||||
} else {
|
||||
m_sounds_server_to_client[server_id] = client_id;
|
||||
m_sounds_client_to_server[client_id] = server_id;
|
||||
}
|
||||
|
@ -215,9 +215,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
new fields for TOCLIENT_SET_LIGHTING and TOCLIENT_SET_SKY
|
||||
Send forgotten TweenedParameter properties
|
||||
[scheduled bump for 5.7.0]
|
||||
PROTOCOL VERSION 43:
|
||||
"start_time" added to TOCLIENT_PLAY_SOUND
|
||||
[scheduled bump for 5.8.0]
|
||||
*/
|
||||
|
||||
#define LATEST_PROTOCOL_VERSION 42
|
||||
#define LATEST_PROTOCOL_VERSION 43
|
||||
#define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION)
|
||||
|
||||
// Server's supported network protocol range
|
||||
@ -454,15 +457,18 @@ enum ToClientCommand
|
||||
|
||||
TOCLIENT_PLAY_SOUND = 0x3f,
|
||||
/*
|
||||
s32 sound_id
|
||||
s32 server_id
|
||||
u16 len
|
||||
u8[len] sound name
|
||||
s32 gain*1000
|
||||
u8 type (0=local, 1=positional, 2=object)
|
||||
s32[3] pos_nodes*10000
|
||||
f32 gain
|
||||
u8 type (SoundLocation: 0=local, 1=positional, 2=object)
|
||||
v3f pos_nodes (in BS-space)
|
||||
u16 object_id
|
||||
u8 loop (bool)
|
||||
f32 fade
|
||||
f32 pitch
|
||||
u8 ephemeral (bool)
|
||||
f32 start_time (in seconds)
|
||||
*/
|
||||
|
||||
TOCLIENT_STOP_SOUND = 0x40,
|
||||
|
@ -403,9 +403,9 @@ void ContentFeatures::reset()
|
||||
waving = 0;
|
||||
legacy_facedir_simple = false;
|
||||
legacy_wallmounted = false;
|
||||
sound_footstep = SimpleSoundSpec();
|
||||
sound_dig = SimpleSoundSpec("__group");
|
||||
sound_dug = SimpleSoundSpec();
|
||||
sound_footstep = SoundSpec();
|
||||
sound_dig = SoundSpec("__group");
|
||||
sound_dug = SoundSpec();
|
||||
connects_to.clear();
|
||||
connects_to_ids.clear();
|
||||
connect_sides = 0;
|
||||
@ -529,9 +529,9 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
|
||||
collision_box.serialize(os, protocol_version);
|
||||
|
||||
// sound
|
||||
sound_footstep.serialize(os, protocol_version);
|
||||
sound_dig.serialize(os, protocol_version);
|
||||
sound_dug.serialize(os, protocol_version);
|
||||
sound_footstep.serializeSimple(os, protocol_version);
|
||||
sound_dig.serializeSimple(os, protocol_version);
|
||||
sound_dug.serializeSimple(os, protocol_version);
|
||||
|
||||
// legacy
|
||||
writeU8(os, legacy_facedir_simple);
|
||||
@ -626,9 +626,9 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
|
||||
collision_box.deSerialize(is);
|
||||
|
||||
// sounds
|
||||
sound_footstep.deSerialize(is, protocol_version);
|
||||
sound_dig.deSerialize(is, protocol_version);
|
||||
sound_dug.deSerialize(is, protocol_version);
|
||||
sound_footstep.deSerializeSimple(is, protocol_version);
|
||||
sound_dig.deSerializeSimple(is, protocol_version);
|
||||
sound_dug.deSerializeSimple(is, protocol_version);
|
||||
|
||||
// read legacy properties
|
||||
legacy_facedir_simple = readU8(is);
|
||||
|
@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
class Client;
|
||||
#endif
|
||||
#include "itemgroup.h"
|
||||
#include "sound.h" // SimpleSoundSpec
|
||||
#include "sound.h" // SoundSpec
|
||||
#include "constants.h" // BS
|
||||
#include "texture_override.h" // TextureOverride
|
||||
#include "tileanimation.h"
|
||||
@ -434,9 +434,9 @@ struct ContentFeatures
|
||||
|
||||
// --- SOUND PROPERTIES ---
|
||||
|
||||
SimpleSoundSpec sound_footstep;
|
||||
SimpleSoundSpec sound_dig;
|
||||
SimpleSoundSpec sound_dug;
|
||||
SoundSpec sound_footstep;
|
||||
SoundSpec sound_dig;
|
||||
SoundSpec sound_dug;
|
||||
|
||||
// --- LEGACY ---
|
||||
|
||||
|
@ -146,11 +146,13 @@ public:
|
||||
std::vector<CollisionInfo> *collision_info)
|
||||
{}
|
||||
|
||||
// in BS-space
|
||||
v3f getSpeed() const
|
||||
{
|
||||
return m_speed;
|
||||
}
|
||||
|
||||
// in BS-space
|
||||
void setSpeed(v3f speed)
|
||||
{
|
||||
m_speed = speed;
|
||||
@ -223,7 +225,7 @@ public:
|
||||
|
||||
protected:
|
||||
char m_name[PLAYERNAME_SIZE];
|
||||
v3f m_speed;
|
||||
v3f m_speed; // velocity; in BS-space
|
||||
u16 m_wield_index = 0;
|
||||
PlayerFovSpec m_fov_override_spec = { 0.0f, false, 0.0f };
|
||||
|
||||
|
@ -104,10 +104,10 @@ void read_item_definition(lua_State* L, int index,
|
||||
if (!lua_isnil(L, -1)) {
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
lua_getfield(L, -1, "place");
|
||||
read_soundspec(L, -1, def.sound_place);
|
||||
read_simplesoundspec(L, -1, def.sound_place);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, -1, "place_failed");
|
||||
read_soundspec(L, -1, def.sound_place_failed);
|
||||
read_simplesoundspec(L, -1, def.sound_place_failed);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
@ -117,10 +117,10 @@ void read_item_definition(lua_State* L, int index,
|
||||
if (!lua_isnil(L, -1)) {
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
lua_getfield(L, -1, "punch_use");
|
||||
read_soundspec(L, -1, def.sound_use);
|
||||
read_simplesoundspec(L, -1, def.sound_use);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, -1, "punch_use_air");
|
||||
read_soundspec(L, -1, def.sound_use_air);
|
||||
read_simplesoundspec(L, -1, def.sound_use_air);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
@ -187,9 +187,9 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
|
||||
}
|
||||
push_groups(L, i.groups);
|
||||
lua_setfield(L, -2, "groups");
|
||||
push_soundspec(L, i.sound_place);
|
||||
push_simplesoundspec(L, i.sound_place);
|
||||
lua_setfield(L, -2, "sound_place");
|
||||
push_soundspec(L, i.sound_place_failed);
|
||||
push_simplesoundspec(L, i.sound_place_failed);
|
||||
lua_setfield(L, -2, "sound_place_failed");
|
||||
lua_pushstring(L, i.node_placement_prediction.c_str());
|
||||
lua_setfield(L, -2, "node_placement_prediction");
|
||||
@ -821,13 +821,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
||||
lua_getfield(L, index, "sounds");
|
||||
if(lua_istable(L, -1)){
|
||||
lua_getfield(L, -1, "footstep");
|
||||
read_soundspec(L, -1, f.sound_footstep);
|
||||
read_simplesoundspec(L, -1, f.sound_footstep);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, -1, "dig");
|
||||
read_soundspec(L, -1, f.sound_dig);
|
||||
read_simplesoundspec(L, -1, f.sound_dig);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, -1, "dug");
|
||||
read_soundspec(L, -1, f.sound_dug);
|
||||
read_simplesoundspec(L, -1, f.sound_dug);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
@ -965,11 +965,11 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
|
||||
push_nodebox(L, c.collision_box);
|
||||
lua_setfield(L, -2, "collision_box");
|
||||
lua_newtable(L);
|
||||
push_soundspec(L, c.sound_footstep);
|
||||
push_simplesoundspec(L, c.sound_footstep);
|
||||
lua_setfield(L, -2, "sound_footstep");
|
||||
push_soundspec(L, c.sound_dig);
|
||||
push_simplesoundspec(L, c.sound_dig);
|
||||
lua_setfield(L, -2, "sound_dig");
|
||||
push_soundspec(L, c.sound_dug);
|
||||
push_simplesoundspec(L, c.sound_dug);
|
||||
lua_setfield(L, -2, "sound_dug");
|
||||
lua_setfield(L, -2, "sounds");
|
||||
lua_pushboolean(L, c.legacy_facedir_simple);
|
||||
@ -1071,6 +1071,7 @@ void read_server_sound_params(lua_State *L, int index,
|
||||
// Functional overlap: this may modify SimpleSoundSpec contents
|
||||
getfloatfield(L, index, "fade", params.spec.fade);
|
||||
getfloatfield(L, index, "pitch", params.spec.pitch);
|
||||
getfloatfield(L, index, "start_time", params.spec.start_time);
|
||||
getboolfield(L, index, "loop", params.spec.loop);
|
||||
|
||||
getfloatfield(L, index, "gain", params.gain);
|
||||
@ -1101,7 +1102,7 @@ void read_server_sound_params(lua_State *L, int index,
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
void read_soundspec(lua_State *L, int index, SimpleSoundSpec &spec)
|
||||
void read_simplesoundspec(lua_State *L, int index, SoundSpec &spec)
|
||||
{
|
||||
if(index < 0)
|
||||
index = lua_gettop(L) + 1 + index;
|
||||
@ -1118,7 +1119,7 @@ void read_soundspec(lua_State *L, int index, SimpleSoundSpec &spec)
|
||||
}
|
||||
}
|
||||
|
||||
void push_soundspec(lua_State *L, const SimpleSoundSpec &spec)
|
||||
void push_simplesoundspec(lua_State *L, const SoundSpec &spec)
|
||||
{
|
||||
lua_createtable(L, 0, 3);
|
||||
lua_pushstring(L, spec.name.c_str());
|
||||
|
@ -53,7 +53,7 @@ struct ItemStack;
|
||||
struct ItemDefinition;
|
||||
struct ToolCapabilities;
|
||||
struct ObjectProperties;
|
||||
struct SimpleSoundSpec;
|
||||
struct SoundSpec;
|
||||
struct ServerPlayingSound;
|
||||
class Inventory;
|
||||
class InventoryList;
|
||||
@ -87,8 +87,8 @@ void push_palette (lua_State *L,
|
||||
TileDef read_tiledef (lua_State *L, int index,
|
||||
u8 drawtype, bool special);
|
||||
|
||||
void read_soundspec (lua_State *L, int index,
|
||||
SimpleSoundSpec &spec);
|
||||
void read_simplesoundspec (lua_State *L, int index,
|
||||
SoundSpec &spec);
|
||||
NodeBox read_nodebox (lua_State *L, int index);
|
||||
|
||||
void read_server_sound_params (lua_State *L, int index,
|
||||
@ -167,8 +167,8 @@ std::vector<ItemStack> read_items (lua_State *L,
|
||||
int index,
|
||||
IGameDef* gdef);
|
||||
|
||||
void push_soundspec (lua_State *L,
|
||||
const SimpleSoundSpec &spec);
|
||||
void push_simplesoundspec (lua_State *L,
|
||||
const SoundSpec &spec);
|
||||
|
||||
bool string_to_enum (const EnumString *spec,
|
||||
int &result,
|
||||
|
@ -28,10 +28,11 @@ set(common_SCRIPT_LUA_API_SRCS
|
||||
set(client_SCRIPT_LUA_API_SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_camera.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_client.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_client_sound.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_localplayer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu_sound.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_minimap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_particles_local.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_sound.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
|
||||
PARENT_SCOPE)
|
||||
|
@ -260,63 +260,6 @@ int ModApiClient::l_get_meta(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sound_play(spec, parameters)
|
||||
int ModApiClient::l_sound_play(lua_State *L)
|
||||
{
|
||||
ISoundManager *sound = getClient(L)->getSoundManager();
|
||||
|
||||
SimpleSoundSpec spec;
|
||||
read_soundspec(L, 1, spec);
|
||||
|
||||
SoundLocation type = SoundLocation::Local;
|
||||
float gain = 1.0f;
|
||||
v3f position;
|
||||
|
||||
if (lua_istable(L, 2)) {
|
||||
getfloatfield(L, 2, "gain", gain);
|
||||
getfloatfield(L, 2, "pitch", spec.pitch);
|
||||
getboolfield(L, 2, "loop", spec.loop);
|
||||
|
||||
lua_getfield(L, 2, "pos");
|
||||
if (!lua_isnil(L, -1)) {
|
||||
position = read_v3f(L, -1) * BS;
|
||||
type = SoundLocation::Position;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
spec.gain *= gain;
|
||||
|
||||
s32 handle;
|
||||
if (type == SoundLocation::Local)
|
||||
handle = sound->playSound(spec);
|
||||
else
|
||||
handle = sound->playSoundAt(spec, position);
|
||||
|
||||
lua_pushinteger(L, handle);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sound_stop(handle)
|
||||
int ModApiClient::l_sound_stop(lua_State *L)
|
||||
{
|
||||
s32 handle = luaL_checkinteger(L, 1);
|
||||
|
||||
getClient(L)->getSoundManager()->stopSound(handle);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// sound_fade(handle, step, gain)
|
||||
int ModApiClient::l_sound_fade(lua_State *L)
|
||||
{
|
||||
s32 handle = luaL_checkinteger(L, 1);
|
||||
float step = readParam<float>(L, 2);
|
||||
float gain = readParam<float>(L, 3);
|
||||
getClient(L)->getSoundManager()->fadeSound(handle, step, gain);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get_server_info()
|
||||
int ModApiClient::l_get_server_info(lua_State *L)
|
||||
{
|
||||
@ -433,9 +376,6 @@ void ModApiClient::Initialize(lua_State *L, int top)
|
||||
API_FCT(get_node_or_nil);
|
||||
API_FCT(disconnect);
|
||||
API_FCT(get_meta);
|
||||
API_FCT(sound_play);
|
||||
API_FCT(sound_stop);
|
||||
API_FCT(sound_fade);
|
||||
API_FCT(get_server_info);
|
||||
API_FCT(get_item_def);
|
||||
API_FCT(get_node_def);
|
||||
|
@ -78,15 +78,6 @@ private:
|
||||
// get_meta(pos)
|
||||
static int l_get_meta(lua_State *L);
|
||||
|
||||
// sound_play(spec, parameters)
|
||||
static int l_sound_play(lua_State *L);
|
||||
|
||||
// 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_server_info()
|
||||
static int l_get_server_info(lua_State *L);
|
||||
|
||||
|
150
src/script/lua_api/l_client_sound.cpp
Normal file
150
src/script/lua_api/l_client_sound.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2023 DS
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "l_client_sound.h"
|
||||
#include "l_internal.h"
|
||||
#include "common/c_content.h"
|
||||
#include "common/c_converter.h"
|
||||
#include "client/client.h"
|
||||
#include "client/sound.h"
|
||||
|
||||
/* ModApiClientSound */
|
||||
|
||||
// sound_play(spec, parameters)
|
||||
int ModApiClientSound::l_sound_play(lua_State *L)
|
||||
{
|
||||
ISoundManager *sound_manager = getClient(L)->getSoundManager();
|
||||
|
||||
SoundSpec spec;
|
||||
read_simplesoundspec(L, 1, spec);
|
||||
|
||||
SoundLocation type = SoundLocation::Local;
|
||||
float gain = 1.0f;
|
||||
v3f position;
|
||||
|
||||
if (lua_istable(L, 2)) {
|
||||
getfloatfield(L, 2, "gain", gain);
|
||||
getfloatfield(L, 2, "pitch", spec.pitch);
|
||||
getboolfield(L, 2, "loop", spec.loop);
|
||||
|
||||
lua_getfield(L, 2, "pos");
|
||||
if (!lua_isnil(L, -1)) {
|
||||
position = read_v3f(L, -1);
|
||||
type = SoundLocation::Position;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
spec.gain *= gain;
|
||||
|
||||
sound_handle_t handle = sound_manager->allocateId(2);
|
||||
|
||||
if (type == SoundLocation::Local)
|
||||
sound_manager->playSound(handle, spec);
|
||||
else
|
||||
sound_manager->playSoundAt(handle, spec, position, v3f(0.0f));
|
||||
|
||||
ClientSoundHandle::create(L, handle);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ModApiClientSound::Initialize(lua_State *L, int top)
|
||||
{
|
||||
API_FCT(sound_play);
|
||||
}
|
||||
|
||||
/* ClientSoundHandle */
|
||||
|
||||
ClientSoundHandle *ClientSoundHandle::checkobject(lua_State *L, int narg)
|
||||
{
|
||||
luaL_checktype(L, narg, LUA_TUSERDATA);
|
||||
void *ud = luaL_checkudata(L, narg, className);
|
||||
if (!ud)
|
||||
luaL_typerror(L, narg, className);
|
||||
return *(ClientSoundHandle**)ud; // unbox pointer
|
||||
}
|
||||
|
||||
int ClientSoundHandle::gc_object(lua_State *L)
|
||||
{
|
||||
ClientSoundHandle *o = *(ClientSoundHandle **)(lua_touserdata(L, 1));
|
||||
if (getClient(L) && getClient(L)->getSoundManager())
|
||||
getClient(L)->getSoundManager()->freeId(o->m_handle);
|
||||
delete o;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// :stop()
|
||||
int ClientSoundHandle::l_stop(lua_State *L)
|
||||
{
|
||||
ClientSoundHandle *o = checkobject(L, 1);
|
||||
getClient(L)->getSoundManager()->stopSound(o->m_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// :fade(step, gain)
|
||||
int ClientSoundHandle::l_fade(lua_State *L)
|
||||
{
|
||||
ClientSoundHandle *o = checkobject(L, 1);
|
||||
float step = readParam<float>(L, 2);
|
||||
float gain = readParam<float>(L, 3);
|
||||
getClient(L)->getSoundManager()->fadeSound(o->m_handle, step, gain);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ClientSoundHandle::create(lua_State *L, sound_handle_t handle)
|
||||
{
|
||||
ClientSoundHandle *o = new ClientSoundHandle(handle);
|
||||
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
|
||||
luaL_getmetatable(L, className);
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
void ClientSoundHandle::Register(lua_State *L)
|
||||
{
|
||||
lua_newtable(L);
|
||||
int methodtable = lua_gettop(L);
|
||||
luaL_newmetatable(L, className);
|
||||
int metatable = lua_gettop(L);
|
||||
|
||||
lua_pushliteral(L, "__metatable");
|
||||
lua_pushvalue(L, methodtable);
|
||||
lua_settable(L, metatable); // hide metatable from Lua getmetatable()
|
||||
|
||||
lua_pushliteral(L, "__index");
|
||||
lua_pushvalue(L, methodtable);
|
||||
lua_settable(L, metatable);
|
||||
|
||||
lua_pushliteral(L, "__gc");
|
||||
lua_pushcfunction(L, gc_object);
|
||||
lua_settable(L, metatable);
|
||||
|
||||
lua_pop(L, 1); // drop metatable
|
||||
|
||||
luaL_register(L, nullptr, methods); // fill methodtable
|
||||
lua_pop(L, 1); // drop methodtable
|
||||
}
|
||||
|
||||
const char ClientSoundHandle::className[] = "ClientSoundHandle";
|
||||
const luaL_Reg ClientSoundHandle::methods[] = {
|
||||
luamethod(ClientSoundHandle, stop),
|
||||
luamethod(ClientSoundHandle, fade),
|
||||
{0,0}
|
||||
};
|
66
src/script/lua_api/l_client_sound.h
Normal file
66
src/script/lua_api/l_client_sound.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2023 DS
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lua_api/l_base.h"
|
||||
#include "util/basic_macros.h"
|
||||
|
||||
using sound_handle_t = int;
|
||||
|
||||
class ModApiClientSound : public ModApiBase
|
||||
{
|
||||
private:
|
||||
// sound_play(spec, parameters)
|
||||
static int l_sound_play(lua_State *L);
|
||||
|
||||
public:
|
||||
static void Initialize(lua_State *L, int top);
|
||||
};
|
||||
|
||||
class ClientSoundHandle final : public ModApiBase
|
||||
{
|
||||
private:
|
||||
sound_handle_t m_handle;
|
||||
|
||||
static const char className[];
|
||||
static const luaL_Reg methods[];
|
||||
|
||||
ClientSoundHandle(sound_handle_t handle) : m_handle(handle) {}
|
||||
|
||||
DISABLE_CLASS_COPY(ClientSoundHandle)
|
||||
|
||||
static ClientSoundHandle *checkobject(lua_State *L, int narg);
|
||||
|
||||
static int gc_object(lua_State *L);
|
||||
|
||||
// :stop()
|
||||
static int l_stop(lua_State *L);
|
||||
|
||||
// :fade(step, gain)
|
||||
static int l_fade(lua_State *L);
|
||||
|
||||
public:
|
||||
~ClientSoundHandle() = default;
|
||||
|
||||
static void create(lua_State *L, sound_handle_t handle);
|
||||
static void Register(lua_State *L);
|
||||
};
|
116
src/script/lua_api/l_mainmenu_sound.cpp
Normal file
116
src/script/lua_api/l_mainmenu_sound.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2023 DS
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "l_mainmenu_sound.h"
|
||||
#include "l_internal.h"
|
||||
#include "common/c_content.h"
|
||||
#include "gui/guiEngine.h"
|
||||
|
||||
/* ModApiMainMenuSound */
|
||||
|
||||
// sound_play(spec, loop)
|
||||
int ModApiMainMenuSound::l_sound_play(lua_State *L)
|
||||
{
|
||||
SoundSpec spec;
|
||||
read_simplesoundspec(L, 1, spec);
|
||||
spec.loop = readParam<bool>(L, 2);
|
||||
|
||||
ISoundManager &sound_manager = *getGuiEngine(L)->m_sound_manager;
|
||||
|
||||
sound_handle_t handle = sound_manager.allocateId(2);
|
||||
sound_manager.playSound(handle, spec);
|
||||
|
||||
MainMenuSoundHandle::create(L, handle);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ModApiMainMenuSound::Initialize(lua_State *L, int top)
|
||||
{
|
||||
API_FCT(sound_play);
|
||||
}
|
||||
|
||||
/* MainMenuSoundHandle */
|
||||
|
||||
MainMenuSoundHandle *MainMenuSoundHandle::checkobject(lua_State *L, int narg)
|
||||
{
|
||||
luaL_checktype(L, narg, LUA_TUSERDATA);
|
||||
void *ud = luaL_checkudata(L, narg, className);
|
||||
if (!ud)
|
||||
luaL_typerror(L, narg, className);
|
||||
return *(MainMenuSoundHandle**)ud; // unbox pointer
|
||||
}
|
||||
|
||||
int MainMenuSoundHandle::gc_object(lua_State *L)
|
||||
{
|
||||
MainMenuSoundHandle *o = *(MainMenuSoundHandle **)(lua_touserdata(L, 1));
|
||||
if (getGuiEngine(L) && getGuiEngine(L)->m_sound_manager)
|
||||
getGuiEngine(L)->m_sound_manager->freeId(o->m_handle);
|
||||
delete o;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// :stop()
|
||||
int MainMenuSoundHandle::l_stop(lua_State *L)
|
||||
{
|
||||
MainMenuSoundHandle *o = checkobject(L, 1);
|
||||
getGuiEngine(L)->m_sound_manager->stopSound(o->m_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MainMenuSoundHandle::create(lua_State *L, sound_handle_t handle)
|
||||
{
|
||||
MainMenuSoundHandle *o = new MainMenuSoundHandle(handle);
|
||||
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
|
||||
luaL_getmetatable(L, className);
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
void MainMenuSoundHandle::Register(lua_State *L)
|
||||
{
|
||||
lua_newtable(L);
|
||||
int methodtable = lua_gettop(L);
|
||||
luaL_newmetatable(L, className);
|
||||
int metatable = lua_gettop(L);
|
||||
|
||||
lua_pushliteral(L, "__metatable");
|
||||
lua_pushvalue(L, methodtable);
|
||||
lua_settable(L, metatable); // hide metatable from Lua getmetatable()
|
||||
|
||||
lua_pushliteral(L, "__index");
|
||||
lua_pushvalue(L, methodtable);
|
||||
lua_settable(L, metatable);
|
||||
|
||||
lua_pushliteral(L, "__gc");
|
||||
lua_pushcfunction(L, gc_object);
|
||||
lua_settable(L, metatable);
|
||||
|
||||
lua_pop(L, 1); // drop metatable
|
||||
|
||||
luaL_register(L, nullptr, methods); // fill methodtable
|
||||
lua_pop(L, 1); // drop methodtable
|
||||
}
|
||||
|
||||
const char MainMenuSoundHandle::className[] = "MainMenuSoundHandle";
|
||||
const luaL_Reg MainMenuSoundHandle::methods[] = {
|
||||
luamethod(MainMenuSoundHandle, stop),
|
||||
{0,0}
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2023 DS
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||
|
||||
@ -21,13 +22,42 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#pragma once
|
||||
|
||||
#include "lua_api/l_base.h"
|
||||
#include "util/basic_macros.h"
|
||||
|
||||
class ModApiSound : public ModApiBase
|
||||
using sound_handle_t = int;
|
||||
|
||||
class ModApiMainMenuSound : public ModApiBase
|
||||
{
|
||||
private:
|
||||
// sound_play(spec, loop)
|
||||
static int l_sound_play(lua_State *L);
|
||||
static int l_sound_stop(lua_State *L);
|
||||
|
||||
public:
|
||||
static void Initialize(lua_State *L, int top);
|
||||
};
|
||||
|
||||
class MainMenuSoundHandle final : public ModApiBase
|
||||
{
|
||||
private:
|
||||
sound_handle_t m_handle;
|
||||
|
||||
static const char className[];
|
||||
static const luaL_Reg methods[];
|
||||
|
||||
MainMenuSoundHandle(sound_handle_t handle) : m_handle(handle) {}
|
||||
|
||||
DISABLE_CLASS_COPY(MainMenuSoundHandle)
|
||||
|
||||
static MainMenuSoundHandle *checkobject(lua_State *L, int narg);
|
||||
|
||||
static int gc_object(lua_State *L);
|
||||
|
||||
// :stop()
|
||||
static int l_stop(lua_State *L);
|
||||
|
||||
public:
|
||||
~MainMenuSoundHandle() = default;
|
||||
|
||||
static void create(lua_State *L, sound_handle_t handle);
|
||||
static void Register(lua_State *L);
|
||||
};
|
@ -503,7 +503,7 @@ int ModApiServer::l_sound_play(lua_State *L)
|
||||
{
|
||||
NO_MAP_LOCK_REQUIRED;
|
||||
ServerPlayingSound params;
|
||||
read_soundspec(L, 1, params.spec);
|
||||
read_simplesoundspec(L, 1, params.spec);
|
||||
read_server_sound_params(L, 2, params);
|
||||
bool ephemeral = lua_gettop(L) > 2 && readParam<bool>(L, 3);
|
||||
if (ephemeral) {
|
||||
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "l_sound.h"
|
||||
#include "l_internal.h"
|
||||
#include "common/c_content.h"
|
||||
#include "gui/guiEngine.h"
|
||||
|
||||
|
||||
int ModApiSound::l_sound_play(lua_State *L)
|
||||
{
|
||||
SimpleSoundSpec spec;
|
||||
read_soundspec(L, 1, spec);
|
||||
spec.loop = readParam<bool>(L, 2);
|
||||
|
||||
s32 handle = getGuiEngine(L)->playSound(spec);
|
||||
|
||||
lua_pushinteger(L, handle);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ModApiSound::l_sound_stop(lua_State *L)
|
||||
{
|
||||
u32 handle = luaL_checkinteger(L, 1);
|
||||
|
||||
getGuiEngine(L)->stopSound(handle);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ModApiSound::Initialize(lua_State *L, int top)
|
||||
{
|
||||
API_FCT(sound_play);
|
||||
API_FCT(sound_stop);
|
||||
}
|
@ -29,13 +29,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "lua_api/l_modchannels.h"
|
||||
#include "lua_api/l_particles_local.h"
|
||||
#include "lua_api/l_storage.h"
|
||||
#include "lua_api/l_sound.h"
|
||||
#include "lua_api/l_util.h"
|
||||
#include "lua_api/l_item.h"
|
||||
#include "lua_api/l_nodemeta.h"
|
||||
#include "lua_api/l_localplayer.h"
|
||||
#include "lua_api/l_camera.h"
|
||||
#include "lua_api/l_settings.h"
|
||||
#include "lua_api/l_client_sound.h"
|
||||
|
||||
ClientScripting::ClientScripting(Client *client):
|
||||
ScriptApiBase(ScriptingType::Client)
|
||||
@ -75,6 +75,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top)
|
||||
LuaCamera::Register(L);
|
||||
ModChannelRef::Register(L);
|
||||
LuaSettings::Register(L);
|
||||
ClientSoundHandle::Register(L);
|
||||
|
||||
ModApiUtil::InitializeClient(L, top);
|
||||
ModApiClient::Initialize(L, top);
|
||||
@ -83,6 +84,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top)
|
||||
ModApiEnvMod::InitializeClient(L, top);
|
||||
ModApiChannels::Initialize(L, top);
|
||||
ModApiParticlesLocal::Initialize(L, top);
|
||||
ModApiClientSound::Initialize(L, top);
|
||||
}
|
||||
|
||||
void ClientScripting::on_client_ready(LocalPlayer *localplayer)
|
||||
|
@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "lua_api/l_base.h"
|
||||
#include "lua_api/l_http.h"
|
||||
#include "lua_api/l_mainmenu.h"
|
||||
#include "lua_api/l_sound.h"
|
||||
#include "lua_api/l_mainmenu_sound.h"
|
||||
#include "lua_api/l_util.h"
|
||||
#include "lua_api/l_settings.h"
|
||||
#include "log.h"
|
||||
@ -66,7 +66,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top)
|
||||
// Initialize mod API modules
|
||||
ModApiMainMenu::Initialize(L, top);
|
||||
ModApiUtil::Initialize(L, top);
|
||||
ModApiSound::Initialize(L, top);
|
||||
ModApiMainMenuSound::Initialize(L, top);
|
||||
ModApiHttp::Initialize(L, top);
|
||||
|
||||
asyncEngine.registerStateInitializer(registerLuaClasses);
|
||||
@ -83,6 +83,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top)
|
||||
void MainMenuScripting::registerLuaClasses(lua_State *L, int top)
|
||||
{
|
||||
LuaSettings::Register(L);
|
||||
MainMenuSoundHandle::Register(L);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -2231,7 +2231,7 @@ s32 Server::playSound(ServerPlayingSound ¶ms, bool ephemeral)
|
||||
pkt << id << params.spec.name << gain
|
||||
<< (u8) params.type << pos << params.object
|
||||
<< params.spec.loop << params.spec.fade << params.spec.pitch
|
||||
<< ephemeral;
|
||||
<< ephemeral << params.spec.start_time;
|
||||
|
||||
bool as_reliable = !ephemeral;
|
||||
|
||||
|
@ -62,7 +62,7 @@ struct RollbackAction;
|
||||
class EmergeManager;
|
||||
class ServerScripting;
|
||||
class ServerEnvironment;
|
||||
struct SimpleSoundSpec;
|
||||
struct SoundSpec;
|
||||
struct CloudParams;
|
||||
struct SkyboxParams;
|
||||
struct SunParams;
|
||||
@ -97,7 +97,7 @@ struct MediaInfo
|
||||
}
|
||||
};
|
||||
|
||||
// Combines the pure sound (SimpleSoundSpec) with positional information
|
||||
// Combines the pure sound (SoundSpec) with positional information
|
||||
struct ServerPlayingSound
|
||||
{
|
||||
SoundLocation type = SoundLocation::Local;
|
||||
@ -111,7 +111,7 @@ struct ServerPlayingSound
|
||||
|
||||
v3f getPos(ServerEnvironment *env, bool *pos_exists) const;
|
||||
|
||||
SimpleSoundSpec spec;
|
||||
SoundSpec spec;
|
||||
|
||||
std::unordered_set<session_t> clients; // peer ids
|
||||
};
|
||||
|
35
src/sound.h
35
src/sound.h
@ -24,20 +24,29 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "util/serialize.h"
|
||||
#include "irrlichttypes_bloated.h"
|
||||
|
||||
// This class describes the basic sound information for playback.
|
||||
// Positional handling is done separately.
|
||||
|
||||
struct SimpleSoundSpec
|
||||
/**
|
||||
* Describes the sound information for playback.
|
||||
* Positional handling is done separately.
|
||||
*
|
||||
* `SimpleSoundSpec`, as used by modding, is a `SoundSpec` with only name, fain,
|
||||
* pitch and fade.
|
||||
*/
|
||||
struct SoundSpec
|
||||
{
|
||||
SimpleSoundSpec(const std::string &name = "", float gain = 1.0f,
|
||||
bool loop = false, float fade = 0.0f, float pitch = 1.0f) :
|
||||
name(name), gain(gain), fade(fade), pitch(pitch), loop(loop)
|
||||
SoundSpec(const std::string &name = "", float gain = 1.0f,
|
||||
bool loop = false, float fade = 0.0f, float pitch = 1.0f,
|
||||
float start_time = 0.0f) :
|
||||
name(name), gain(gain), fade(fade), pitch(pitch), start_time(start_time),
|
||||
loop(loop)
|
||||
{
|
||||
}
|
||||
|
||||
bool exists() const { return !name.empty(); }
|
||||
|
||||
void serialize(std::ostream &os, u16 protocol_version) const
|
||||
/**
|
||||
* Serialize a `SimpleSoundSpec`.
|
||||
*/
|
||||
void serializeSimple(std::ostream &os, u16 protocol_version) const
|
||||
{
|
||||
os << serializeString16(name);
|
||||
writeF32(os, gain);
|
||||
@ -45,7 +54,10 @@ struct SimpleSoundSpec
|
||||
writeF32(os, fade);
|
||||
}
|
||||
|
||||
void deSerialize(std::istream &is, u16 protocol_version)
|
||||
/**
|
||||
* Deserialize a `SimpleSoundSpec`.
|
||||
*/
|
||||
void deSerializeSimple(std::istream &is, u16 protocol_version)
|
||||
{
|
||||
name = deSerializeString16(is);
|
||||
gain = readF32(is);
|
||||
@ -53,11 +65,16 @@ struct SimpleSoundSpec
|
||||
fade = readF32(is);
|
||||
}
|
||||
|
||||
// Name of the sound-group
|
||||
std::string name;
|
||||
float gain = 1.0f;
|
||||
float fade = 0.0f;
|
||||
float pitch = 1.0f;
|
||||
float start_time = 0.0f;
|
||||
bool loop = false;
|
||||
// If true, a local fallback (ie. from the user's sound pack) is used if the
|
||||
// sound-group does not exist.
|
||||
bool use_local_fallback = true;
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user