The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
lua_audio.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 - 2025
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13*/
14
15#include "lua_audio.hpp"
16
17#include "log.hpp"
19#include "resources.hpp"
22#include "sound.hpp"
23#include "sound_music_track.hpp"
24#include "soundsource.hpp"
25#include <set>
26#include <utility>
27
28static lg::log_domain log_audio("audio");
29#define DBG_AUDIO LOG_STREAM(debug, log_audio)
30#define LOG_AUDIO LOG_STREAM(info, log_audio)
31#define ERR_AUDIO LOG_STREAM(err, log_audio)
32
33static const char* Track = "music track";
34static const char* Source = "sound source";
35
37 std::shared_ptr<sound::music_track> track;
38public:
39 explicit lua_music_track(std::shared_ptr<sound::music_track>&& new_track) : track(std::move(new_track)) {}
41 return *track;
42 }
44 return *track;
45 }
46 std::shared_ptr<sound::music_track> operator->() {
47 return track;
48 }
49 std::shared_ptr<const sound::music_track> operator->() const {
50 return track;
51 }
52};
53
54static lua_music_track* push_track(lua_State* L, std::shared_ptr<sound::music_track>&& new_track) {
55 if(new_track) {
56 lua_music_track* trk = new(L) lua_music_track(std::move(new_track));
57 luaL_setmetatable(L, Track);
58 return trk;
59 } else {
60 lua_pushnil(L);
61 return nullptr;
62 }
63}
64
65static lua_music_track* get_track(lua_State* L, int i) {
66 return static_cast<lua_music_track*>(luaL_checkudata(L, i, Track));
67}
68
87
88static lua_sound_source& push_source(lua_State* L, const soundsource::sourcespec& spec) {
90 luaL_setmetatable(L, Source);
91 return *src;
92}
93
94static lua_sound_source& get_source(lua_State* L, int i) {
95 return *static_cast<lua_sound_source*>(luaL_checkudata(L, i, Source));
96}
97
98/**
99 * Destroys a lua_music_track object before it is collected (__gc metamethod).
100 */
101static int impl_track_collect(lua_State* L)
102{
103 lua_music_track* u = get_track(L, 1);
104 u->lua_music_track::~lua_music_track();
105 return 0;
106}
107
108static int impl_music_get(lua_State* L) {
109 if(lua_isnumber(L, 2)) {
110 push_track(L, sound::get_track(lua_tointeger(L, 2) - 1));
111 return 1;
112 }
113 const char* m = luaL_checkstring(L, 2);
114
115 if(strcmp(m, "current") == 0) {
117 return 1;
118 }
119
120 if(strcmp(m, "previous") == 0) {
122 return 1;
123 }
124
125 if(strcmp(m, "current_i") == 0) {
126 auto current_index = sound::get_current_track_index();
127 if(current_index) {
128 lua_pushinteger(L, *current_index + 1);
129 } else {
130 lua_pushnil(L);
131 }
132 return 1;
133 }
134 if(strcmp(m, "all") == 0) {
135 config playlist;
137 const auto& range = playlist.child_range("music");
138 std::vector<config> tracks(range.begin(), range.end());
139 lua_push(L, tracks);
140 return 1;
141 }
142 // This calculation reverses the one used in [volume] to get back the relative volume level.
143 // (Which is the same calculation that's duplicated in impl_music_set.)
144 return_float_attrib("volume", (sound::get_music_volume() / prefs::get().music_volume()).as_percent());
145 return luaW_getmetafield(L, 1, m);
146}
147
148static int impl_music_set(lua_State* L) {
149 if(lua_isnumber(L, 2)) {
150 unsigned int i = lua_tointeger(L, 2) - 1;
151 config cfg;
152 if(lua_isnil(L, 3)) {
153 if(i < sound::get_num_tracks()) {
155 }
156 } else if(luaW_toconfig(L, 3, cfg)) {
157 // Don't clear the playlist
158 cfg["append"] = true;
159 // Don't allow play_once=yes
160 if(cfg["play_once"].to_bool()) {
161 return luaL_argerror(L, 3, "For play_once, use wesnoth.music_list.play instead");
162 }
163 if(i >= sound::get_num_tracks()) {
165 } else {
166 // Remove the track at that index and add the new one in its place
167 // It's a little inefficient though...
170 }
171 } else {
172 lua_music_track& track = *get_track(L, 3);
173 if(i < sound::get_num_tracks()) {
174 sound::set_track(i, track.operator->());
175 } else {
176 track->write(cfg, true);
178 }
179 }
180 return 0;
181 }
182 const char* m = luaL_checkstring(L, 2);
184 modify_int_attrib_check_range("current_i", sound::play_track(value - 1), 1, static_cast<int>(sound::get_num_tracks()));
185 return 0;
186}
187
188static int impl_music_len(lua_State* L) {
189 lua_pushinteger(L, sound::get_num_tracks());
190 return 1;
191}
192
193static int intf_music_play(lua_State* L) {
194 sound::play_music_once(luaL_checkstring(L, 1));
195 return 0;
196}
197
198static int intf_music_next(lua_State*) {
199 std::size_t n = sound::get_num_tracks();
200 if(n > 0) {
202 }
203 return 0;
204}
205
206static int intf_music_add(lua_State* L) {
207 int index = -1;
208 if(lua_isinteger(L, 1)) {
209 index = lua_tointeger(L, 1);
210 lua_remove(L, 1);
211 }
212 config cfg = config {
213 "name", luaL_checkstring(L, 1),
214 "append", true,
215 };
216 bool found_ms_before = false, found_ms_after = false, found_imm = false;
217 for(int i = 2; i <= lua_gettop(L); i++) {
218 if(lua_isboolean(L, i)) {
219 if(found_imm) {
220 return luaL_argerror(L, i, "only one boolean argument may be passed");
221 } else {
222 cfg["immediate"] = luaW_toboolean(L, i);
223 }
224 } else if(lua_isnumber(L, i)) {
225 if(found_ms_after) {
226 return luaL_argerror(L, i, "only two integer arguments may be passed");
227 } else if(found_ms_before) {
228 cfg["ms_after"] = lua_tointeger(L, i);
229 found_ms_after = true;
230 } else {
231 cfg["ms_before"] = lua_tointeger(L, i);
232 found_ms_before = true;
233 }
234 } else {
235 return luaL_argerror(L, i, "unrecognized argument");
236 }
237 }
238 sound::play_music_config(cfg, false, index);
239 return 0;
240}
241
242static int intf_music_clear(lua_State*) {
244 return 0;
245}
246
247static int intf_music_remove(lua_State* L) {
248 // Use a non-standard comparator to ensure iteration in descending order
249 std::set<int, std::greater<int>> to_remove;
250 for(int i = 1; i <= lua_gettop(L); i++) {
251 to_remove.insert(luaL_checkinteger(L, i));
252 }
253 for(int i : to_remove) {
255 }
256 return 0;
257}
258
259static int intf_music_commit(lua_State*) {
261 return 0;
262}
263
264static int impl_track_get(lua_State* L) {
265 lua_music_track* track = get_track(L, 1);
266 if(track == nullptr) {
267 return luaL_error(L, "Error: Attempted to access an invalid music track.\n");
268 }
269 const char* m = luaL_checkstring(L, 2);
270 return_bool_attrib("append", (*track)->append());
271 return_bool_attrib("shuffle", (*track)->shuffle());
272 return_bool_attrib("immediate", (*track)->immediate());
273 return_bool_attrib("once", (*track)->play_once());
274 return_int_attrib("ms_before", (*track)->ms_before().count());
275 return_int_attrib("ms_after", (*track)->ms_after().count());
276 return_string_attrib("name", (*track)->id());
277 return_string_attrib("title", (*track)->title());
278
279 return_cfg_attrib("__cfg",
280 cfg["append"]=(*track)->append();
281 cfg["shuffle"]=(*track)->shuffle();
282 cfg["immediate"]=(*track)->immediate();
283 cfg["once"]=(*track)->play_once();
284 cfg["ms_before"]=(*track)->ms_before();
285 cfg["ms_after"]=(*track)->ms_after();
286 cfg["name"]=(*track)->id();
287 cfg["title"]=(*track)->title());
288
289 return luaW_getmetafield(L, 1, m);
290}
291
292static int impl_track_set(lua_State* L) {
293 lua_music_track* track = get_track(L, 1);
294 if(track == nullptr) {
295 return luaL_error(L, "Error: Attempted to access an invalid music track.\n");
296 }
297 const char* m = luaL_checkstring(L, 2);
298 modify_bool_attrib("shuffle", (*track)->set_shuffle(value));
299 modify_bool_attrib("once", (*track)->set_play_once(value));
300 modify_int_attrib("ms_before", (*track)->set_ms_before(std::chrono::milliseconds{value}));
301 modify_int_attrib("ms_after", (*track)->set_ms_after(std::chrono::milliseconds{value}));
302 modify_string_attrib("title", (*track)->set_title(value));
303 return 0;
304}
305
306static int impl_track_eq(lua_State* L) {
307 lua_music_track* a = get_track(L, 1);
308 lua_music_track* b = get_track(L, 2);
309 if(!a || !b) {
310 // This implies that one argument is not a music track, though I suspect this is dead code...?
311 // Does Lua ever call this if the arguments are not of the same type?
312 lua_pushboolean(L, false);
313 return 1;
314 }
315 lua_music_track& lhs = *a;
316 lua_music_track& rhs = *b;
317 lua_pushboolean(L, lhs->id() == rhs->id() && lhs->shuffle() == rhs->shuffle() && lhs->play_once() == rhs->play_once() && lhs->ms_before() == rhs->ms_before() && lhs->ms_after() == rhs->ms_after());
318 return 1;
319}
320
321/**
322 * Get an existing sound source
323 * Key: The sound source ID
324 */
325static int impl_sndsrc_get(lua_State* L) {
327 return 0;
328 }
329 std::string id = luaL_checkstring(L, 2);
330 if(!resources::soundsources->contains(id)) {
331 return 0;
332 }
334 return 1;
335}
336
337/**
338 * Adds or removes a sound source by its ID
339 * Key: sound source ID
340 * Value: Table containing keyword arguments, existing sound source userdata, or nil to delete
341 */
342static int impl_sndsrc_set(lua_State* L) {
344 return 0;
345 }
346 std::string id = luaL_checkstring(L, 2);
347 config cfg;
348 if(lua_isnil(L, 3)) {
350 } else if(luaW_toconfig(L, 3, cfg)) {
351 cfg["id"] = id;
355 } else {
356 auto& src = get_source(L, 3);
359 }
360 return 0;
361}
362static int impl_source_collect(lua_State* L)
363{
364 lua_sound_source& u = get_source(L, 1);
365 u.lua_sound_source::~lua_sound_source();
366 return 0;
367}
368
369static int impl_source_get(lua_State* L) {
371 const char* m = luaL_checkstring(L, 2);
372 return_string_attrib("id", src->id());
373 return_vector_string_attrib("sounds", utils::split(src->files()));
374 return_int_attrib("delay", src->minimum_delay().count());
375 return_int_attrib("chance", src->chance());
376 return_int_attrib("loop", src->loops());
377 return_int_attrib("range", src->full_range());
378 return_int_attrib("fade_range", src->fade_range());
379 return_bool_attrib("check_fogged", src->check_fogged());
380 return_bool_attrib("check_shrouded", src->check_shrouded());
381 return_cfg_attrib("__cfg", src->write(cfg));
382
383 if(strcmp(m, "locations") == 0) {
384 const auto& locs = src->get_locations();
385 lua_createtable(L, locs.size(), 0);
386 for(const auto& loc : locs) {
388 lua_rawseti(L, -1, lua_rawlen(L, -2) + 1);
389 }
390 }
391
392 return luaW_getmetafield(L, 1, m);
393}
394
395static int impl_source_set(lua_State* L) {
397 const char* m = luaL_checkstring(L, 2);
398 modify_int_attrib("delay", src->set_minimum_delay(std::chrono::milliseconds{value}));
399 modify_int_attrib("chance", src->set_chance(value));
400 modify_int_attrib("loop", src->set_loops(value));
401 modify_int_attrib("range", src->set_full_range(value));
402 modify_int_attrib("fade_range", src->set_fade_range(value));
403 modify_bool_attrib("check_fogged", src->set_check_fogged(value));
404 modify_bool_attrib("check_shrouded", src->set_check_shrouded(value));
405
406 if(strcmp(m, "sounds") == 0) {
407 std::string files;
408 if(lua_istable(L, 3)) {
409 files = utils::join(lua_check<std::vector<std::string>>(L, 3));
410 } else {
411 files = luaL_checkstring(L, 3);
412 }
413 src->set_files(files);
414 }
415
416 if(strcmp(m, "locations") == 0) {
417 std::vector<map_location> locs;
418 locs.resize(1);
419 if(luaW_tolocation(L, 3, locs[0])) {
420
421 } else {
422 locs.clear();
423 for(lua_pushnil(L); lua_next(L, 3); lua_pop(L, 1)) {
424 locs.push_back(luaW_checklocation(L, -1));
425 }
426 }
427 src->set_locations(locs);
428 }
429
430 // Now apply the change
433 return 0;
434}
435
436static int impl_source_eq(lua_State* L) {
437 lua_sound_source& a = get_source(L, 1);
439 if(a->id() != b->id()) {
440 lua_pushboolean(L, false);
441 return 1;
442 }
443 lua_pushboolean(L,
444 a->files() == b->files() &&
445 a->minimum_delay() == b->minimum_delay() &&
446 a->chance() == b->chance() &&
447 a->loops() == b->loops() &&
448 a->full_range() == b->full_range() &&
449 a->fade_range() == b->fade_range() &&
450 a->check_fogged() == b->check_fogged() &&
451 a->check_shrouded() == b->check_shrouded() &&
452 std::set<map_location>(a->get_locations().begin(), a->get_locations().end()) == std::set<map_location>(b->get_locations().begin(), b->get_locations().end())
453 );
454 return 1;
455}
456
457/**
458 * Gets the current sound volume
459 * - Return: Current volume
460 */
461static int impl_audio_get(lua_State* L)
462{
463 std::string m = luaL_checkstring(L, 2);
464 if(m != "volume") return 0;
466 lua_pushnumber(L, (sound::get_sound_volume() / vol).as_percent());
467 return 1;
468}
469
470/**
471 * Sets the current sound volume
472 * - Arg: New volume to set
473 */
474static int impl_audio_set(lua_State* L)
475{
476 std::string m = luaL_checkstring(L, 2);
477 if(m != "volume") {
478 lua_rawset(L, 1);
479 return 0;
480 }
482 float rel = lua_tonumber(L, 3);
483 if(rel < 0.0f || rel > 100.0f) {
484 return luaL_argerror(L, 1, "volume must be in range 0..100");
485 }
487 return 0;
488}
489
490namespace lua_audio {
491 std::string register_table(lua_State* L) {
492 // Metatable to enable the volume attribute
493 luaW_getglobal(L, "wesnoth", "audio");
494 lua_createtable(L, 0, 2);
495 static luaL_Reg vol_callbacks[] {
496 { "__index", impl_audio_get },
497 { "__newindex", impl_audio_set },
498 { nullptr, nullptr },
499 };
500 luaL_setfuncs(L, vol_callbacks, 0);
501 lua_setmetatable(L, -2);
502
503 // The music playlist metatable
504 lua_newuserdatauv(L, 0, 0);
505 lua_createtable(L, 0, 10);
506 static luaL_Reg pl_callbacks[] {
507 { "__index", impl_music_get },
508 { "__newindex", impl_music_set },
509 { "__len", impl_music_len },
510 { "play", intf_music_play },
511 { "add", intf_music_add },
512 { "clear", intf_music_clear },
513 { "remove", intf_music_remove },
514 { "next", intf_music_next },
515 { "force_refresh", intf_music_commit },
516 { nullptr, nullptr },
517 };
518 luaL_setfuncs(L, pl_callbacks, 0);
519 lua_pushstring(L, "music playlist");
520 lua_setfield(L, -2, "__metatable");
521 lua_setmetatable(L, -2);
522 lua_setfield(L, -2, "music_list");
523
524 // The sound source map metatable
525 lua_newuserdatauv(L, 0, 0);
526 lua_createtable(L, 0, 3);
527 static luaL_Reg slm_callbacks[] {
528 { "__index", impl_sndsrc_get },
529 { "__newindex", impl_sndsrc_set },
530 { nullptr, nullptr },
531 };
532 luaL_setfuncs(L, slm_callbacks, 0);
533 lua_pushstring(L, "sound source map");
534 lua_setfield(L, -2, "__metatable");
535 lua_setmetatable(L, -2);
536 lua_setfield(L, -2, "sources");
537 lua_pop(L, 1);
538
539 // The music track metatable
540 luaL_newmetatable(L, Track);
541 static luaL_Reg track_callbacks[] {
542 {"__gc", impl_track_collect},
543 { "__index", impl_track_get },
544 { "__newindex", impl_track_set },
545 { "__eq", impl_track_eq },
546 { nullptr, nullptr },
547 };
548 luaL_setfuncs(L, track_callbacks, 0);
549 lua_pushstring(L, Track);
550 lua_setfield(L, -2, "__metatable");
551 lua_pop(L, 1);
552
553 // The sound source metatable
554 luaL_newmetatable(L, Source);
555 static luaL_Reg source_callbacks[] {
556 {"__gc", impl_source_collect},
557 { "__index", impl_source_get },
558 { "__newindex", impl_source_set },
559 { "__eq", impl_source_eq },
560 { nullptr, nullptr },
561 };
562 luaL_setfuncs(L, source_callbacks, 0);
563 lua_pushstring(L, Source);
564 lua_setfield(L, -2, "__metatable");
565
566 return "Adding music playlist table...\n";
567 }
568}
map_location loc
Definition move.cpp:172
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void append(const config &cfg)
Append data from another config object to this one.
Definition config.cpp:188
child_itors child_range(std::string_view key)
Definition config.cpp:268
sound::music_track & operator*()
Definition lua_audio.cpp:40
lua_music_track(std::shared_ptr< sound::music_track > &&new_track)
Definition lua_audio.cpp:39
const sound::music_track & operator*() const
Definition lua_audio.cpp:43
std::shared_ptr< sound::music_track > operator->()
Definition lua_audio.cpp:46
std::shared_ptr< const sound::music_track > operator->() const
Definition lua_audio.cpp:49
std::shared_ptr< sound::music_track > track
Definition lua_audio.cpp:37
const soundsource::sourcespec & operator*() const
Definition lua_audio.cpp:77
lua_sound_source(lua_sound_source &)=delete
soundsource::sourcespec * operator->()
Definition lua_audio.cpp:80
soundsource::sourcespec spec
Definition lua_audio.cpp:70
const soundsource::sourcespec * operator->() const
Definition lua_audio.cpp:83
lua_sound_source(const soundsource::sourcespec &spec)
Definition lua_audio.cpp:72
soundsource::sourcespec & operator*()
Definition lua_audio.cpp:74
static prefs & get()
sound::volume sound_volume()
Internal representation of music tracks.
static constexpr volume from_percent(float percentage)
Definition sound.hpp:121
void add(const sourcespec &source)
void remove(const std::string &id)
Sound source info class.
const std::string & id() const
const std::vector< map_location > & get_locations() const
const std::string & files() const
const config * cfg
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
Standard logging facilities (interface).
std::decay_t< T > lua_check(lua_State *L, int n)
static int intf_music_add(lua_State *L)
static int impl_audio_set(lua_State *L)
Sets the current sound volume.
static int impl_source_collect(lua_State *L)
static const char * Track
Definition lua_audio.cpp:33
static int impl_music_len(lua_State *L)
static int impl_track_set(lua_State *L)
static int intf_music_commit(lua_State *)
static int impl_music_get(lua_State *L)
static int intf_music_remove(lua_State *L)
static lua_music_track * get_track(lua_State *L, int i)
Definition lua_audio.cpp:65
static lua_sound_source & get_source(lua_State *L, int i)
Definition lua_audio.cpp:94
static int impl_audio_get(lua_State *L)
Gets the current sound volume.
static lua_sound_source & push_source(lua_State *L, const soundsource::sourcespec &spec)
Definition lua_audio.cpp:88
static lg::log_domain log_audio("audio")
static int impl_track_get(lua_State *L)
static int impl_source_eq(lua_State *L)
static lua_music_track * push_track(lua_State *L, std::shared_ptr< sound::music_track > &&new_track)
Definition lua_audio.cpp:54
static int impl_source_set(lua_State *L)
static int intf_music_next(lua_State *)
static int intf_music_play(lua_State *L)
static int impl_track_eq(lua_State *L)
static int impl_source_get(lua_State *L)
static int impl_sndsrc_set(lua_State *L)
Adds or removes a sound source by its ID Key: sound source ID Value: Table containing keyword argumen...
static const char * Source
Definition lua_audio.cpp:34
static int impl_sndsrc_get(lua_State *L)
Get an existing sound source Key: The sound source ID.
static int impl_track_collect(lua_State *L)
Destroys a lua_music_track object before it is collected (__gc metamethod).
static int impl_music_set(lua_State *L)
static int intf_music_clear(lua_State *)
void luaW_pushlocation(lua_State *L, const map_location &ml)
Converts a map location object to a Lua table pushed at the top of the stack.
bool luaW_toboolean(lua_State *L, int n)
bool luaW_getmetafield(lua_State *L, int idx, const char *key)
Like luaL_getmetafield, but returns false if key is an empty string or begins with two underscores.
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
bool luaW_tolocation(lua_State *L, int index, map_location &loc)
Converts an optional table or pair of integers to a map location object.
map_location luaW_checklocation(lua_State *L, int index)
Converts an optional table or pair of integers to a map location object.
bool luaW_getglobal(lua_State *L, const std::vector< std::string > &path)
Pushes the value found by following the variadic names (char *), if the value is not nil.
#define return_float_attrib(name, accessor)
#define return_vector_string_attrib(name, accessor)
#define return_string_attrib(name, accessor)
#define return_int_attrib(name, accessor)
#define modify_bool_attrib(name, accessor)
#define return_bool_attrib(name, accessor)
#define modify_int_attrib(name, accessor)
#define return_cfg_attrib(name, accessor)
#define modify_float_attrib_check_range(name, accessor, allowed_min, allowed_max)
#define modify_string_attrib(name, accessor)
#define modify_int_attrib_check_range(name, accessor, allowed_min, allowed_max)
std::string register_table(lua_State *L)
soundsource::manager * soundsources
Definition resources.cpp:27
void write_music_play_list(config &snapshot)
Definition sound.cpp:823
void empty_playlist()
Definition sound.cpp:608
void set_music_volume(volume vol)
Definition sound.cpp:1025
volume get_music_volume()
Definition sound.cpp:1016
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition sound.cpp:687
void remove_track(unsigned int i)
Definition sound.cpp:254
utils::optional< std::size_t > get_current_track_index()
Definition sound.cpp:199
unsigned int get_num_tracks()
Definition sound.cpp:229
void set_sound_volume(volume vol)
Definition sound.cpp:1042
void play_music_once(const std::string &file)
Definition sound.cpp:599
void set_track(unsigned int i, const std::shared_ptr< music_track > &to)
Definition sound.cpp:247
volume get_sound_volume()
Definition sound.cpp:1032
void commit_music_changes()
Definition sound.cpp:795
void play_track(unsigned int i)
Definition sound.cpp:625
std::shared_ptr< music_track > get_previous_music_track()
Definition sound.cpp:224
std::shared_ptr< music_track > get_track(unsigned int i)
Definition sound.cpp:234
std::shared_ptr< music_track > get_current_track()
Definition sound.cpp:214
STL namespace.
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
void lua_push(lua_State *L, const T &val)
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
rect src
Non-transparent portion of the surface to compose.
static map_location::direction n
#define i
#define b