1 local F = minetest.formspec_escape
3 -- hashed node pos -> sound handle
4 local played_sounds = {}
6 -- all of these can be set via the formspec
21 "sparam.exclude_player",
22 "sparam.max_hear_distance",
30 local function get_all_metadata(meta)
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"),
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"),
50 gain = meta:get_string("fade.gain"),
51 step = meta:get_string("fade.step"),
53 ephemeral = meta:get_string("ephemeral"),
57 local function log_msg(msg)
58 minetest.log("action", msg)
59 minetest.chat_send_all(msg)
62 local function try_call(f, ...)
63 local function log_on_err(success, errmsg, ...)
65 log_msg("[soundstuff:jukebox] Call failed: "..errmsg)
71 return log_on_err(pcall(f, ...))
74 local function show_formspec(pos, player)
75 local meta = minetest.get_meta(pos)
77 local md = get_all_metadata(meta)
79 local pos_hash = minetest.hash_node_position(pos)
80 local sound_handle = played_sounds[pos_hash]
83 local function fs_add(str)
93 fs_add(string.format([[
95 box[-0.1,-0.1;4.2,3.2;#EBEBEB20]
96 style[*;font=mono,bold]
97 label[0,0.25;SimpleSoundSpec]
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]
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)))
110 -- sound parameter table
111 fs_add(string.format([[
113 box[-0.1,-0.1;4.2,10.2;#EBEBEB20]
114 style[*;font=mono,bold]
115 label[0,0.25;sound parameter table]
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]
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)))
142 fs_add(string.format([[
144 box[-0.1,-0.1;3.2,3.2;#EBEBEB20]
145 style[*;font=mono,bold]
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
153 box[0,2;3,0.75;#363636FF]
154 label[0.25,2.375;no sound]
158 button[0,2;3,0.75;btn_fade;Fade]
163 field_close_on_enter[fade.step;false]
164 field_close_on_enter[fade.gain;false]
168 fs_add(string.format([[
169 checkbox[0.5,5;ephemeral;ephemeral;%s]
172 -- play/stop and release buttons
173 if not sound_handle then
176 button[0,0;3,0.75;btn_play;Play]
182 button[0,0;3,0.75;btn_stop;Stop]
183 button[0,1;3,0.75;btn_release;Release]
188 -- save and quit button
190 button_exit[10.75,10;3,0.75;btn_save_quit;Save & Quit]
193 minetest.show_formspec(player:get_player_name(), "soundstuff:jukebox@"..pos:to_string(),
197 minetest.register_node("soundstuff:jukebox", {
198 description = "Jukebox\nAllows to play arbitrary sounds.",
199 tiles = {"soundstuff_jukebox.png"},
200 groups = {dig_immediate = 2},
202 on_construct = function(pos)
203 local meta = minetest.get_meta(pos)
205 meta:set_string("sss.name", "")
206 meta:set_string("sss.gain", "")
207 meta:set_string("sss.pitch", "")
208 meta:set_string("sss.fade", "")
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", "")
220 meta:set_string("fade.gain", "")
221 meta:set_string("fade.step", "")
223 meta:set_string("ephemeral", "")
225 meta:mark_as_private(meta_keys)
228 on_rightclick = function(pos, _node, clicker, _itemstack, _pointed_thing)
229 show_formspec(pos, clicker)
233 minetest.register_on_player_receive_fields(function(player, formname, fields)
234 if formname:sub(1, 19) ~= "soundstuff:jukebox@" then
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.")
244 local meta = minetest.get_meta(pos)
246 for _, k in ipairs(meta_keys) do
248 meta:set_string(k, fields[k])
251 meta:mark_as_private(meta_keys)
253 local pos_hash = minetest.hash_node_position(pos)
254 local sound_handle = played_sounds[pos_hash]
256 if not sound_handle then
257 if fields.btn_play then
258 local md = get_all_metadata(meta)
262 gain = tonumber(md.sss.gain),
263 pitch = tonumber(md.sss.pitch),
264 fade = tonumber(md.sss.fade),
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),
277 local ephemeral = minetest.is_yes(md.ephemeral)
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)))
290 sound_handle = try_call(minetest.sound_play, sss, sparam, ephemeral)
292 played_sounds[pos_hash] = sound_handle
293 show_formspec(pos, player)
297 if fields.btn_stop then
298 log_msg("[soundstuff:jukebox] Stopping sound: minetest.sound_stop(<handle>)")
300 try_call(minetest.sound_stop, sound_handle)
302 elseif fields.btn_release then
303 log_msg("[soundstuff:jukebox] Releasing handle.")
305 played_sounds[pos_hash] = nil
306 show_formspec(pos, player)
308 elseif fields.btn_fade then
309 local md = get_all_metadata(meta)
311 local step = tonumber(md.fade.step)
312 local gain = tonumber(md.fade.gain)
314 log_msg(string.format(
315 "[soundstuff:jukebox] Fading sound: minetest.sound_fade(<handle>, %s, %s)",
318 try_call(minetest.sound_fade, sound_handle, step, gain)