jukebox.lua 9.1 KB

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