forked from Mirrorlandia_minetest/minetest
Sound refactor and improvements (#12764)
This commit is contained in:
parent
8e1af25738
commit
edcbfa31c9
@ -83,6 +83,8 @@ SmallJoker:
|
|||||||
DS:
|
DS:
|
||||||
games/devtest/mods/soundstuff/textures/soundstuff_bigfoot.png
|
games/devtest/mods/soundstuff/textures/soundstuff_bigfoot.png
|
||||||
games/devtest/mods/soundstuff/textures/soundstuff_jukebox.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
|
games/devtest/mods/testtools/textures/testtools_branding_iron.png
|
||||||
|
|
||||||
License of Minetest source code
|
License of Minetest source code
|
||||||
|
@ -5,3 +5,14 @@ function core.setting_get_pos(name)
|
|||||||
end
|
end
|
||||||
return core.string_to_pos(value)
|
return core.string_to_pos(value)
|
||||||
end
|
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" ..
|
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
||||||
DIR_DELIM .. "pack" .. DIR_DELIM
|
DIR_DELIM .. "pack" .. DIR_DELIM
|
||||||
|
|
||||||
|
dofile(menupath .. DIR_DELIM .. "misc.lua")
|
||||||
|
|
||||||
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
|
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
|
||||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
|
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
|
||||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.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
|
* `minetest.sound_play(spec, parameters)`: returns a handle
|
||||||
* `spec` is a `SimpleSoundSpec`
|
* `spec` is a `SimpleSoundSpec`
|
||||||
* `parameters` is a sound parameter table
|
* `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`
|
* `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`
|
* `handle` is a handle returned by `minetest.sound_play`
|
||||||
* `step` determines how fast a sound will fade.
|
* `step` determines how fast a sound will fade.
|
||||||
Negative step will lower the sound volume, positive step will increase
|
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
|
For positional playing of sounds, only single-channel (mono) files are
|
||||||
supported. Otherwise OpenAL will play them non-positionally.
|
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:
|
the mod name "`foomod`", a sound could be called:
|
||||||
|
|
||||||
foomod_foosound.ogg
|
foomod_foosound.ogg
|
||||||
|
|
||||||
Sounds are referred to by their name with a dot, a single digit and the
|
Sound group
|
||||||
file extension stripped out. When a sound is played, the actual sound file
|
-----------
|
||||||
is chosen randomly from the matching sounds.
|
|
||||||
|
|
||||||
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:
|
from the available ones of the following files:
|
||||||
|
|
||||||
* `foomod_foosound.ogg`
|
* `foomod_foosound.ogg`
|
||||||
@ -1012,62 +1017,10 @@ from the available ones of the following files:
|
|||||||
* (...)
|
* (...)
|
||||||
* `foomod_foosound.9.ogg`
|
* `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`
|
`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.
|
This is either a string or a table.
|
||||||
|
|
||||||
In string form, you just specify the sound name or
|
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:
|
Table form has the following fields:
|
||||||
|
|
||||||
* `name`: Sound name
|
* `name`:
|
||||||
* `gain`: Volume (`1.0` = 100%)
|
Sound-group name.
|
||||||
* `pitch`: Pitch (`1.0` = 100%)
|
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:
|
Examples:
|
||||||
|
|
||||||
@ -1090,10 +1057,105 @@ Examples:
|
|||||||
* `{name = "default_place_node", gain = 0.5}`: 50% volume
|
* `{name = "default_place_node", gain = 0.5}`: 50% volume
|
||||||
* `{name = "default_place_node", gain = 0.9, pitch = 1.1}`: 90% volume, 110% pitch
|
* `{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_damage`: Played when the local player takes damage (gain = 0.5)
|
||||||
* `player_falling_damage`: Played when the local player takes
|
* `player_falling_damage`: Played when the local player takes
|
||||||
@ -8804,7 +8866,10 @@ Used by `minetest.register_node`.
|
|||||||
|
|
||||||
footstep = <SimpleSoundSpec>,
|
footstep = <SimpleSoundSpec>,
|
||||||
-- If walkable, played when object walks on it. If node is
|
-- 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",
|
dig = <SimpleSoundSpec> or "__group",
|
||||||
-- While digging node.
|
-- While digging node.
|
||||||
|
@ -81,7 +81,7 @@ Filesystem
|
|||||||
* `core.sound_play(spec, looped)` -> handle
|
* `core.sound_play(spec, looped)` -> handle
|
||||||
* `spec` = `SimpleSoundSpec` (see `lua_api.md`)
|
* `spec` = `SimpleSoundSpec` (see `lua_api.md`)
|
||||||
* `looped` = bool
|
* `looped` = bool
|
||||||
* `core.sound_stop(handle)`
|
* `handle:stop()` or `core.sound_stop(handle)`
|
||||||
* `core.get_video_drivers()`
|
* `core.get_video_drivers()`
|
||||||
* get list of video drivers supported by engine (not all modes are guaranteed to work)
|
* 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
|
* 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 .. "sound_event_items.lua")
|
||||||
dofile(path .. "jukebox.lua")
|
dofile(path .. "jukebox.lua")
|
||||||
dofile(path .. "bigfoot.lua")
|
dofile(path .. "bigfoot.lua")
|
||||||
|
dofile(path .. "racecar.lua")
|
||||||
|
@ -14,6 +14,7 @@ local meta_keys = {
|
|||||||
"sparam.gain",
|
"sparam.gain",
|
||||||
"sparam.pitch",
|
"sparam.pitch",
|
||||||
"sparam.fade",
|
"sparam.fade",
|
||||||
|
"sparam.start_time",
|
||||||
"sparam.loop",
|
"sparam.loop",
|
||||||
"sparam.pos",
|
"sparam.pos",
|
||||||
"sparam.object",
|
"sparam.object",
|
||||||
@ -39,6 +40,7 @@ local function get_all_metadata(meta)
|
|||||||
gain = meta:get_string("sparam.gain"),
|
gain = meta:get_string("sparam.gain"),
|
||||||
pitch = meta:get_string("sparam.pitch"),
|
pitch = meta:get_string("sparam.pitch"),
|
||||||
fade = meta:get_string("sparam.fade"),
|
fade = meta:get_string("sparam.fade"),
|
||||||
|
start_time = meta:get_string("sparam.start_time"),
|
||||||
loop = meta:get_string("sparam.loop"),
|
loop = meta:get_string("sparam.loop"),
|
||||||
pos = meta:get_string("sparam.pos"),
|
pos = meta:get_string("sparam.pos"),
|
||||||
object = meta:get_string("sparam.object"),
|
object = meta:get_string("sparam.object"),
|
||||||
@ -86,7 +88,7 @@ local function show_formspec(pos, player)
|
|||||||
|
|
||||||
fs_add([[
|
fs_add([[
|
||||||
formspec_version[6]
|
formspec_version[6]
|
||||||
size[14,11]
|
size[14,12]
|
||||||
]])
|
]])
|
||||||
|
|
||||||
-- SimpleSoundSpec
|
-- SimpleSoundSpec
|
||||||
@ -110,23 +112,25 @@ local function show_formspec(pos, player)
|
|||||||
-- sound parameter table
|
-- sound parameter table
|
||||||
fs_add(string.format([[
|
fs_add(string.format([[
|
||||||
container[5.5,0.5]
|
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]
|
style[*;font=mono,bold]
|
||||||
label[0,0.25;sound parameter table]
|
label[0,0.25;sound parameter table]
|
||||||
style[*;font=mono]
|
style[*;font=mono]
|
||||||
field[0.00,1;1,0.75;sparam.gain;gain;%s]
|
field[0.00,1;1,0.75;sparam.gain;gain;%s]
|
||||||
field[1.25,1;1,0.75;sparam.pitch;pitch;%s]
|
field[1.25,1;1,0.75;sparam.pitch;pitch;%s]
|
||||||
field[2.50,1;1,0.75;sparam.fade;fade;%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,2.25;4,0.75;sparam.start_time;start_time;%s]
|
||||||
field[0,3.50;4,0.75;sparam.pos;pos;%s]
|
field[0,3.50;4,0.75;sparam.loop;loop;%s]
|
||||||
field[0,4.75;4,0.75;sparam.object;object;%s]
|
field[0,4.75;4,0.75;sparam.pos;pos;%s]
|
||||||
field[0,6.00;4,0.75;sparam.to_player;to_player;%s]
|
field[0,6.00;4,0.75;sparam.object;object;%s]
|
||||||
field[0,7.25;4,0.75;sparam.exclude_player;exclude_player;%s]
|
field[0,7.25;4,0.75;sparam.to_player;to_player;%s]
|
||||||
field[0,8.50;4,0.75;sparam.max_hear_distance;max_hear_distance;%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[]
|
container_end[]
|
||||||
field_close_on_enter[sparam.gain;false]
|
field_close_on_enter[sparam.gain;false]
|
||||||
field_close_on_enter[sparam.pitch;false]
|
field_close_on_enter[sparam.pitch;false]
|
||||||
field_close_on_enter[sparam.fade;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.loop;false]
|
||||||
field_close_on_enter[sparam.pos;false]
|
field_close_on_enter[sparam.pos;false]
|
||||||
field_close_on_enter[sparam.object;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.exclude_player;false]
|
||||||
field_close_on_enter[sparam.max_hear_distance;false]
|
field_close_on_enter[sparam.max_hear_distance;false]
|
||||||
tooltip[sparam.object;Get a name with the Branding Iron.]
|
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.gain), F(md.sparam.pitch), F(md.sparam.fade),
|
||||||
F(md.sparam.pos), F(md.sparam.object), F(md.sparam.to_player),
|
F(md.sparam.start_time), F(md.sparam.loop), F(md.sparam.pos),
|
||||||
F(md.sparam.exclude_player), F(md.sparam.max_hear_distance)))
|
F(md.sparam.object), F(md.sparam.to_player), F(md.sparam.exclude_player),
|
||||||
|
F(md.sparam.max_hear_distance)))
|
||||||
|
|
||||||
-- fade
|
-- fade
|
||||||
fs_add(string.format([[
|
fs_add(string.format([[
|
||||||
@ -187,7 +192,7 @@ local function show_formspec(pos, player)
|
|||||||
|
|
||||||
-- save and quit button
|
-- save and quit button
|
||||||
fs_add([[
|
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(),
|
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.gain", "")
|
||||||
meta:set_string("sparam.pitch", "")
|
meta:set_string("sparam.pitch", "")
|
||||||
meta:set_string("sparam.fade", "")
|
meta:set_string("sparam.fade", "")
|
||||||
|
meta:set_string("sparam.start_time", "")
|
||||||
meta:set_string("sparam.loop", "")
|
meta:set_string("sparam.loop", "")
|
||||||
meta:set_string("sparam.pos", pos:to_string())
|
meta:set_string("sparam.pos", pos:to_string())
|
||||||
meta:set_string("sparam.object", "")
|
meta:set_string("sparam.object", "")
|
||||||
@ -267,6 +273,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|||||||
gain = tonumber(md.sparam.gain),
|
gain = tonumber(md.sparam.gain),
|
||||||
pitch = tonumber(md.sparam.pitch),
|
pitch = tonumber(md.sparam.pitch),
|
||||||
fade = tonumber(md.sparam.fade),
|
fade = tonumber(md.sparam.fade),
|
||||||
|
start_time = tonumber(md.sparam.start_time),
|
||||||
loop = minetest.is_yes(md.sparam.loop),
|
loop = minetest.is_yes(md.sparam.loop),
|
||||||
pos = vector.from_string(md.sparam.pos),
|
pos = vector.from_string(md.sparam.pos),
|
||||||
object = testtools.get_branded_object(md.sparam.object),
|
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)",
|
"[soundstuff:jukebox] Playing sound: minetest.sound_play(%s, %s, %s)",
|
||||||
string.format("{name=\"%s\", gain=%s, pitch=%s, fade=%s}",
|
string.format("{name=\"%s\", gain=%s, pitch=%s, fade=%s}",
|
||||||
sss.name, sss.gain, sss.pitch, sss.fade),
|
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}",
|
.."object=%s, to_player=\"%s\", exclude_player=\"%s\", max_hear_distance=%s}",
|
||||||
sparam.gain, sparam.pitch, sparam.fade, sparam.loop, sparam.pos,
|
sparam.gain, sparam.pitch, sparam.fade, sparam.start_time,
|
||||||
sparam.object and "<objref>", sparam.to_player, sparam.exclude_player,
|
sparam.loop, sparam.pos, sparam.object and "<objref>",
|
||||||
|
sparam.to_player, sparam.exclude_player,
|
||||||
sparam.max_hear_distance),
|
sparam.max_hear_distance),
|
||||||
tostring(ephemeral)))
|
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)
|
if(USE_SOUND)
|
||||||
set(sound_SRCS ${sound_SRCS}
|
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
|
set(SOUND_INCLUDE_DIRS
|
||||||
${OPENAL_INCLUDE_DIR}
|
${OPENAL_INCLUDE_DIR}
|
||||||
${VORBIS_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 "settings.h"
|
||||||
#include "wieldmesh.h"
|
#include "wieldmesh.h"
|
||||||
#include "noise.h" // easeCurve
|
#include "noise.h" // easeCurve
|
||||||
#include "sound.h"
|
|
||||||
#include "mtevent.h"
|
#include "mtevent.h"
|
||||||
#include "nodedef.h"
|
#include "nodedef.h"
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
|
@ -374,6 +374,11 @@ Client::~Client()
|
|||||||
if (m_mod_storage_database)
|
if (m_mod_storage_database)
|
||||||
m_mod_storage_database->endSave();
|
m_mod_storage_database->endSave();
|
||||||
delete m_mod_storage_database;
|
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)
|
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) {
|
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;
|
u16 object_id = m_sounds_to_object.second;
|
||||||
ClientActiveObject *cao = m_env.getActiveObject(object_id);
|
ClientActiveObject *cao = m_env.getActiveObject(object_id);
|
||||||
if (!cao)
|
if (!cao)
|
||||||
continue;
|
continue;
|
||||||
m_sound->updateSoundPosition(client_id, cao->getPosition());
|
m_sound->updateSoundPosVel(client_id, cao->getPosition() * (1.0f/BS),
|
||||||
|
cao->getVelocity() * (1.0f/BS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -719,22 +725,24 @@ void Client::step(float dtime)
|
|||||||
if(m_removed_sounds_check_timer >= 2.32) {
|
if(m_removed_sounds_check_timer >= 2.32) {
|
||||||
m_removed_sounds_check_timer = 0;
|
m_removed_sounds_check_timer = 0;
|
||||||
// Find removed sounds and clear references to them
|
// 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;
|
std::vector<s32> removed_server_ids;
|
||||||
for (std::unordered_map<s32, int>::iterator i = m_sounds_server_to_client.begin();
|
for (sound_handle_t client_id : removed_client_ids) {
|
||||||
i != m_sounds_server_to_client.end();) {
|
auto client_to_server_id_it = m_sounds_client_to_server.find(client_id);
|
||||||
s32 server_id = i->first;
|
if (client_to_server_id_it == m_sounds_client_to_server.end())
|
||||||
int client_id = i->second;
|
continue;
|
||||||
++i;
|
s32 server_id = client_to_server_id_it->second;
|
||||||
if(!m_sound->soundExists(client_id)) {
|
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_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);
|
removed_server_ids.push_back(server_id);
|
||||||
}
|
}
|
||||||
|
m_sounds_to_objects.erase(client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync to server
|
// Sync to server
|
||||||
if(!removed_server_ids.empty()) {
|
if (!removed_server_ids.empty()) {
|
||||||
sendRemovedSounds(removed_server_ids);
|
sendRemovedSounds(removed_server_ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -800,9 +808,13 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
|
|||||||
};
|
};
|
||||||
name = removeStringEnd(filename, sound_ext);
|
name = removeStringEnd(filename, sound_ext);
|
||||||
if (!name.empty()) {
|
if (!name.empty()) {
|
||||||
TRACESTREAM(<< "Client: Attempting to load sound "
|
TRACESTREAM(<< "Client: Attempting to load sound file \""
|
||||||
<< "file \"" << filename << "\"" << std::endl);
|
<< filename << "\"" << std::endl);
|
||||||
return m_sound->loadSoundData(name, data);
|
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[] = {
|
const char *model_ext[] = {
|
||||||
@ -1205,7 +1217,7 @@ void Client::sendGotBlocks(const std::vector<v3s16> &blocks)
|
|||||||
Send(&pkt);
|
Send(&pkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::sendRemovedSounds(std::vector<s32> &soundList)
|
void Client::sendRemovedSounds(const std::vector<s32> &soundList)
|
||||||
{
|
{
|
||||||
size_t server_ids = soundList.size();
|
size_t server_ids = soundList.size();
|
||||||
assert(server_ids <= 0xFFFF);
|
assert(server_ids <= 0xFFFF);
|
||||||
|
@ -70,6 +70,7 @@ class NetworkPacket;
|
|||||||
namespace con {
|
namespace con {
|
||||||
class Connection;
|
class Connection;
|
||||||
}
|
}
|
||||||
|
using sound_handle_t = int;
|
||||||
|
|
||||||
enum LocalClientState {
|
enum LocalClientState {
|
||||||
LC_Created,
|
LC_Created,
|
||||||
@ -468,7 +469,7 @@ private:
|
|||||||
void startAuth(AuthMechanism chosen_auth_mechanism);
|
void startAuth(AuthMechanism chosen_auth_mechanism);
|
||||||
void sendDeletedBlocks(std::vector<v3s16> &blocks);
|
void sendDeletedBlocks(std::vector<v3s16> &blocks);
|
||||||
void sendGotBlocks(const 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;
|
bool canSendChatMessage() const;
|
||||||
|
|
||||||
@ -564,11 +565,12 @@ private:
|
|||||||
// Sounds
|
// Sounds
|
||||||
float m_removed_sounds_check_timer = 0.0f;
|
float m_removed_sounds_check_timer = 0.0f;
|
||||||
// Mapping from server sound ids to our sound ids
|
// 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!
|
// 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
|
// 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
|
// Privileges
|
||||||
std::unordered_set<std::string> m_privileges;
|
std::unordered_set<std::string> m_privileges;
|
||||||
|
@ -47,7 +47,8 @@ public:
|
|||||||
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
|
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
|
||||||
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
|
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
|
||||||
virtual bool collideWithObjects() 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
|
virtual scene::ISceneNode *getSceneNode() const
|
||||||
{ return NULL; }
|
{ return NULL; }
|
||||||
virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const
|
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),
|
v3s16 node_below_pos = floatToInt(foot_pos + v3f(0.0f, -0.5f, 0.0f),
|
||||||
1.0f);
|
1.0f);
|
||||||
MapNode n = m_env->getMap().getNode(node_below_pos);
|
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
|
// Reduce footstep gain, as non-local-player footsteps are
|
||||||
// somehow louder.
|
// somehow louder.
|
||||||
spec.gain *= 0.6f;
|
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;
|
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; }
|
inline const v3f &getRotation() const { return m_rotation; }
|
||||||
|
|
||||||
|
@ -260,31 +260,25 @@ class SoundMaker
|
|||||||
const NodeDefManager *m_ndef;
|
const NodeDefManager *m_ndef;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool makes_footstep_sound;
|
bool makes_footstep_sound = true;
|
||||||
float m_player_step_timer;
|
float m_player_step_timer = 0.0f;
|
||||||
float m_player_jump_timer;
|
float m_player_jump_timer = 0.0f;
|
||||||
|
|
||||||
SimpleSoundSpec m_player_step_sound;
|
SoundSpec m_player_step_sound;
|
||||||
SimpleSoundSpec m_player_leftpunch_sound;
|
SoundSpec m_player_leftpunch_sound;
|
||||||
// Second sound made on left punch, currently used for item 'use' sound
|
// Second sound made on left punch, currently used for item 'use' sound
|
||||||
SimpleSoundSpec m_player_leftpunch_sound2;
|
SoundSpec m_player_leftpunch_sound2;
|
||||||
SimpleSoundSpec m_player_rightpunch_sound;
|
SoundSpec m_player_rightpunch_sound;
|
||||||
|
|
||||||
SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
|
SoundMaker(ISoundManager *sound, const NodeDefManager *ndef) :
|
||||||
m_sound(sound),
|
m_sound(sound), m_ndef(ndef) {}
|
||||||
m_ndef(ndef),
|
|
||||||
makes_footstep_sound(true),
|
|
||||||
m_player_step_timer(0.0f),
|
|
||||||
m_player_jump_timer(0.0f)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void playPlayerStep()
|
void playPlayerStep()
|
||||||
{
|
{
|
||||||
if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
|
if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
|
||||||
m_player_step_timer = 0.03;
|
m_player_step_timer = 0.03;
|
||||||
if (makes_footstep_sound)
|
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) {
|
if (m_player_jump_timer <= 0.0f) {
|
||||||
m_player_jump_timer = 0.2f;
|
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)
|
static void cameraPunchLeft(MtEvent *e, void *data)
|
||||||
{
|
{
|
||||||
SoundMaker *sm = (SoundMaker *)data;
|
SoundMaker *sm = (SoundMaker *)data;
|
||||||
sm->m_sound->playSound(sm->m_player_leftpunch_sound);
|
sm->m_sound->playSound(0, sm->m_player_leftpunch_sound);
|
||||||
sm->m_sound->playSound(sm->m_player_leftpunch_sound2);
|
sm->m_sound->playSound(0, sm->m_player_leftpunch_sound2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cameraPunchRight(MtEvent *e, void *data)
|
static void cameraPunchRight(MtEvent *e, void *data)
|
||||||
{
|
{
|
||||||
SoundMaker *sm = (SoundMaker *)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)
|
static void nodeDug(MtEvent *e, void *data)
|
||||||
{
|
{
|
||||||
SoundMaker *sm = (SoundMaker *)data;
|
SoundMaker *sm = (SoundMaker *)data;
|
||||||
NodeDugEvent *nde = (NodeDugEvent *)e;
|
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)
|
static void playerDamage(MtEvent *e, void *data)
|
||||||
{
|
{
|
||||||
SoundMaker *sm = (SoundMaker *)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)
|
static void playerFallingDamage(MtEvent *e, void *data)
|
||||||
{
|
{
|
||||||
SoundMaker *sm = (SoundMaker *)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)
|
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;
|
typedef s32 SamplerLayer_t;
|
||||||
|
|
||||||
@ -936,7 +894,6 @@ private:
|
|||||||
IWritableItemDefManager *itemdef_manager = nullptr;
|
IWritableItemDefManager *itemdef_manager = nullptr;
|
||||||
NodeDefManager *nodedef_manager = nullptr;
|
NodeDefManager *nodedef_manager = nullptr;
|
||||||
|
|
||||||
GameOnDemandSoundFetcher soundfetcher; // useful when testing
|
|
||||||
std::unique_ptr<ISoundManager> sound_manager;
|
std::unique_ptr<ISoundManager> sound_manager;
|
||||||
SoundMaker *soundmaker = nullptr;
|
SoundMaker *soundmaker = nullptr;
|
||||||
|
|
||||||
@ -1278,10 +1235,13 @@ void Game::run()
|
|||||||
if (m_is_paused)
|
if (m_is_paused)
|
||||||
dtime = 0.0f;
|
dtime = 0.0f;
|
||||||
|
|
||||||
if (!was_paused && m_is_paused)
|
if (!was_paused && m_is_paused) {
|
||||||
pauseAnimation();
|
pauseAnimation();
|
||||||
else if (was_paused && !m_is_paused)
|
sound_manager->pauseAll();
|
||||||
|
} else if (was_paused && !m_is_paused) {
|
||||||
resumeAnimation();
|
resumeAnimation();
|
||||||
|
sound_manager->resumeAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_is_paused)
|
if (!m_is_paused)
|
||||||
@ -1397,11 +1357,13 @@ bool Game::initSound()
|
|||||||
#if USE_SOUND
|
#if USE_SOUND
|
||||||
if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
|
if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
|
||||||
infostream << "Attempting to use OpenAL audio" << std::endl;
|
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)
|
if (!sound_manager)
|
||||||
infostream << "Failed to initialize OpenAL audio" << std::endl;
|
infostream << "Failed to initialize OpenAL audio" << std::endl;
|
||||||
} else
|
} else {
|
||||||
infostream << "Sound disabled." << std::endl;
|
infostream << "Sound disabled." << std::endl;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!sound_manager) {
|
if (!sound_manager) {
|
||||||
@ -3194,10 +3156,13 @@ void Game::updateCamera(f32 dtime)
|
|||||||
void Game::updateSound(f32 dtime)
|
void Game::updateSound(f32 dtime)
|
||||||
{
|
{
|
||||||
// Update sound listener
|
// Update sound listener
|
||||||
|
LocalPlayer *player = client->getEnv().getLocalPlayer();
|
||||||
|
ClientActiveObject *parent = player->getParent();
|
||||||
v3s16 camera_offset = camera->getOffset();
|
v3s16 camera_offset = camera->getOffset();
|
||||||
sound_manager->updateListener(
|
sound_manager->updateListener(
|
||||||
camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
|
(1.0f/BS) * camera->getCameraNode()->getPosition()
|
||||||
v3f(0, 0, 0), // velocity
|
+ intToFloat(camera_offset, 1.0f),
|
||||||
|
(1.0f/BS) * (parent ? parent->getVelocity() : player->getSpeed()),
|
||||||
camera->getDirection(),
|
camera->getDirection(),
|
||||||
camera->getCameraNode()->getUpVector());
|
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
|
// Tell the sound maker whether to make footstep sounds
|
||||||
soundmaker->makes_footstep_sound = player->makes_footstep_sound;
|
soundmaker->makes_footstep_sound = player->makes_footstep_sound;
|
||||||
|
|
||||||
@ -3332,7 +3295,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
|
|||||||
|
|
||||||
runData.punching = false;
|
runData.punching = false;
|
||||||
|
|
||||||
soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
|
soundmaker->m_player_leftpunch_sound = SoundSpec();
|
||||||
soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
|
soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
|
||||||
selected_def.sound_use : selected_def.sound_use_air;
|
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)
|
// Placing animation (always shown for feedback)
|
||||||
camera->setDigging(1);
|
camera->setDigging(1);
|
||||||
|
|
||||||
soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
|
soundmaker->m_player_rightpunch_sound = SoundSpec();
|
||||||
|
|
||||||
// If the wielded item has node placement prediction,
|
// If the wielded item has node placement prediction,
|
||||||
// make that happen
|
// 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
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include "irr_v3d.h"
|
#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:
|
public:
|
||||||
virtual void fetchSounds(const std::string &name,
|
virtual ~SoundFallbackPathProvider() = default;
|
||||||
std::set<std::string> &dst_paths,
|
std::vector<std::string> getLocalFallbackPathsForSoundname(const std::string &name);
|
||||||
std::set<std::string> &dst_datas) = 0;
|
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
|
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:
|
public:
|
||||||
virtual ~ISoundManager() = default;
|
virtual ~ISoundManager() = default;
|
||||||
|
|
||||||
// Multiple sounds can be loaded per name; when played, the sound
|
/**
|
||||||
// should be chosen randomly from alternatives
|
* Removes finished sounds, steps streamed sounds, and does similar tasks.
|
||||||
// Return value determines success/failure
|
* Should not be called while paused.
|
||||||
virtual bool loadSoundFile(
|
* @param dtime In seconds.
|
||||||
const std::string &name, const std::string &filepath) = 0;
|
*/
|
||||||
virtual bool loadSoundData(
|
virtual void step(f32 dtime) = 0;
|
||||||
const std::string &name, const std::string &filedata) = 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;
|
* @param pos In node-space.
|
||||||
virtual void setListenerGain(float gain) = 0;
|
* @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.
|
* Adds a sound to load from a file (only OggVorbis).
|
||||||
virtual int playSound(const SimpleSoundSpec &spec) = 0;
|
* @param name The name of the sound. Must be unique, otherwise call fails.
|
||||||
virtual int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) = 0;
|
* @param filepath The path for
|
||||||
virtual void stopSound(int sound) = 0;
|
* @return true on success, false on failure (ie. sound was already added or
|
||||||
virtual bool soundExists(int sound) = 0;
|
* file does not exist).
|
||||||
virtual void updateSoundPosition(int sound, v3f pos) = 0;
|
*/
|
||||||
virtual bool updateSoundGain(int id, float gain) = 0;
|
virtual bool loadSoundFile(const std::string &name, const std::string &filepath) = 0;
|
||||||
virtual float getSoundGain(int id) = 0;
|
/**
|
||||||
virtual void step(float dtime) = 0;
|
* Same as `loadSoundFile`, but reads the OggVorbis file from memory.
|
||||||
virtual void fadeSound(int sound, float step, float gain) = 0;
|
*/
|
||||||
|
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:
|
public:
|
||||||
virtual bool loadSoundFile(const std::string &name, const std::string &filepath)
|
void step(f32 dtime) override {}
|
||||||
{
|
void pauseAll() override {}
|
||||||
return true;
|
void resumeAll() override {}
|
||||||
}
|
|
||||||
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) {}
|
|
||||||
|
|
||||||
int playSound(const SimpleSoundSpec &spec) { return -1; }
|
void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up) override {}
|
||||||
int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) { return -1; }
|
void setListenerGain(f32 gain) override {}
|
||||||
void stopSound(int sound) {}
|
|
||||||
bool soundExists(int sound) { return false; }
|
bool loadSoundFile(const std::string &name, const std::string &filepath) override { return true; }
|
||||||
void updateSoundPosition(int sound, v3f pos) {}
|
bool loadSoundData(const std::string &name, std::string &&filedata) override { return true; }
|
||||||
bool updateSoundGain(int id, float gain) { return false; }
|
void addSoundToGroup(const std::string &sound_name, const std::string &group_name) override {};
|
||||||
float getSoundGain(int id) { return 0; }
|
|
||||||
void step(float dtime) {}
|
void playSound(sound_handle_t id, const SoundSpec &spec) override { reportRemovedSound(id); }
|
||||||
void fadeSound(int sound, float step, float gain) {}
|
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"
|
#include "sound_openal.h"
|
||||||
|
#include "sound_openal_internal.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
|
|
||||||
|
|
||||||
std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
|
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()
|
std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
|
||||||
{
|
{
|
||||||
auto smg = std::make_shared<SoundManagerSingleton>();
|
auto smg = std::make_shared<SoundManagerSingleton>();
|
||||||
@ -728,7 +35,8 @@ std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
|
|||||||
return smg;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class SoundManagerSingleton;
|
class SoundManagerSingleton;
|
||||||
extern std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
|
extern std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
|
||||||
|
|
||||||
std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton();
|
std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton();
|
||||||
ISoundManager *createOpenALSoundManager(
|
std::unique_ptr<ISoundManager> createOpenALSoundManager(
|
||||||
SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher);
|
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 "guiMainMenu.h"
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "client/sound_openal.h"
|
#include "client/sound_openal.h"
|
||||||
#include "client/clouds.h"
|
|
||||||
#include "httpfetch.h"
|
#include "httpfetch.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "client/fontengine.h"
|
#include "client/fontengine.h"
|
||||||
@ -97,28 +96,15 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/** MenuMusicFetcher */
|
/** MenuMusicFetcher */
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
void MenuMusicFetcher::fetchSounds(const std::string &name,
|
void MenuMusicFetcher::addThePaths(const std::string &name,
|
||||||
std::set<std::string> &dst_paths,
|
std::vector<std::string> &paths)
|
||||||
std::set<std::string> &dst_datas)
|
|
||||||
{
|
{
|
||||||
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
|
// Allow full paths
|
||||||
if (name.find(DIR_DELIM_CHAR) != std::string::npos) {
|
if (name.find(DIR_DELIM_CHAR) != std::string::npos) {
|
||||||
add_paths(name);
|
addAllAlternatives(name, paths);
|
||||||
} else {
|
} else {
|
||||||
std::string share_prefix = porting::path_share + DIR_DELIM;
|
addAllAlternatives(porting::path_share + DIR_DELIM + "sounds" + DIR_DELIM + name, paths);
|
||||||
add_paths(name, share_prefix + "sounds" + DIR_DELIM);
|
addAllAlternatives(porting::path_user + DIR_DELIM + "sounds" + DIR_DELIM + name, paths);
|
||||||
std::string user_prefix = porting::path_user + DIR_DELIM;
|
|
||||||
add_paths(name, user_prefix + "sounds" + DIR_DELIM);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,8 +137,10 @@ GUIEngine::GUIEngine(JoystickController *joystick,
|
|||||||
|
|
||||||
// create soundmanager
|
// create soundmanager
|
||||||
#if USE_SOUND
|
#if USE_SOUND
|
||||||
if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get())
|
if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
|
||||||
m_sound_manager.reset(createOpenALSoundManager(g_sound_manager_singleton.get(), &m_soundfetcher));
|
m_sound_manager = createOpenALSoundManager(g_sound_manager_singleton.get(),
|
||||||
|
std::make_unique<MenuMusicFetcher>());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (!m_sound_manager)
|
if (!m_sound_manager)
|
||||||
m_sound_manager = std::make_unique<DummySoundManager>();
|
m_sound_manager = std::make_unique<DummySoundManager>();
|
||||||
@ -318,11 +306,12 @@ void GUIEngine::run()
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
GUIEngine::~GUIEngine()
|
GUIEngine::~GUIEngine()
|
||||||
{
|
{
|
||||||
m_sound_manager.reset();
|
// deinitialize script first. gc destructors might depend on other stuff
|
||||||
|
infostream << "GUIEngine: Deinitializing scripting" << std::endl;
|
||||||
infostream<<"GUIEngine: Deinitializing scripting"<<std::endl;
|
|
||||||
m_script.reset();
|
m_script.reset();
|
||||||
|
|
||||||
|
m_sound_manager.reset();
|
||||||
|
|
||||||
m_irr_toplefttext->setText(L"");
|
m_irr_toplefttext->setText(L"");
|
||||||
|
|
||||||
//clean up texture pointers
|
//clean up texture pointers
|
||||||
@ -608,16 +597,3 @@ void GUIEngine::updateTopLeftTextSize()
|
|||||||
m_irr_toplefttext = gui::StaticText::add(m_rendering_engine->get_gui_env(),
|
m_irr_toplefttext = gui::StaticText::add(m_rendering_engine->get_gui_env(),
|
||||||
m_toplefttext, rect, false, true, 0, -1);
|
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;
|
std::vector<video::ITexture*> m_to_delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** GUIEngine specific implementation of OnDemandSoundFetcher */
|
/** GUIEngine specific implementation of SoundFallbackPathProvider */
|
||||||
class MenuMusicFetcher: public OnDemandSoundFetcher
|
class MenuMusicFetcher final : public SoundFallbackPathProvider
|
||||||
{
|
{
|
||||||
public:
|
protected:
|
||||||
/**
|
void addThePaths(const std::string &name,
|
||||||
* get sound file paths according to sound name
|
std::vector<std::string> &paths) override;
|
||||||
* @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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** implementation of main menu based uppon formspecs */
|
/** implementation of main menu based uppon formspecs */
|
||||||
class GUIEngine {
|
class GUIEngine {
|
||||||
/** grant ModApiMainMenu access to private members */
|
/** grant ModApiMainMenu access to private members */
|
||||||
friend class ModApiMainMenu;
|
friend class ModApiMainMenu;
|
||||||
friend class ModApiSound;
|
friend class ModApiMainMenuSound;
|
||||||
|
friend class MainMenuSoundHandle;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@ -197,8 +187,6 @@ private:
|
|||||||
MainMenuData *m_data = nullptr;
|
MainMenuData *m_data = nullptr;
|
||||||
/** texture source */
|
/** texture source */
|
||||||
std::unique_ptr<ISimpleTextureSource> m_texture_source;
|
std::unique_ptr<ISimpleTextureSource> m_texture_source;
|
||||||
/** sound fetcher, used by sound manager*/
|
|
||||||
MenuMusicFetcher m_soundfetcher{};
|
|
||||||
/** sound manager*/
|
/** sound manager*/
|
||||||
std::unique_ptr<ISoundManager> m_sound_manager;
|
std::unique_ptr<ISoundManager> m_sound_manager;
|
||||||
|
|
||||||
@ -296,11 +284,4 @@ private:
|
|||||||
bool m_clouds_enabled = true;
|
bool m_clouds_enabled = true;
|
||||||
/** data used to draw clouds */
|
/** data used to draw clouds */
|
||||||
clouddata m_cloud;
|
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) &&
|
if ((s.ftype == f_TabHeader) &&
|
||||||
(s.fid == event.GUIEvent.Caller->getID())) {
|
(s.fid == event.GUIEvent.Caller->getID())) {
|
||||||
if (!s.sound.empty() && m_sound_manager)
|
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;
|
s.send = true;
|
||||||
acceptInput();
|
acceptInput();
|
||||||
s.send = false;
|
s.send = false;
|
||||||
@ -4861,7 +4861,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||||||
|
|
||||||
if (s.ftype == f_Button || s.ftype == f_CheckBox) {
|
if (s.ftype == f_Button || s.ftype == f_CheckBox) {
|
||||||
if (!s.sound.empty() && m_sound_manager)
|
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;
|
s.send = true;
|
||||||
if (s.is_exit) {
|
if (s.is_exit) {
|
||||||
@ -4886,7 +4886,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!s.sound.empty() && m_sound_manager)
|
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;
|
s.send = true;
|
||||||
acceptInput(quit_mode_no);
|
acceptInput(quit_mode_no);
|
||||||
|
|
||||||
@ -4904,7 +4904,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||||||
s.fdefault.clear();
|
s.fdefault.clear();
|
||||||
} else if (s.ftype == f_Unknown || s.ftype == f_HyperText) {
|
} else if (s.ftype == f_Unknown || s.ftype == f_HyperText) {
|
||||||
if (!s.sound.empty() && m_sound_manager)
|
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;
|
s.send = true;
|
||||||
acceptInput();
|
acceptInput();
|
||||||
s.send = false;
|
s.send = false;
|
||||||
|
@ -117,10 +117,10 @@ void ItemDefinition::reset()
|
|||||||
delete tool_capabilities;
|
delete tool_capabilities;
|
||||||
tool_capabilities = NULL;
|
tool_capabilities = NULL;
|
||||||
groups.clear();
|
groups.clear();
|
||||||
sound_place = SimpleSoundSpec();
|
sound_place = SoundSpec();
|
||||||
sound_place_failed = SimpleSoundSpec();
|
sound_place_failed = SoundSpec();
|
||||||
sound_use = SimpleSoundSpec();
|
sound_use = SoundSpec();
|
||||||
sound_use_air = SimpleSoundSpec();
|
sound_use_air = SoundSpec();
|
||||||
range = -1;
|
range = -1;
|
||||||
node_placement_prediction.clear();
|
node_placement_prediction.clear();
|
||||||
place_param2 = 0;
|
place_param2 = 0;
|
||||||
@ -158,8 +158,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
|
|||||||
os << serializeString16(node_placement_prediction);
|
os << serializeString16(node_placement_prediction);
|
||||||
|
|
||||||
// Version from ContentFeatures::serialize to keep in sync
|
// Version from ContentFeatures::serialize to keep in sync
|
||||||
sound_place.serialize(os, protocol_version);
|
sound_place.serializeSimple(os, protocol_version);
|
||||||
sound_place_failed.serialize(os, protocol_version);
|
sound_place_failed.serializeSimple(os, protocol_version);
|
||||||
|
|
||||||
writeF32(os, range);
|
writeF32(os, range);
|
||||||
os << serializeString16(palette_image);
|
os << serializeString16(palette_image);
|
||||||
@ -171,8 +171,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
|
|||||||
|
|
||||||
os << place_param2;
|
os << place_param2;
|
||||||
|
|
||||||
sound_use.serialize(os, protocol_version);
|
sound_use.serializeSimple(os, protocol_version);
|
||||||
sound_use_air.serialize(os, protocol_version);
|
sound_use_air.serializeSimple(os, protocol_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemDefinition::deSerialize(std::istream &is, u16 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);
|
node_placement_prediction = deSerializeString16(is);
|
||||||
|
|
||||||
sound_place.deSerialize(is, protocol_version);
|
sound_place.deSerializeSimple(is, protocol_version);
|
||||||
sound_place_failed.deSerialize(is, protocol_version);
|
sound_place_failed.deSerializeSimple(is, protocol_version);
|
||||||
|
|
||||||
range = readF32(is);
|
range = readF32(is);
|
||||||
palette_image = deSerializeString16(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
|
place_param2 = readU8(is); // 0 if missing
|
||||||
|
|
||||||
sound_use.deSerialize(is, protocol_version);
|
sound_use.deSerializeSimple(is, protocol_version);
|
||||||
sound_use_air.deSerialize(is, protocol_version);
|
sound_use_air.deSerializeSimple(is, protocol_version);
|
||||||
} catch(SerializationError &e) {};
|
} catch(SerializationError &e) {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +78,9 @@ struct ItemDefinition
|
|||||||
// May be NULL. If non-NULL, deleted by destructor
|
// May be NULL. If non-NULL, deleted by destructor
|
||||||
ToolCapabilities *tool_capabilities;
|
ToolCapabilities *tool_capabilities;
|
||||||
ItemGroupList groups;
|
ItemGroupList groups;
|
||||||
SimpleSoundSpec sound_place;
|
SoundSpec sound_place;
|
||||||
SimpleSoundSpec sound_place_failed;
|
SoundSpec sound_place_failed;
|
||||||
SimpleSoundSpec sound_use, sound_use_air;
|
SoundSpec sound_use, sound_use_air;
|
||||||
f32 range;
|
f32 range;
|
||||||
|
|
||||||
// Client shall immediately place this node when player places the item.
|
// 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)
|
void Client::handleCommand_PlaySound(NetworkPacket* pkt)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
[0] u32 server_id
|
[0] s32 server_id
|
||||||
[4] u16 name length
|
[4] u16 name length
|
||||||
[6] char name[len]
|
[6] char name[len]
|
||||||
[ 6 + len] f32 gain
|
[ 6 + len] f32 gain
|
||||||
[10 + len] u8 type
|
[10 + len] u8 type (SoundLocation)
|
||||||
[11 + len] (f32 * 3) pos
|
[11 + len] v3f pos (in BS-space)
|
||||||
[23 + len] u16 object_id
|
[23 + len] u16 object_id
|
||||||
[25 + len] bool loop
|
[25 + len] bool loop
|
||||||
[26 + len] f32 fade
|
[26 + len] f32 fade
|
||||||
[30 + len] f32 pitch
|
[30 + len] f32 pitch
|
||||||
[34 + len] bool ephemeral
|
[34 + len] bool ephemeral
|
||||||
|
[35 + len] f32 start_time (in seconds)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
s32 server_id;
|
s32 server_id;
|
||||||
|
|
||||||
SimpleSoundSpec spec;
|
SoundSpec spec;
|
||||||
SoundLocation type; // 0=local, 1=positional, 2=object
|
SoundLocation type;
|
||||||
v3f pos;
|
v3f pos;
|
||||||
u16 object_id;
|
u16 object_id;
|
||||||
bool ephemeral = false;
|
bool ephemeral = false;
|
||||||
|
|
||||||
*pkt >> server_id >> spec.name >> spec.gain >> (u8 &)type >> pos >> object_id >> spec.loop;
|
*pkt >> server_id >> spec.name >> spec.gain >> (u8 &)type >> pos >> object_id >> spec.loop;
|
||||||
|
pos *= 1.0f/BS;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
*pkt >> spec.fade;
|
*pkt >> spec.fade;
|
||||||
*pkt >> spec.pitch;
|
*pkt >> spec.pitch;
|
||||||
*pkt >> ephemeral;
|
*pkt >> ephemeral;
|
||||||
|
*pkt >> spec.start_time;
|
||||||
} catch (PacketError &e) {};
|
} catch (PacketError &e) {};
|
||||||
|
|
||||||
|
// Generate a new id
|
||||||
|
sound_handle_t client_id = (ephemeral && object_id == 0) ? 0 : m_sound->allocateId(2);
|
||||||
|
|
||||||
// Start playing
|
// Start playing
|
||||||
int client_id = -1;
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case SoundLocation::Local:
|
case SoundLocation::Local:
|
||||||
client_id = m_sound->playSound(spec);
|
m_sound->playSound(client_id, spec);
|
||||||
break;
|
break;
|
||||||
case SoundLocation::Position:
|
case SoundLocation::Position:
|
||||||
client_id = m_sound->playSoundAt(spec, pos);
|
m_sound->playSoundAt(client_id, spec, pos, v3f(0.0f));
|
||||||
break;
|
break;
|
||||||
case SoundLocation::Object:
|
case SoundLocation::Object: {
|
||||||
{
|
ClientActiveObject *cao = m_env.getActiveObject(object_id);
|
||||||
ClientActiveObject *cao = m_env.getActiveObject(object_id);
|
v3f vel(0.0f);
|
||||||
if (cao)
|
if (cao) {
|
||||||
pos = cao->getPosition();
|
pos = cao->getPosition() * (1.0f/BS);
|
||||||
client_id = m_sound->playSoundAt(spec, pos);
|
vel = cao->getVelocity() * (1.0f/BS);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
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) {
|
if (client_id != 0) {
|
||||||
// for ephemeral sounds, server_id is not meaningful
|
// Note: m_sounds_client_to_server takes 1 ownership
|
||||||
if (!ephemeral) {
|
// 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_server_to_client[server_id] = client_id;
|
||||||
m_sounds_client_to_server[client_id] = server_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
|
new fields for TOCLIENT_SET_LIGHTING and TOCLIENT_SET_SKY
|
||||||
Send forgotten TweenedParameter properties
|
Send forgotten TweenedParameter properties
|
||||||
[scheduled bump for 5.7.0]
|
[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)
|
#define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION)
|
||||||
|
|
||||||
// Server's supported network protocol range
|
// Server's supported network protocol range
|
||||||
@ -454,15 +457,18 @@ enum ToClientCommand
|
|||||||
|
|
||||||
TOCLIENT_PLAY_SOUND = 0x3f,
|
TOCLIENT_PLAY_SOUND = 0x3f,
|
||||||
/*
|
/*
|
||||||
s32 sound_id
|
s32 server_id
|
||||||
u16 len
|
u16 len
|
||||||
u8[len] sound name
|
u8[len] sound name
|
||||||
s32 gain*1000
|
f32 gain
|
||||||
u8 type (0=local, 1=positional, 2=object)
|
u8 type (SoundLocation: 0=local, 1=positional, 2=object)
|
||||||
s32[3] pos_nodes*10000
|
v3f pos_nodes (in BS-space)
|
||||||
u16 object_id
|
u16 object_id
|
||||||
u8 loop (bool)
|
u8 loop (bool)
|
||||||
|
f32 fade
|
||||||
|
f32 pitch
|
||||||
u8 ephemeral (bool)
|
u8 ephemeral (bool)
|
||||||
|
f32 start_time (in seconds)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TOCLIENT_STOP_SOUND = 0x40,
|
TOCLIENT_STOP_SOUND = 0x40,
|
||||||
|
@ -403,9 +403,9 @@ void ContentFeatures::reset()
|
|||||||
waving = 0;
|
waving = 0;
|
||||||
legacy_facedir_simple = false;
|
legacy_facedir_simple = false;
|
||||||
legacy_wallmounted = false;
|
legacy_wallmounted = false;
|
||||||
sound_footstep = SimpleSoundSpec();
|
sound_footstep = SoundSpec();
|
||||||
sound_dig = SimpleSoundSpec("__group");
|
sound_dig = SoundSpec("__group");
|
||||||
sound_dug = SimpleSoundSpec();
|
sound_dug = SoundSpec();
|
||||||
connects_to.clear();
|
connects_to.clear();
|
||||||
connects_to_ids.clear();
|
connects_to_ids.clear();
|
||||||
connect_sides = 0;
|
connect_sides = 0;
|
||||||
@ -529,9 +529,9 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
|
|||||||
collision_box.serialize(os, protocol_version);
|
collision_box.serialize(os, protocol_version);
|
||||||
|
|
||||||
// sound
|
// sound
|
||||||
sound_footstep.serialize(os, protocol_version);
|
sound_footstep.serializeSimple(os, protocol_version);
|
||||||
sound_dig.serialize(os, protocol_version);
|
sound_dig.serializeSimple(os, protocol_version);
|
||||||
sound_dug.serialize(os, protocol_version);
|
sound_dug.serializeSimple(os, protocol_version);
|
||||||
|
|
||||||
// legacy
|
// legacy
|
||||||
writeU8(os, legacy_facedir_simple);
|
writeU8(os, legacy_facedir_simple);
|
||||||
@ -626,9 +626,9 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
|
|||||||
collision_box.deSerialize(is);
|
collision_box.deSerialize(is);
|
||||||
|
|
||||||
// sounds
|
// sounds
|
||||||
sound_footstep.deSerialize(is, protocol_version);
|
sound_footstep.deSerializeSimple(is, protocol_version);
|
||||||
sound_dig.deSerialize(is, protocol_version);
|
sound_dig.deSerializeSimple(is, protocol_version);
|
||||||
sound_dug.deSerialize(is, protocol_version);
|
sound_dug.deSerializeSimple(is, protocol_version);
|
||||||
|
|
||||||
// read legacy properties
|
// read legacy properties
|
||||||
legacy_facedir_simple = readU8(is);
|
legacy_facedir_simple = readU8(is);
|
||||||
|
@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
class Client;
|
class Client;
|
||||||
#endif
|
#endif
|
||||||
#include "itemgroup.h"
|
#include "itemgroup.h"
|
||||||
#include "sound.h" // SimpleSoundSpec
|
#include "sound.h" // SoundSpec
|
||||||
#include "constants.h" // BS
|
#include "constants.h" // BS
|
||||||
#include "texture_override.h" // TextureOverride
|
#include "texture_override.h" // TextureOverride
|
||||||
#include "tileanimation.h"
|
#include "tileanimation.h"
|
||||||
@ -434,9 +434,9 @@ struct ContentFeatures
|
|||||||
|
|
||||||
// --- SOUND PROPERTIES ---
|
// --- SOUND PROPERTIES ---
|
||||||
|
|
||||||
SimpleSoundSpec sound_footstep;
|
SoundSpec sound_footstep;
|
||||||
SimpleSoundSpec sound_dig;
|
SoundSpec sound_dig;
|
||||||
SimpleSoundSpec sound_dug;
|
SoundSpec sound_dug;
|
||||||
|
|
||||||
// --- LEGACY ---
|
// --- LEGACY ---
|
||||||
|
|
||||||
|
@ -146,11 +146,13 @@ public:
|
|||||||
std::vector<CollisionInfo> *collision_info)
|
std::vector<CollisionInfo> *collision_info)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
// in BS-space
|
||||||
v3f getSpeed() const
|
v3f getSpeed() const
|
||||||
{
|
{
|
||||||
return m_speed;
|
return m_speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in BS-space
|
||||||
void setSpeed(v3f speed)
|
void setSpeed(v3f speed)
|
||||||
{
|
{
|
||||||
m_speed = speed;
|
m_speed = speed;
|
||||||
@ -223,7 +225,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
char m_name[PLAYERNAME_SIZE];
|
char m_name[PLAYERNAME_SIZE];
|
||||||
v3f m_speed;
|
v3f m_speed; // velocity; in BS-space
|
||||||
u16 m_wield_index = 0;
|
u16 m_wield_index = 0;
|
||||||
PlayerFovSpec m_fov_override_spec = { 0.0f, false, 0.0f };
|
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)) {
|
if (!lua_isnil(L, -1)) {
|
||||||
luaL_checktype(L, -1, LUA_TTABLE);
|
luaL_checktype(L, -1, LUA_TTABLE);
|
||||||
lua_getfield(L, -1, "place");
|
lua_getfield(L, -1, "place");
|
||||||
read_soundspec(L, -1, def.sound_place);
|
read_simplesoundspec(L, -1, def.sound_place);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
lua_getfield(L, -1, "place_failed");
|
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);
|
||||||
}
|
}
|
||||||
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)) {
|
if (!lua_isnil(L, -1)) {
|
||||||
luaL_checktype(L, -1, LUA_TTABLE);
|
luaL_checktype(L, -1, LUA_TTABLE);
|
||||||
lua_getfield(L, -1, "punch_use");
|
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_pop(L, 1);
|
||||||
lua_getfield(L, -1, "punch_use_air");
|
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);
|
||||||
}
|
}
|
||||||
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);
|
push_groups(L, i.groups);
|
||||||
lua_setfield(L, -2, "groups");
|
lua_setfield(L, -2, "groups");
|
||||||
push_soundspec(L, i.sound_place);
|
push_simplesoundspec(L, i.sound_place);
|
||||||
lua_setfield(L, -2, "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_setfield(L, -2, "sound_place_failed");
|
||||||
lua_pushstring(L, i.node_placement_prediction.c_str());
|
lua_pushstring(L, i.node_placement_prediction.c_str());
|
||||||
lua_setfield(L, -2, "node_placement_prediction");
|
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");
|
lua_getfield(L, index, "sounds");
|
||||||
if(lua_istable(L, -1)){
|
if(lua_istable(L, -1)){
|
||||||
lua_getfield(L, -1, "footstep");
|
lua_getfield(L, -1, "footstep");
|
||||||
read_soundspec(L, -1, f.sound_footstep);
|
read_simplesoundspec(L, -1, f.sound_footstep);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
lua_getfield(L, -1, "dig");
|
lua_getfield(L, -1, "dig");
|
||||||
read_soundspec(L, -1, f.sound_dig);
|
read_simplesoundspec(L, -1, f.sound_dig);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
lua_getfield(L, -1, "dug");
|
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);
|
||||||
}
|
}
|
||||||
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);
|
push_nodebox(L, c.collision_box);
|
||||||
lua_setfield(L, -2, "collision_box");
|
lua_setfield(L, -2, "collision_box");
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
push_soundspec(L, c.sound_footstep);
|
push_simplesoundspec(L, c.sound_footstep);
|
||||||
lua_setfield(L, -2, "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");
|
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, "sound_dug");
|
||||||
lua_setfield(L, -2, "sounds");
|
lua_setfield(L, -2, "sounds");
|
||||||
lua_pushboolean(L, c.legacy_facedir_simple);
|
lua_pushboolean(L, c.legacy_facedir_simple);
|
||||||
@ -1067,10 +1067,11 @@ void read_server_sound_params(lua_State *L, int index,
|
|||||||
if(index < 0)
|
if(index < 0)
|
||||||
index = lua_gettop(L) + 1 + index;
|
index = lua_gettop(L) + 1 + index;
|
||||||
|
|
||||||
if(lua_istable(L, index)){
|
if (lua_istable(L, index)) {
|
||||||
// Functional overlap: this may modify SimpleSoundSpec contents
|
// Functional overlap: this may modify SimpleSoundSpec contents
|
||||||
getfloatfield(L, index, "fade", params.spec.fade);
|
getfloatfield(L, index, "fade", params.spec.fade);
|
||||||
getfloatfield(L, index, "pitch", params.spec.pitch);
|
getfloatfield(L, index, "pitch", params.spec.pitch);
|
||||||
|
getfloatfield(L, index, "start_time", params.spec.start_time);
|
||||||
getboolfield(L, index, "loop", params.spec.loop);
|
getboolfield(L, index, "loop", params.spec.loop);
|
||||||
|
|
||||||
getfloatfield(L, index, "gain", params.gain);
|
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)
|
if(index < 0)
|
||||||
index = lua_gettop(L) + 1 + index;
|
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_createtable(L, 0, 3);
|
||||||
lua_pushstring(L, spec.name.c_str());
|
lua_pushstring(L, spec.name.c_str());
|
||||||
|
@ -53,7 +53,7 @@ struct ItemStack;
|
|||||||
struct ItemDefinition;
|
struct ItemDefinition;
|
||||||
struct ToolCapabilities;
|
struct ToolCapabilities;
|
||||||
struct ObjectProperties;
|
struct ObjectProperties;
|
||||||
struct SimpleSoundSpec;
|
struct SoundSpec;
|
||||||
struct ServerPlayingSound;
|
struct ServerPlayingSound;
|
||||||
class Inventory;
|
class Inventory;
|
||||||
class InventoryList;
|
class InventoryList;
|
||||||
@ -87,8 +87,8 @@ void push_palette (lua_State *L,
|
|||||||
TileDef read_tiledef (lua_State *L, int index,
|
TileDef read_tiledef (lua_State *L, int index,
|
||||||
u8 drawtype, bool special);
|
u8 drawtype, bool special);
|
||||||
|
|
||||||
void read_soundspec (lua_State *L, int index,
|
void read_simplesoundspec (lua_State *L, int index,
|
||||||
SimpleSoundSpec &spec);
|
SoundSpec &spec);
|
||||||
NodeBox read_nodebox (lua_State *L, int index);
|
NodeBox read_nodebox (lua_State *L, int index);
|
||||||
|
|
||||||
void read_server_sound_params (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,
|
int index,
|
||||||
IGameDef* gdef);
|
IGameDef* gdef);
|
||||||
|
|
||||||
void push_soundspec (lua_State *L,
|
void push_simplesoundspec (lua_State *L,
|
||||||
const SimpleSoundSpec &spec);
|
const SoundSpec &spec);
|
||||||
|
|
||||||
bool string_to_enum (const EnumString *spec,
|
bool string_to_enum (const EnumString *spec,
|
||||||
int &result,
|
int &result,
|
||||||
|
@ -28,10 +28,11 @@ set(common_SCRIPT_LUA_API_SRCS
|
|||||||
set(client_SCRIPT_LUA_API_SRCS
|
set(client_SCRIPT_LUA_API_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_camera.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_camera.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_client.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_localplayer.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu.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_minimap.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_particles_local.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_particles_local.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_sound.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
@ -260,63 +260,6 @@ int ModApiClient::l_get_meta(lua_State *L)
|
|||||||
return 1;
|
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()
|
// get_server_info()
|
||||||
int ModApiClient::l_get_server_info(lua_State *L)
|
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(get_node_or_nil);
|
||||||
API_FCT(disconnect);
|
API_FCT(disconnect);
|
||||||
API_FCT(get_meta);
|
API_FCT(get_meta);
|
||||||
API_FCT(sound_play);
|
|
||||||
API_FCT(sound_stop);
|
|
||||||
API_FCT(sound_fade);
|
|
||||||
API_FCT(get_server_info);
|
API_FCT(get_server_info);
|
||||||
API_FCT(get_item_def);
|
API_FCT(get_item_def);
|
||||||
API_FCT(get_node_def);
|
API_FCT(get_node_def);
|
||||||
|
@ -78,15 +78,6 @@ private:
|
|||||||
// get_meta(pos)
|
// get_meta(pos)
|
||||||
static int l_get_meta(lua_State *L);
|
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()
|
// get_server_info()
|
||||||
static int l_get_server_info(lua_State *L);
|
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
|
Minetest
|
||||||
|
Copyright (C) 2023 DS
|
||||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "lua_api/l_base.h"
|
#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:
|
private:
|
||||||
|
// sound_play(spec, loop)
|
||||||
static int l_sound_play(lua_State *L);
|
static int l_sound_play(lua_State *L);
|
||||||
static int l_sound_stop(lua_State *L);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void Initialize(lua_State *L, int top);
|
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;
|
NO_MAP_LOCK_REQUIRED;
|
||||||
ServerPlayingSound params;
|
ServerPlayingSound params;
|
||||||
read_soundspec(L, 1, params.spec);
|
read_simplesoundspec(L, 1, params.spec);
|
||||||
read_server_sound_params(L, 2, params);
|
read_server_sound_params(L, 2, params);
|
||||||
bool ephemeral = lua_gettop(L) > 2 && readParam<bool>(L, 3);
|
bool ephemeral = lua_gettop(L) > 2 && readParam<bool>(L, 3);
|
||||||
if (ephemeral) {
|
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_modchannels.h"
|
||||||
#include "lua_api/l_particles_local.h"
|
#include "lua_api/l_particles_local.h"
|
||||||
#include "lua_api/l_storage.h"
|
#include "lua_api/l_storage.h"
|
||||||
#include "lua_api/l_sound.h"
|
|
||||||
#include "lua_api/l_util.h"
|
#include "lua_api/l_util.h"
|
||||||
#include "lua_api/l_item.h"
|
#include "lua_api/l_item.h"
|
||||||
#include "lua_api/l_nodemeta.h"
|
#include "lua_api/l_nodemeta.h"
|
||||||
#include "lua_api/l_localplayer.h"
|
#include "lua_api/l_localplayer.h"
|
||||||
#include "lua_api/l_camera.h"
|
#include "lua_api/l_camera.h"
|
||||||
#include "lua_api/l_settings.h"
|
#include "lua_api/l_settings.h"
|
||||||
|
#include "lua_api/l_client_sound.h"
|
||||||
|
|
||||||
ClientScripting::ClientScripting(Client *client):
|
ClientScripting::ClientScripting(Client *client):
|
||||||
ScriptApiBase(ScriptingType::Client)
|
ScriptApiBase(ScriptingType::Client)
|
||||||
@ -75,6 +75,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top)
|
|||||||
LuaCamera::Register(L);
|
LuaCamera::Register(L);
|
||||||
ModChannelRef::Register(L);
|
ModChannelRef::Register(L);
|
||||||
LuaSettings::Register(L);
|
LuaSettings::Register(L);
|
||||||
|
ClientSoundHandle::Register(L);
|
||||||
|
|
||||||
ModApiUtil::InitializeClient(L, top);
|
ModApiUtil::InitializeClient(L, top);
|
||||||
ModApiClient::Initialize(L, top);
|
ModApiClient::Initialize(L, top);
|
||||||
@ -83,6 +84,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top)
|
|||||||
ModApiEnvMod::InitializeClient(L, top);
|
ModApiEnvMod::InitializeClient(L, top);
|
||||||
ModApiChannels::Initialize(L, top);
|
ModApiChannels::Initialize(L, top);
|
||||||
ModApiParticlesLocal::Initialize(L, top);
|
ModApiParticlesLocal::Initialize(L, top);
|
||||||
|
ModApiClientSound::Initialize(L, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientScripting::on_client_ready(LocalPlayer *localplayer)
|
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_base.h"
|
||||||
#include "lua_api/l_http.h"
|
#include "lua_api/l_http.h"
|
||||||
#include "lua_api/l_mainmenu.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_util.h"
|
||||||
#include "lua_api/l_settings.h"
|
#include "lua_api/l_settings.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@ -66,7 +66,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top)
|
|||||||
// Initialize mod API modules
|
// Initialize mod API modules
|
||||||
ModApiMainMenu::Initialize(L, top);
|
ModApiMainMenu::Initialize(L, top);
|
||||||
ModApiUtil::Initialize(L, top);
|
ModApiUtil::Initialize(L, top);
|
||||||
ModApiSound::Initialize(L, top);
|
ModApiMainMenuSound::Initialize(L, top);
|
||||||
ModApiHttp::Initialize(L, top);
|
ModApiHttp::Initialize(L, top);
|
||||||
|
|
||||||
asyncEngine.registerStateInitializer(registerLuaClasses);
|
asyncEngine.registerStateInitializer(registerLuaClasses);
|
||||||
@ -83,6 +83,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top)
|
|||||||
void MainMenuScripting::registerLuaClasses(lua_State *L, int top)
|
void MainMenuScripting::registerLuaClasses(lua_State *L, int top)
|
||||||
{
|
{
|
||||||
LuaSettings::Register(L);
|
LuaSettings::Register(L);
|
||||||
|
MainMenuSoundHandle::Register(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -2231,7 +2231,7 @@ s32 Server::playSound(ServerPlayingSound ¶ms, bool ephemeral)
|
|||||||
pkt << id << params.spec.name << gain
|
pkt << id << params.spec.name << gain
|
||||||
<< (u8) params.type << pos << params.object
|
<< (u8) params.type << pos << params.object
|
||||||
<< params.spec.loop << params.spec.fade << params.spec.pitch
|
<< params.spec.loop << params.spec.fade << params.spec.pitch
|
||||||
<< ephemeral;
|
<< ephemeral << params.spec.start_time;
|
||||||
|
|
||||||
bool as_reliable = !ephemeral;
|
bool as_reliable = !ephemeral;
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ struct RollbackAction;
|
|||||||
class EmergeManager;
|
class EmergeManager;
|
||||||
class ServerScripting;
|
class ServerScripting;
|
||||||
class ServerEnvironment;
|
class ServerEnvironment;
|
||||||
struct SimpleSoundSpec;
|
struct SoundSpec;
|
||||||
struct CloudParams;
|
struct CloudParams;
|
||||||
struct SkyboxParams;
|
struct SkyboxParams;
|
||||||
struct SunParams;
|
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
|
struct ServerPlayingSound
|
||||||
{
|
{
|
||||||
SoundLocation type = SoundLocation::Local;
|
SoundLocation type = SoundLocation::Local;
|
||||||
@ -111,7 +111,7 @@ struct ServerPlayingSound
|
|||||||
|
|
||||||
v3f getPos(ServerEnvironment *env, bool *pos_exists) const;
|
v3f getPos(ServerEnvironment *env, bool *pos_exists) const;
|
||||||
|
|
||||||
SimpleSoundSpec spec;
|
SoundSpec spec;
|
||||||
|
|
||||||
std::unordered_set<session_t> clients; // peer ids
|
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 "util/serialize.h"
|
||||||
#include "irrlichttypes_bloated.h"
|
#include "irrlichttypes_bloated.h"
|
||||||
|
|
||||||
// This class describes the basic sound information for playback.
|
/**
|
||||||
// Positional handling is done separately.
|
* Describes the sound information for playback.
|
||||||
|
* Positional handling is done separately.
|
||||||
struct SimpleSoundSpec
|
*
|
||||||
|
* `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,
|
SoundSpec(const std::string &name = "", float gain = 1.0f,
|
||||||
bool loop = false, float fade = 0.0f, float pitch = 1.0f) :
|
bool loop = false, float fade = 0.0f, float pitch = 1.0f,
|
||||||
name(name), gain(gain), fade(fade), pitch(pitch), loop(loop)
|
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(); }
|
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);
|
os << serializeString16(name);
|
||||||
writeF32(os, gain);
|
writeF32(os, gain);
|
||||||
@ -45,7 +54,10 @@ struct SimpleSoundSpec
|
|||||||
writeF32(os, fade);
|
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);
|
name = deSerializeString16(is);
|
||||||
gain = readF32(is);
|
gain = readF32(is);
|
||||||
@ -53,11 +65,16 @@ struct SimpleSoundSpec
|
|||||||
fade = readF32(is);
|
fade = readF32(is);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name of the sound-group
|
||||||
std::string name;
|
std::string name;
|
||||||
float gain = 1.0f;
|
float gain = 1.0f;
|
||||||
float fade = 0.0f;
|
float fade = 0.0f;
|
||||||
float pitch = 1.0f;
|
float pitch = 1.0f;
|
||||||
|
float start_time = 0.0f;
|
||||||
bool loop = false;
|
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