1 | /** |
2 | * Copyright (c) 2006-2023 LOVE Development Team |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * |
8 | * Permission is granted to anyone to use this software for any purpose, |
9 | * including commercial applications, and to alter it and redistribute it |
10 | * freely, subject to the following restrictions: |
11 | * |
12 | * 1. The origin of this software must not be misrepresented; you must not |
13 | * claim that you wrote the original software. If you use this software |
14 | * in a product, an acknowledgment in the product documentation would be |
15 | * appreciated but is not required. |
16 | * 2. Altered source versions must be plainly marked as such, and must not be |
17 | * misrepresented as being the original software. |
18 | * 3. This notice may not be removed or altered from any source distribution. |
19 | **/ |
20 | |
21 | // LOVE |
22 | #include "wrap_Audio.h" |
23 | |
24 | #include "openal/Audio.h" |
25 | #include "null/Audio.h" |
26 | |
27 | #include "common/runtime.h" |
28 | |
29 | // C++ |
30 | #include <iostream> |
31 | #include <cmath> |
32 | |
33 | namespace love |
34 | { |
35 | namespace audio |
36 | { |
37 | |
38 | #define instance() (Module::getInstance<Audio>(Module::M_AUDIO)) |
39 | |
40 | int w_getActiveSourceCount(lua_State *L) |
41 | { |
42 | lua_pushinteger(L, instance()->getActiveSourceCount()); |
43 | return 1; |
44 | } |
45 | |
46 | int w_newSource(lua_State *L) |
47 | { |
48 | Source::Type stype = Source::TYPE_STREAM; |
49 | |
50 | if (!luax_istype(L, 1, love::sound::SoundData::type) && !luax_istype(L, 1, love::sound::Decoder::type)) |
51 | { |
52 | const char *stypestr = luaL_checkstring(L, 2); |
53 | if (stypestr && !Source::getConstant(stypestr, stype)) |
54 | return luax_enumerror(L, "source type" , Source::getConstants(stype), stypestr); |
55 | |
56 | if (stype == Source::TYPE_QUEUE) |
57 | return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead." ); |
58 | } |
59 | |
60 | if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type)) |
61 | luax_convobj(L, 1, "sound" , "newDecoder" ); |
62 | |
63 | if (stype == Source::TYPE_STATIC && luax_istype(L, 1, love::sound::Decoder::type)) |
64 | luax_convobj(L, 1, "sound" , "newSoundData" ); |
65 | |
66 | Source *t = nullptr; |
67 | |
68 | luax_catchexcept(L, [&]() { |
69 | if (luax_istype(L, 1, love::sound::SoundData::type)) |
70 | t = instance()->newSource(luax_totype<love::sound::SoundData>(L, 1)); |
71 | else if (luax_istype(L, 1, love::sound::Decoder::type)) |
72 | t = instance()->newSource(luax_totype<love::sound::Decoder>(L, 1)); |
73 | }); |
74 | |
75 | if (t != nullptr) |
76 | { |
77 | luax_pushtype(L, t); |
78 | t->release(); |
79 | return 1; |
80 | } |
81 | else |
82 | return luax_typerror(L, 1, "Decoder or SoundData" ); |
83 | } |
84 | |
85 | int w_newQueueableSource(lua_State *L) |
86 | { |
87 | Source *t = nullptr; |
88 | |
89 | luax_catchexcept(L, [&]() { |
90 | t = instance()->newSource((int)luaL_checkinteger(L, 1), (int)luaL_checkinteger(L, 2), (int)luaL_checkinteger(L, 3), (int)luaL_optinteger(L, 4, 0)); |
91 | }); |
92 | |
93 | if (t != nullptr) |
94 | { |
95 | luax_pushtype(L, t); |
96 | t->release(); |
97 | return 1; |
98 | } |
99 | else |
100 | return 0; //all argument type errors are checked in above constructor |
101 | } |
102 | |
103 | static std::vector<Source*> readSourceList(lua_State *L, int n) |
104 | { |
105 | if (n < 0) |
106 | n += lua_gettop(L) + 1; |
107 | |
108 | int items = (int) luax_objlen(L, n); |
109 | std::vector<Source*> sources(items); |
110 | |
111 | for (int i = 0; i < items; i++) |
112 | { |
113 | lua_rawgeti(L, n, i+1); |
114 | sources[i] = luax_checksource(L, -1); |
115 | lua_pop(L, 1); |
116 | } |
117 | |
118 | return sources; |
119 | } |
120 | |
121 | static std::vector<Source*> readSourceVararg(lua_State *L, int i) |
122 | { |
123 | const int top = lua_gettop(L); |
124 | |
125 | if (i < 0) |
126 | i += top + 1; |
127 | |
128 | int items = top - i + 1; |
129 | std::vector<Source*> sources(items); |
130 | |
131 | for (int pos = 0; i <= top; i++, pos++) |
132 | sources[pos] = luax_checksource(L, i); |
133 | |
134 | return sources; |
135 | } |
136 | |
137 | int w_play(lua_State *L) |
138 | { |
139 | if (lua_istable(L, 1)) |
140 | luax_pushboolean(L, instance()->play(readSourceList(L, 1))); |
141 | else if (lua_gettop(L) > 1) |
142 | luax_pushboolean(L, instance()->play(readSourceVararg(L, 1))); |
143 | else |
144 | { |
145 | Source *s = luax_checksource(L, 1); |
146 | luax_pushboolean(L, instance()->play(s)); |
147 | } |
148 | |
149 | return 1; |
150 | } |
151 | |
152 | int w_stop(lua_State *L) |
153 | { |
154 | if (lua_isnone(L, 1)) |
155 | instance()->stop(); |
156 | else if (lua_istable(L, 1)) |
157 | instance()->stop(readSourceList(L, 1)); |
158 | else if (lua_gettop(L) > 1) |
159 | instance()->stop(readSourceVararg(L, 1)); |
160 | else |
161 | { |
162 | Source *s = luax_checksource(L, 1); |
163 | s->stop(); |
164 | } |
165 | return 0; |
166 | } |
167 | |
168 | int w_pause(lua_State *L) |
169 | { |
170 | if (lua_isnone(L, 1)) |
171 | { |
172 | auto sources = instance()->pause(); |
173 | |
174 | lua_createtable(L, (int) sources.size(), 0); |
175 | for (int i = 0; i < (int) sources.size(); i++) |
176 | { |
177 | luax_pushtype(L, sources[i]); |
178 | lua_rawseti(L, -2, i+1); |
179 | } |
180 | return 1; |
181 | } |
182 | else if (lua_istable(L, 1)) |
183 | instance()->pause(readSourceList(L, 1)); |
184 | else if (lua_gettop(L) > 1) |
185 | instance()->pause(readSourceVararg(L, 1)); |
186 | else |
187 | { |
188 | Source *s = luax_checksource(L, 1); |
189 | s->pause(); |
190 | } |
191 | |
192 | return 0; |
193 | } |
194 | |
195 | int w_setVolume(lua_State *L) |
196 | { |
197 | float v = (float)luaL_checknumber(L, 1); |
198 | instance()->setVolume(v); |
199 | return 0; |
200 | } |
201 | |
202 | int w_getVolume(lua_State *L) |
203 | { |
204 | lua_pushnumber(L, instance()->getVolume()); |
205 | return 1; |
206 | } |
207 | |
208 | int w_setPosition(lua_State *L) |
209 | { |
210 | float v[3]; |
211 | v[0] = (float)luaL_checknumber(L, 1); |
212 | v[1] = (float)luaL_checknumber(L, 2); |
213 | v[2] = (float)luaL_optnumber(L, 3, 0); |
214 | instance()->setPosition(v); |
215 | return 0; |
216 | } |
217 | |
218 | int w_getPosition(lua_State *L) |
219 | { |
220 | float v[3]; |
221 | instance()->getPosition(v); |
222 | lua_pushnumber(L, v[0]); |
223 | lua_pushnumber(L, v[1]); |
224 | lua_pushnumber(L, v[2]); |
225 | return 3; |
226 | } |
227 | |
228 | int w_setOrientation(lua_State *L) |
229 | { |
230 | float v[6]; |
231 | v[0] = (float)luaL_checknumber(L, 1); |
232 | v[1] = (float)luaL_checknumber(L, 2); |
233 | v[2] = (float)luaL_checknumber(L, 3); |
234 | v[3] = (float)luaL_checknumber(L, 4); |
235 | v[4] = (float)luaL_checknumber(L, 5); |
236 | v[5] = (float)luaL_checknumber(L, 6); |
237 | instance()->setOrientation(v); |
238 | return 0; |
239 | } |
240 | |
241 | int w_getOrientation(lua_State *L) |
242 | { |
243 | float v[6]; |
244 | instance()->getOrientation(v); |
245 | lua_pushnumber(L, v[0]); |
246 | lua_pushnumber(L, v[1]); |
247 | lua_pushnumber(L, v[2]); |
248 | lua_pushnumber(L, v[3]); |
249 | lua_pushnumber(L, v[4]); |
250 | lua_pushnumber(L, v[5]); |
251 | return 6; |
252 | } |
253 | |
254 | int w_setVelocity(lua_State *L) |
255 | { |
256 | float v[3]; |
257 | v[0] = (float)luaL_checknumber(L, 1); |
258 | v[1] = (float)luaL_checknumber(L, 2); |
259 | v[2] = (float)luaL_optnumber(L, 3, 0); |
260 | instance()->setVelocity(v); |
261 | return 0; |
262 | } |
263 | |
264 | int w_getVelocity(lua_State *L) |
265 | { |
266 | float v[3]; |
267 | instance()->getVelocity(v); |
268 | lua_pushnumber(L, v[0]); |
269 | lua_pushnumber(L, v[1]); |
270 | lua_pushnumber(L, v[2]); |
271 | return 3; |
272 | } |
273 | |
274 | int w_setDopplerScale(lua_State *L) |
275 | { |
276 | instance()->setDopplerScale(luax_checkfloat(L, 1)); |
277 | return 0; |
278 | } |
279 | |
280 | int w_getDopplerScale(lua_State *L) |
281 | { |
282 | lua_pushnumber(L, instance()->getDopplerScale()); |
283 | return 1; |
284 | } |
285 | /* |
286 | int w_setMeter(lua_State *L) |
287 | { |
288 | instance()->setMeter(luax_checkfloat(L, 1)); |
289 | return 0; |
290 | } |
291 | |
292 | int w_getMeter(lua_State *L) |
293 | { |
294 | lua_pushnumber(L, instance()->getMeter()); |
295 | return 1; |
296 | } |
297 | */ |
298 | int w_setDistanceModel(lua_State *L) |
299 | { |
300 | const char *modelStr = luaL_checkstring(L, 1); |
301 | Audio::DistanceModel distanceModel; |
302 | if (!Audio::getConstant(modelStr, distanceModel)) |
303 | return luax_enumerror(L, "distance model" , Audio::getConstants(distanceModel), modelStr); |
304 | instance()->setDistanceModel(distanceModel); |
305 | return 0; |
306 | } |
307 | |
308 | int w_getDistanceModel(lua_State *L) |
309 | { |
310 | Audio::DistanceModel distanceModel = instance()->getDistanceModel(); |
311 | const char *modelStr; |
312 | if (!Audio::getConstant(distanceModel, modelStr)) |
313 | return 0; |
314 | lua_pushstring(L, modelStr); |
315 | return 1; |
316 | } |
317 | |
318 | int w_getRecordingDevices(lua_State *L) |
319 | { |
320 | const std::vector<RecordingDevice*> &devices = instance()->getRecordingDevices(); |
321 | |
322 | lua_createtable(L, devices.size(), 0); |
323 | |
324 | for (unsigned int i = 0; i < devices.size(); i++) |
325 | { |
326 | luax_pushtype(L, devices[i]); |
327 | lua_rawseti(L, -2, i + 1); |
328 | } |
329 | |
330 | return 1; |
331 | } |
332 | |
333 | int w_setEffect(lua_State *L) |
334 | { |
335 | const char *namestr = luaL_checkstring(L, 1); |
336 | |
337 | if (lua_isnoneornil(L, 2) || (lua_gettop(L) == 2 && lua_isboolean(L, 2) && !lua_toboolean(L, 2))) |
338 | { |
339 | lua_pushboolean(L, instance()->unsetEffect(namestr)); |
340 | return 1; |
341 | } |
342 | |
343 | luaL_checktype(L, 2, LUA_TTABLE); |
344 | |
345 | const char *paramstr = nullptr; |
346 | |
347 | //find type (mandatory) |
348 | Effect::getConstant(Effect::EFFECT_TYPE, paramstr, Effect::TYPE_BASIC); |
349 | lua_pushstring(L, paramstr); |
350 | lua_rawget(L, 2); |
351 | if (lua_type(L, -1) == LUA_TNIL) |
352 | return luaL_error(L, "Effect type not specificed." ); |
353 | |
354 | Effect::Type type = Effect::TYPE_MAX_ENUM; |
355 | const char *typestr = luaL_checkstring(L, -1); |
356 | if (!Effect::getConstant(typestr, type)) |
357 | return luax_enumerror(L, "effect type" , Effect::getConstants(type), typestr); |
358 | |
359 | lua_pop(L, 1); |
360 | std::map<Effect::Parameter, float> params; |
361 | params[Effect::EFFECT_TYPE] = static_cast<int>(type); |
362 | |
363 | // Iterate over the whole table, reading valid parameters and erroring on invalid ones |
364 | lua_pushnil(L); |
365 | while (lua_next(L, 2)) |
366 | { |
367 | const char *keystr = luaL_checkstring(L, -2); |
368 | Effect::Parameter param; |
369 | |
370 | if(Effect::getConstant(keystr, param, type) || Effect::getConstant(keystr, param, Effect::TYPE_BASIC)) |
371 | { |
372 | #define luax_effecterror(l,t) luaL_error(l,"Bad parameter type for %s %s: " t " expected, got %s", typestr, keystr, lua_typename(L, -1)) |
373 | switch(Effect::getParameterType(param)) |
374 | { |
375 | case Effect::PARAM_FLOAT: |
376 | if (!lua_isnumber(L, -1)) |
377 | return luax_effecterror(L, "number" ); |
378 | params[param] = lua_tonumber(L, -1); |
379 | break; |
380 | case Effect::PARAM_BOOL: |
381 | if (!lua_isboolean(L, -1)) |
382 | return luax_effecterror(L, "boolean" ); |
383 | params[param] = lua_toboolean(L, -1) ? 1.0 : 0.0; |
384 | break; |
385 | case Effect::PARAM_WAVEFORM: |
386 | { |
387 | if (!lua_isstring(L, -1)) |
388 | return luax_effecterror(L, "string" ); |
389 | paramstr = lua_tostring(L, -1); |
390 | Effect::Waveform waveform; |
391 | if (!Effect::getConstant(paramstr, waveform)) |
392 | return luax_enumerror(L, "waveform type" , paramstr); |
393 | params[param] = static_cast<int>(waveform); |
394 | break; |
395 | } |
396 | /* |
397 | case Effect::PARAM_DIRECTION: |
398 | { |
399 | if (!lua_isstring(L, -1)) |
400 | return luax_effecterror(L, "string"); |
401 | paramstr = lua_tostring(L, -1); |
402 | Effect::Direction direction; |
403 | if (!Effect::getConstant(paramstr, direction)) |
404 | return luaL_error(L, "Invalid direction type: %s", paramstr); |
405 | params[param] = static_cast<int>(direction); |
406 | break; |
407 | } |
408 | case Effect::PARAM_PHONEME: |
409 | { |
410 | if (!lua_isstring(L, -1)) |
411 | return luax_effecterror(L, "string"); |
412 | paramstr = lua_tostring(L, -1); |
413 | Effect::Phoneme phoneme; |
414 | if (!Effect::getConstant(basicstr, phoneme)) |
415 | return luaL_error(L, "Invalid phoneme type: %s", paramstr); |
416 | params[param] = static_cast<int>(phoneme); |
417 | break; |
418 | } |
419 | */ |
420 | case Effect::PARAM_TYPE: |
421 | case Effect::PARAM_MAX_ENUM: |
422 | break; |
423 | } |
424 | #undef luax_effecterror |
425 | } |
426 | else |
427 | luaL_error(L, "Invalid '%s' Effect parameter: %s" , typestr, keystr); |
428 | |
429 | //remove the value (-1) from stack, keep the key (-2) to feed into lua_next |
430 | lua_pop(L, 1); |
431 | } |
432 | |
433 | luax_catchexcept(L, [&]() { lua_pushboolean(L, instance()->setEffect(namestr, params)); }); |
434 | return 1; |
435 | } |
436 | |
437 | int w_getEffect(lua_State *L) |
438 | { |
439 | const char *namestr = luaL_checkstring(L, 1); |
440 | |
441 | std::map<Effect::Parameter, float> params; |
442 | |
443 | if (!instance()->getEffect(namestr, params)) |
444 | return 0; |
445 | |
446 | const char *keystr, *valstr; |
447 | Effect::Type type = static_cast<Effect::Type>((int)params[Effect::EFFECT_TYPE]); |
448 | |
449 | if (lua_istable(L, 2)) |
450 | lua_pushvalue(L, 2); |
451 | else |
452 | lua_createtable(L, 0, params.size()); |
453 | |
454 | for (auto p : params) |
455 | { |
456 | if (!Effect::getConstant(p.first, keystr, type)) |
457 | Effect::getConstant(p.first, keystr, Effect::TYPE_BASIC); |
458 | |
459 | lua_pushstring(L, keystr); |
460 | switch (Effect::getParameterType(p.first)) |
461 | { |
462 | case Effect::PARAM_FLOAT: |
463 | lua_pushnumber(L, p.second); |
464 | break; |
465 | case Effect::PARAM_BOOL: |
466 | lua_pushboolean(L, p.second > 0.5 ? true : false); |
467 | break; |
468 | case Effect::PARAM_WAVEFORM: |
469 | Effect::getConstant(static_cast<Effect::Waveform>((int)p.second), valstr); |
470 | lua_pushstring(L, valstr); |
471 | break; |
472 | /* |
473 | case Effect::PARAM_DIRECTION: |
474 | Effect::getConstant(static_cast<Effect::Direction>((int)p.second), valstr); |
475 | lua_pushstring(L, valstr); |
476 | break; |
477 | case Effect::PARAM_PHONEME: |
478 | Effect::getConstant(static_cast<Effect::Phoneme>((int)p.second), valstr); |
479 | lua_pushstring(L, valstr); |
480 | break; |
481 | */ |
482 | case Effect::PARAM_TYPE: |
483 | Effect::getConstant(static_cast<Effect::Type>((int)p.second), valstr); |
484 | lua_pushstring(L, valstr); |
485 | break; |
486 | case Effect::PARAM_MAX_ENUM: |
487 | break; |
488 | } |
489 | lua_rawset(L, -3); |
490 | } |
491 | return 1; |
492 | } |
493 | |
494 | int w_getActiveEffects(lua_State *L) |
495 | { |
496 | std::vector<std::string> list; |
497 | instance()->getActiveEffects(list); |
498 | |
499 | lua_createtable(L, 0, (int) list.size()); |
500 | for (int i = 0; i < (int) list.size(); i++) |
501 | { |
502 | lua_pushnumber(L, i + 1); |
503 | lua_pushstring(L, list[i].c_str()); |
504 | lua_rawset(L, -3); |
505 | } |
506 | return 1; |
507 | } |
508 | |
509 | int w_getMaxSceneEffects(lua_State *L) |
510 | { |
511 | lua_pushnumber(L, instance()->getMaxSceneEffects()); |
512 | return 1; |
513 | } |
514 | |
515 | int w_getMaxSourceEffects(lua_State *L) |
516 | { |
517 | lua_pushnumber(L, instance()->getMaxSourceEffects()); |
518 | return 1; |
519 | } |
520 | |
521 | int w_isEffectsSupported(lua_State *L) |
522 | { |
523 | lua_pushboolean(L, instance()->isEFXsupported()); |
524 | return 1; |
525 | } |
526 | |
527 | int w_setMixWithSystem(lua_State *L) |
528 | { |
529 | luax_pushboolean(L, Audio::setMixWithSystem(luax_checkboolean(L, 1))); |
530 | return 1; |
531 | } |
532 | |
533 | int w_getSourceCount(lua_State *L) |
534 | { |
535 | luax_markdeprecated(L, "love.audio.getSourceCount" , API_FUNCTION, DEPRECATED_RENAMED, "love.audio.getActiveSourceCount" ); |
536 | return w_getActiveSourceCount(L); |
537 | } |
538 | |
539 | // List of functions to wrap. |
540 | static const luaL_Reg functions[] = |
541 | { |
542 | { "getActiveSourceCount" , w_getActiveSourceCount }, |
543 | { "newSource" , w_newSource }, |
544 | { "newQueueableSource" , w_newQueueableSource }, |
545 | { "play" , w_play }, |
546 | { "stop" , w_stop }, |
547 | { "pause" , w_pause }, |
548 | { "setVolume" , w_setVolume }, |
549 | { "getVolume" , w_getVolume }, |
550 | { "setPosition" , w_setPosition }, |
551 | { "getPosition" , w_getPosition }, |
552 | { "setOrientation" , w_setOrientation }, |
553 | { "getOrientation" , w_getOrientation }, |
554 | { "setVelocity" , w_setVelocity }, |
555 | { "getVelocity" , w_getVelocity }, |
556 | { "setDopplerScale" , w_setDopplerScale }, |
557 | { "getDopplerScale" , w_getDopplerScale }, |
558 | //{ "setMeter", w_setMeter }, |
559 | //{ "getMeter", w_setMeter }, |
560 | { "setDistanceModel" , w_setDistanceModel }, |
561 | { "getDistanceModel" , w_getDistanceModel }, |
562 | { "getRecordingDevices" , w_getRecordingDevices }, |
563 | { "setEffect" , w_setEffect }, |
564 | { "getEffect" , w_getEffect }, |
565 | { "getActiveEffects" , w_getActiveEffects }, |
566 | { "getMaxSceneEffects" , w_getMaxSceneEffects }, |
567 | { "getMaxSourceEffects" , w_getMaxSourceEffects }, |
568 | { "isEffectsSupported" , w_isEffectsSupported }, |
569 | { "setMixWithSystem" , w_setMixWithSystem }, |
570 | |
571 | // Deprecated |
572 | { "getSourceCount" , w_getSourceCount }, |
573 | |
574 | { 0, 0 } |
575 | }; |
576 | |
577 | static const lua_CFunction types[] = |
578 | { |
579 | luaopen_source, |
580 | luaopen_recordingdevice, |
581 | 0 |
582 | }; |
583 | |
584 | extern "C" int luaopen_love_audio(lua_State *L) |
585 | { |
586 | Audio *instance = instance(); |
587 | |
588 | if (instance == nullptr) |
589 | { |
590 | // Try OpenAL first. |
591 | try |
592 | { |
593 | instance = new love::audio::openal::Audio(); |
594 | } |
595 | catch(love::Exception &e) |
596 | { |
597 | std::cout << e.what() << std::endl; |
598 | } |
599 | } |
600 | else |
601 | instance->retain(); |
602 | |
603 | if (instance == nullptr) |
604 | { |
605 | // Fall back to nullaudio. |
606 | try |
607 | { |
608 | instance = new love::audio::null::Audio(); |
609 | } |
610 | catch(love::Exception &e) |
611 | { |
612 | std::cout << e.what() << std::endl; |
613 | } |
614 | } |
615 | |
616 | if (instance == nullptr) |
617 | return luaL_error(L, "Could not open any audio module." ); |
618 | |
619 | WrappedModule w; |
620 | w.module = instance; |
621 | w.name = "audio" ; |
622 | w.type = &Module::type; |
623 | w.functions = functions; |
624 | w.types = types; |
625 | |
626 | int n = luax_register_module(L, w); |
627 | |
628 | return n; |
629 | } |
630 | |
631 | } // audio |
632 | } // love |
633 | |