]> git.lizzy.rs Git - minetest.git/blob - games/devtest/mods/soundstuff/jukebox.lua
Devtest: Add jukebox
[minetest.git] / games / devtest / mods / soundstuff / jukebox.lua
1 local F = minetest.formspec_escape
2
3 -- hashed node pos -> sound handle
4 local played_sounds = {}
5
6 -- all of these can be set via the formspec
7 local meta_keys = {
8         -- SimpleSoundSpec
9         "sss.name",
10         "sss.gain",
11         "sss.pitch",
12         "sss.fade",
13         -- sound parameters
14         "sparam.gain",
15         "sparam.pitch",
16         "sparam.fade",
17         "sparam.loop",
18         "sparam.pos",
19         "sparam.object",
20         "sparam.to_player",
21         "sparam.exclude_player",
22         "sparam.max_hear_distance",
23         -- fade
24         "fade.step",
25         "fade.gain",
26         -- other
27         "ephemeral",
28 }
29
30 local function get_all_metadata(meta)
31         return {
32                 sss = {
33                         name  = meta:get_string("sss.name"),
34                         gain  = meta:get_string("sss.gain"),
35                         pitch = meta:get_string("sss.pitch"),
36                         fade  = meta:get_string("sss.fade"),
37                 },
38                 sparam = {
39                         gain              = meta:get_string("sparam.gain"),
40                         pitch             = meta:get_string("sparam.pitch"),
41                         fade              = meta:get_string("sparam.fade"),
42                         loop              = meta:get_string("sparam.loop"),
43                         pos               = meta:get_string("sparam.pos"),
44                         object            = meta:get_string("sparam.object"),
45                         to_player         = meta:get_string("sparam.to_player"),
46                         exclude_player    = meta:get_string("sparam.exclude_player"),
47                         max_hear_distance = meta:get_string("sparam.max_hear_distance"),
48                 },
49                 fade = {
50                         gain = meta:get_string("fade.gain"),
51                         step = meta:get_string("fade.step"),
52                 },
53                 ephemeral = meta:get_string("ephemeral"),
54         }
55 end
56
57 local function log_msg(msg)
58         minetest.log("action", msg)
59         minetest.chat_send_all(msg)
60 end
61
62 local function try_call(f, ...)
63         local function log_on_err(success, errmsg, ...)
64                 if not success then
65                         log_msg("[soundstuff:jukebox] Call failed: "..errmsg)
66                 else
67                         return errmsg, ...
68                 end
69         end
70
71         return log_on_err(pcall(f, ...))
72 end
73
74 local function show_formspec(pos, player)
75         local meta = minetest.get_meta(pos)
76
77         local md = get_all_metadata(meta)
78
79         local pos_hash = minetest.hash_node_position(pos)
80         local sound_handle = played_sounds[pos_hash]
81
82         local fs = {}
83         local function fs_add(str)
84                 table.insert(fs, str)
85         end
86
87         fs_add([[
88                 formspec_version[6]
89                 size[14,11]
90         ]])
91
92         -- SimpleSoundSpec
93         fs_add(string.format([[
94                 container[0.5,0.5]
95                 box[-0.1,-0.1;4.2,3.2;#EBEBEB20]
96                 style[*;font=mono,bold]
97                 label[0,0.25;SimpleSoundSpec]
98                 style[*;font=mono]
99                 field[0.00,1.00;4,0.75;sss.name;name;%s]
100                 field[0.00,2.25;1,0.75;sss.gain;gain;%s]
101                 field[1.25,2.25;1,0.75;sss.pitch;pitch;%s]
102                 field[2.50,2.25;1,0.75;sss.fade;fade;%s]
103                 container_end[]
104                 field_close_on_enter[sss.name;false]
105                 field_close_on_enter[sss.gain;false]
106                 field_close_on_enter[sss.pitch;false]
107                 field_close_on_enter[sss.fade;false]
108         ]], F(md.sss.name), F(md.sss.gain), F(md.sss.pitch), F(md.sss.fade)))
109
110         -- sound parameter table
111         fs_add(string.format([[
112                 container[5.5,0.5]
113                 box[-0.1,-0.1;4.2,10.2;#EBEBEB20]
114                 style[*;font=mono,bold]
115                 label[0,0.25;sound parameter table]
116                 style[*;font=mono]
117                 field[0.00,1;1,0.75;sparam.gain;gain;%s]
118                 field[1.25,1;1,0.75;sparam.pitch;pitch;%s]
119                 field[2.50,1;1,0.75;sparam.fade;fade;%s]
120                 field[0,2.25;4,0.75;sparam.loop;loop;%s]
121                 field[0,3.50;4,0.75;sparam.pos;pos;%s]
122                 field[0,4.75;4,0.75;sparam.object;object;%s]
123                 field[0,6.00;4,0.75;sparam.to_player;to_player;%s]
124                 field[0,7.25;4,0.75;sparam.exclude_player;exclude_player;%s]
125                 field[0,8.50;4,0.75;sparam.max_hear_distance;max_hear_distance;%s]
126                 container_end[]
127                 field_close_on_enter[sparam.gain;false]
128                 field_close_on_enter[sparam.pitch;false]
129                 field_close_on_enter[sparam.fade;false]
130                 field_close_on_enter[sparam.loop;false]
131                 field_close_on_enter[sparam.pos;false]
132                 field_close_on_enter[sparam.object;false]
133                 field_close_on_enter[sparam.to_player;false]
134                 field_close_on_enter[sparam.exclude_player;false]
135                 field_close_on_enter[sparam.max_hear_distance;false]
136                 tooltip[sparam.object;Get a name with the Branding Iron.]
137         ]], F(md.sparam.gain), F(md.sparam.pitch), F(md.sparam.fade), F(md.sparam.loop),
138                         F(md.sparam.pos), F(md.sparam.object), F(md.sparam.to_player),
139                         F(md.sparam.exclude_player), F(md.sparam.max_hear_distance)))
140
141         -- fade
142         fs_add(string.format([[
143                 container[10.75,3]
144                 box[-0.1,-0.1;3.2,3.2;#EBEBEB20]
145                 style[*;font=mono,bold]
146                 label[0,0.25;fade]
147                 style[*;font=mono]
148                 field[0.00,1;1,0.75;fade.step;step;%s]
149                 field[1.25,1;1,0.75;fade.gain;gain;%s]
150         ]], F(md.fade.step), F(md.fade.gain)))
151         if not sound_handle then
152                 fs_add([[
153                         box[0,2;3,0.75;#363636FF]
154                         label[0.25,2.375;no sound]
155                 ]])
156         else
157                 fs_add([[
158                         button[0,2;3,0.75;btn_fade;Fade]
159                 ]])
160         end
161         fs_add([[
162                 container_end[]
163                 field_close_on_enter[fade.step;false]
164                 field_close_on_enter[fade.gain;false]
165         ]])
166
167         -- ephemeral
168         fs_add(string.format([[
169                 checkbox[0.5,5;ephemeral;ephemeral;%s]
170         ]], md.ephemeral))
171
172         -- play/stop and release buttons
173         if not sound_handle then
174                 fs_add([[
175                         container[10.75,0.5]
176                         button[0,0;3,0.75;btn_play;Play]
177                         container_end[]
178                 ]])
179         else
180                 fs_add([[
181                         container[10.75,0.5]
182                         button[0,0;3,0.75;btn_stop;Stop]
183                         button[0,1;3,0.75;btn_release;Release]
184                         container_end[]
185                 ]])
186         end
187
188         -- save and quit button
189         fs_add([[
190                 button_exit[10.75,10;3,0.75;btn_save_quit;Save & Quit]
191         ]])
192
193         minetest.show_formspec(player:get_player_name(), "soundstuff:jukebox@"..pos:to_string(),
194                         table.concat(fs))
195 end
196
197 minetest.register_node("soundstuff:jukebox", {
198         description = "Jukebox\nAllows to play arbitrary sounds.",
199         tiles = {"soundstuff_jukebox.png"},
200         groups = {dig_immediate = 2},
201
202         on_construct = function(pos)
203                 local meta = minetest.get_meta(pos)
204                 -- SimpleSoundSpec
205                 meta:set_string("sss.name", "")
206                 meta:set_string("sss.gain", "")
207                 meta:set_string("sss.pitch", "")
208                 meta:set_string("sss.fade", "")
209                 -- sound parameters
210                 meta:set_string("sparam.gain", "")
211                 meta:set_string("sparam.pitch", "")
212                 meta:set_string("sparam.fade", "")
213                 meta:set_string("sparam.loop", "")
214                 meta:set_string("sparam.pos", pos:to_string())
215                 meta:set_string("sparam.object", "")
216                 meta:set_string("sparam.to_player", "")
217                 meta:set_string("sparam.exclude_player", "")
218                 meta:set_string("sparam.max_hear_distance", "")
219                 -- fade
220                 meta:set_string("fade.gain", "")
221                 meta:set_string("fade.step", "")
222                 -- other
223                 meta:set_string("ephemeral", "")
224
225                 meta:mark_as_private(meta_keys)
226         end,
227
228         on_rightclick = function(pos, _node, clicker, _itemstack, _pointed_thing)
229                 show_formspec(pos, clicker)
230         end,
231 })
232
233 minetest.register_on_player_receive_fields(function(player, formname, fields)
234         if formname:sub(1, 19) ~= "soundstuff:jukebox@" then
235                 return false
236         end
237
238         local pos = vector.from_string(formname, 20)
239         if not pos or pos ~= pos:round() then
240                 minetest.log("error", "[soundstuff:jukebox] Invalid formname.")
241                 return true
242         end
243
244         local meta = minetest.get_meta(pos)
245
246         for _, k in ipairs(meta_keys) do
247                 if fields[k] then
248                         meta:set_string(k, fields[k])
249                 end
250         end
251         meta:mark_as_private(meta_keys)
252
253         local pos_hash = minetest.hash_node_position(pos)
254         local sound_handle = played_sounds[pos_hash]
255
256         if not sound_handle then
257                 if fields.btn_play then
258                         local md = get_all_metadata(meta)
259
260                         local sss = {
261                                 name = md.sss.name,
262                                 gain  = tonumber(md.sss.gain),
263                                 pitch = tonumber(md.sss.pitch),
264                                 fade  = tonumber(md.sss.fade),
265                         }
266                         local sparam = {
267                                 gain  = tonumber(md.sparam.gain),
268                                 pitch = tonumber(md.sparam.pitch),
269                                 fade  = tonumber(md.sparam.fade),
270                                 loop = minetest.is_yes(md.sparam.loop),
271                                 pos = vector.from_string(md.sparam.pos),
272                                 object = testtools.get_branded_object(md.sparam.object),
273                                 to_player = md.sparam.to_player,
274                                 exclude_player = md.sparam.exclude_player,
275                                 max_hear_distance = tonumber(md.sparam.max_hear_distance),
276                         }
277                         local ephemeral = minetest.is_yes(md.ephemeral)
278
279                         log_msg(string.format(
280                                         "[soundstuff:jukebox] Playing sound: minetest.sound_play(%s, %s, %s)",
281                                         string.format("{name=\"%s\", gain=%s, pitch=%s, fade=%s}",
282                                                         sss.name, sss.gain, sss.pitch, sss.fade),
283                                         string.format("{gain=%s, pitch=%s, fade=%s, loop=%s, pos=%s, "
284                                                 .."object=%s, to_player=\"%s\", exclude_player=\"%s\", max_hear_distance=%s}",
285                                                         sparam.gain, sparam.pitch, sparam.fade, sparam.loop, sparam.pos,
286                                                         sparam.object and "<objref>", sparam.to_player, sparam.exclude_player,
287                                                         sparam.max_hear_distance),
288                                         tostring(ephemeral)))
289
290                         sound_handle = try_call(minetest.sound_play, sss, sparam, ephemeral)
291
292                         played_sounds[pos_hash] = sound_handle
293                         show_formspec(pos, player)
294                 end
295
296         else
297                 if fields.btn_stop then
298                         log_msg("[soundstuff:jukebox] Stopping sound: minetest.sound_stop(<handle>)")
299
300                         try_call(minetest.sound_stop, sound_handle)
301
302                 elseif fields.btn_release then
303                         log_msg("[soundstuff:jukebox] Releasing handle.")
304
305                         played_sounds[pos_hash] = nil
306                         show_formspec(pos, player)
307
308                 elseif fields.btn_fade then
309                         local md = get_all_metadata(meta)
310
311                         local step = tonumber(md.fade.step)
312                         local gain = tonumber(md.fade.gain)
313
314                         log_msg(string.format(
315                                         "[soundstuff:jukebox] Fading sound: minetest.sound_fade(<handle>, %s, %s)",
316                                         step, gain))
317
318                         try_call(minetest.sound_fade, sound_handle, step, gain)
319                 end
320         end
321
322         return true
323 end)