mirror of
https://github.com/ctruLua/ctruLua.git
synced 2025-10-27 16:39:29 +00:00
ADDED AUDIO! WAV working perfectly
Added libogg, libvorbis and libvorbisfile to 3ds_portlibs. You will need to compile thems using make build-portlibs. Opening OGG files works but they play badly. Added a very simple error handler in main.lua Added audio example. Renamed isGfxInitialised to isGfxInitialized. Did you know? The audio module is the longest ctrµLua module.
This commit is contained in:
parent
716c42b849
commit
bda9de4d1c
10 changed files with 985 additions and 17 deletions
4
Makefile
4
Makefile
|
|
@ -55,7 +55,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
||||||
ASFLAGS := -g $(ARCH)
|
ASFLAGS := -g $(ARCH)
|
||||||
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
LIBS := -lsfil -ljpeg -lsftd -lfreetype -lpng -lz -lsf2d -lctru -lm
|
LIBS := -lsfil -ljpeg -lsftd -lfreetype -lpng -lz -lsf2d -lctru -lvorbisfile -lvorbis -logg -lm
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
# list of directories containing libraries, this must be the top level containing
|
# list of directories containing libraries, this must be the top level containing
|
||||||
|
|
@ -142,7 +142,7 @@ $(BUILD):
|
||||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||||
|
|
||||||
build-portlibs:
|
build-portlibs:
|
||||||
@make -C libs/3ds_portlibs zlib freetype libjpeg-turbo libpng
|
@make -C libs/3ds_portlibs zlib freetype libjpeg-turbo libpng libogg libvorbis
|
||||||
|
|
||||||
build-sf2dlib:
|
build-sf2dlib:
|
||||||
@make -C libs/sf2dlib/libsf2d build
|
@make -C libs/sf2dlib/libsf2d build
|
||||||
|
|
|
||||||
2
libs/3ds_portlibs/.gitignore
vendored
2
libs/3ds_portlibs/.gitignore
vendored
|
|
@ -4,4 +4,6 @@ libjpeg-*
|
||||||
libpng-*
|
libpng-*
|
||||||
sqlite-*
|
sqlite-*
|
||||||
zlib-*
|
zlib-*
|
||||||
|
libogg-*
|
||||||
|
libvorbis-*
|
||||||
build/
|
build/
|
||||||
|
|
@ -28,6 +28,16 @@ ZLIB_VERSION := $(ZLIB)-1.2.8
|
||||||
ZLIB_SRC := $(ZLIB_VERSION).tar.gz
|
ZLIB_SRC := $(ZLIB_VERSION).tar.gz
|
||||||
ZLIB_DOWNLOAD := "http://prdownloads.sourceforge.net/libpng/zlib-1.2.8.tar.gz"
|
ZLIB_DOWNLOAD := "http://prdownloads.sourceforge.net/libpng/zlib-1.2.8.tar.gz"
|
||||||
|
|
||||||
|
LIBOGG := libogg
|
||||||
|
LIBOGG_VERSION := $(LIBOGG)-1.3.2
|
||||||
|
LIBOGG_SRC := $(LIBOGG_VERSION).tar.gz
|
||||||
|
LIBOGG_DOWNLOAD := "http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.gz"
|
||||||
|
|
||||||
|
LIBVORBIS := libvorbis
|
||||||
|
LIBVORBIS_VERSION := $(LIBVORBIS)-1.3.5
|
||||||
|
LIBVORBIS_SRC := $(LIBVORBIS_VERSION).tar.gz
|
||||||
|
LIBVORBIS_DOWNLOAD := "http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.gz"
|
||||||
|
|
||||||
export PORTLIBS := $(CURDIR)/build
|
export PORTLIBS := $(CURDIR)/build
|
||||||
export PATH := $(DEVKITARM)/bin:$(PATH)
|
export PATH := $(DEVKITARM)/bin:$(PATH)
|
||||||
export PKG_CONFIG_PATH := $(PORTLIBS)/lib/pkgconfig
|
export PKG_CONFIG_PATH := $(PORTLIBS)/lib/pkgconfig
|
||||||
|
|
@ -66,8 +76,8 @@ $(FREETYPE): $(FREETYPE_SRC)
|
||||||
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static --without-harfbuzz
|
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static --without-harfbuzz
|
||||||
@$(MAKE) -C $(FREETYPE_VERSION)
|
@$(MAKE) -C $(FREETYPE_VERSION)
|
||||||
@make create_build_dir
|
@make create_build_dir
|
||||||
@cp -srf $(CURDIR)/freetype-2.6/include/. $(CURDIR)/build/include
|
@cp -srf $(CURDIR)/$(FREETYPE_VERSION)/include/. $(CURDIR)/build/include
|
||||||
@cp -sf $(CURDIR)/freetype-2.6/objs/.libs/libfreetype.a $(CURDIR)/build/lib/libfreetype.a
|
@cp -sf $(CURDIR)/$(FREETYPE_VERSION)/objs/.libs/libfreetype.a $(CURDIR)/build/lib/libfreetype.a
|
||||||
|
|
||||||
$(LIBEXIF): $(LIBEXIF_SRC)
|
$(LIBEXIF): $(LIBEXIF_SRC)
|
||||||
@[ -d $(LIBEXIF_VERSION) ] || tar -xf $<
|
@[ -d $(LIBEXIF_VERSION) ] || tar -xf $<
|
||||||
|
|
@ -81,8 +91,8 @@ $(LIBJPEGTURBO): $(LIBJPEGTURBO_SRC)
|
||||||
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
|
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
|
||||||
@$(MAKE) CFLAGS+="\"-Drandom()=rand()\"" -C $(LIBJPEGTURBO_VERSION)
|
@$(MAKE) CFLAGS+="\"-Drandom()=rand()\"" -C $(LIBJPEGTURBO_VERSION)
|
||||||
@make create_build_dir
|
@make create_build_dir
|
||||||
@cp -sf $(CURDIR)/libjpeg-turbo-*/*.h $(CURDIR)/build/include
|
@cp -sf $(CURDIR)/$(LIBJPEGTURBO_VERSION)/*.h $(CURDIR)/build/include
|
||||||
@cp -sf $(CURDIR)/libjpeg-turbo-*/.libs/libjpeg.a $(CURDIR)/build/lib/libjpeg.a
|
@cp -sf $(CURDIR)/$(LIBJPEGTURBO_VERSION)/.libs/libjpeg.a $(CURDIR)/build/lib/libjpeg.a
|
||||||
|
|
||||||
$(LIBPNG): $(LIBPNG_SRC)
|
$(LIBPNG): $(LIBPNG_SRC)
|
||||||
@[ -d $(LIBPNG_VERSION) ] || tar -xf $<
|
@[ -d $(LIBPNG_VERSION) ] || tar -xf $<
|
||||||
|
|
@ -90,8 +100,8 @@ $(LIBPNG): $(LIBPNG_SRC)
|
||||||
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
|
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
|
||||||
@$(MAKE) -C $(LIBPNG_VERSION)
|
@$(MAKE) -C $(LIBPNG_VERSION)
|
||||||
@make create_build_dir
|
@make create_build_dir
|
||||||
@cp -sf $(CURDIR)/libpng-*/*.h $(CURDIR)/build/include
|
@cp -sf $(CURDIR)/$(LIBPNG_VERSION)/*.h $(CURDIR)/build/include
|
||||||
@cp -sf $(CURDIR)/libpng-*/.libs/*.a $(CURDIR)/build/lib/libpng.a
|
@cp -sf $(CURDIR)/$(LIBPNG_VERSION)/.libs/*.a $(CURDIR)/build/lib/libpng.a
|
||||||
|
|
||||||
# sqlite won't work with -ffast-math
|
# sqlite won't work with -ffast-math
|
||||||
$(SQLITE): $(SQLITE_SRC)
|
$(SQLITE): $(SQLITE_SRC)
|
||||||
|
|
@ -107,8 +117,26 @@ $(ZLIB): $(ZLIB_SRC)
|
||||||
CHOST=arm-none-eabi ./configure --static --prefix=$(PORTLIBS)
|
CHOST=arm-none-eabi ./configure --static --prefix=$(PORTLIBS)
|
||||||
@$(MAKE) -C $(ZLIB_VERSION)
|
@$(MAKE) -C $(ZLIB_VERSION)
|
||||||
@make create_build_dir
|
@make create_build_dir
|
||||||
@cp -sf $(CURDIR)/zlib-*/*.h $(CURDIR)/build/include
|
@cp -sf $(CURDIR)/$(ZLIB_VERSION)/*.h $(CURDIR)/build/include
|
||||||
@cp -sf $(CURDIR)/zlib-*/libz.a $(CURDIR)/build/lib/libz.a
|
@cp -sf $(CURDIR)/$(ZLIB_VERSION)/libz.a $(CURDIR)/build/lib/libz.a
|
||||||
|
|
||||||
|
$(LIBOGG): $(LIBOGG_SRC)
|
||||||
|
@[ -d $(LIBOGG_VERSION) ] || tar -xf $<
|
||||||
|
@cd $(LIBOGG_VERSION) && \
|
||||||
|
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
|
||||||
|
@$(MAKE) -C $(LIBOGG_VERSION)
|
||||||
|
@make create_build_dir
|
||||||
|
@cp -srf $(CURDIR)/$(LIBOGG_VERSION)/include/. $(CURDIR)/build/include
|
||||||
|
@cp -sf $(CURDIR)/$(LIBOGG_VERSION)/src/.libs/*.a $(CURDIR)/build/lib
|
||||||
|
|
||||||
|
$(LIBVORBIS): $(LIBVORBIS_SRC)
|
||||||
|
@[ -d $(LIBVORBIS_VERSION) ] || tar -xf $<
|
||||||
|
@cd $(LIBVORBIS_VERSION) && \
|
||||||
|
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
|
||||||
|
@$(MAKE) -C $(LIBVORBIS_VERSION)
|
||||||
|
@make create_build_dir
|
||||||
|
@cp -srf $(CURDIR)/$(LIBVORBIS_VERSION)/include/. $(CURDIR)/build/include
|
||||||
|
@cp -sf $(CURDIR)/$(LIBVORBIS_VERSION)/lib/.libs/*.a $(CURDIR)/build/lib
|
||||||
|
|
||||||
# Downloads
|
# Downloads
|
||||||
$(ZLIB_SRC):
|
$(ZLIB_SRC):
|
||||||
|
|
@ -129,6 +157,12 @@ $(LIBPNG_SRC):
|
||||||
$(SQLITE_SRC):
|
$(SQLITE_SRC):
|
||||||
wget -O $@ $(SQLITE_DOWNLOAD)
|
wget -O $@ $(SQLITE_DOWNLOAD)
|
||||||
|
|
||||||
|
$(LIBOGG_SRC):
|
||||||
|
wget -O $@ $(LIBOGG_DOWNLOAD)
|
||||||
|
|
||||||
|
$(LIBVORBIS_SRC):
|
||||||
|
wget -O $@ $(LIBVORBIS_DOWNLOAD)
|
||||||
|
|
||||||
install-zlib:
|
install-zlib:
|
||||||
@$(MAKE) -C $(ZLIB_VERSION) install
|
@$(MAKE) -C $(ZLIB_VERSION) install
|
||||||
|
|
||||||
|
|
@ -138,6 +172,8 @@ install:
|
||||||
@[ ! -d $(LIBJPEGTURBO_VERSION) ] || $(MAKE) -C $(LIBJPEGTURBO_VERSION) install
|
@[ ! -d $(LIBJPEGTURBO_VERSION) ] || $(MAKE) -C $(LIBJPEGTURBO_VERSION) install
|
||||||
@[ ! -d $(LIBPNG_VERSION) ] || $(MAKE) -C $(LIBPNG_VERSION) install
|
@[ ! -d $(LIBPNG_VERSION) ] || $(MAKE) -C $(LIBPNG_VERSION) install
|
||||||
@[ ! -d $(SQLITE_VERSION) ] || $(MAKE) -C $(SQLITE_VERSION) install-libLTLIBRARIES install-data
|
@[ ! -d $(SQLITE_VERSION) ] || $(MAKE) -C $(SQLITE_VERSION) install-libLTLIBRARIES install-data
|
||||||
|
@[ ! -d $(LIBOGG_VERSION) ] || $(MAKE) -C $(LIBOGG_VERSION) install
|
||||||
|
@[ ! -d $(LIBVORBIS_VERSION) ] || $(MAKE) -C $(LIBVORBIS_VERSION) install
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@$(RM) -r $(FREETYPE_VERSION)
|
@$(RM) -r $(FREETYPE_VERSION)
|
||||||
|
|
@ -146,5 +182,7 @@ clean:
|
||||||
@$(RM) -r $(LIBPNG_VERSION)
|
@$(RM) -r $(LIBPNG_VERSION)
|
||||||
@$(RM) -r $(SQLITE_VERSION)
|
@$(RM) -r $(SQLITE_VERSION)
|
||||||
@$(RM) -r $(ZLIB_VERSION)
|
@$(RM) -r $(ZLIB_VERSION)
|
||||||
|
@$(RM) -r $(LIBOGG_VERSION)
|
||||||
|
@$(RM) -r $(LIBVORBIS_VERSION)
|
||||||
@rm -rf $(CURDIR)/build
|
@rm -rf $(CURDIR)/build
|
||||||
@rm -f $(CURDIR)/*.tar.*
|
@rm -f $(CURDIR)/*.tar.*
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,22 @@ repeat
|
||||||
local file = require("openfile")("Choose a Lua file to execute", nil, ".lua", "exist")
|
local file = require("openfile")("Choose a Lua file to execute", nil, ".lua", "exist")
|
||||||
if file then
|
if file then
|
||||||
fs.setDirectory(file:match("^(.-)[^/]*$"))
|
fs.setDirectory(file:match("^(.-)[^/]*$"))
|
||||||
dofile(file)
|
local success, err = pcall(dofile, file)
|
||||||
|
if not success then
|
||||||
|
local gfx = require("ctr.gfx")
|
||||||
|
local hid = require("ctr.hid")
|
||||||
|
gfx.set3D(false)
|
||||||
|
gfx.color.setDefault(0xFFFFFFFF)
|
||||||
|
gfx.color.setBackground(0xFF000000)
|
||||||
|
gfx.font.setDefault()
|
||||||
|
while true do
|
||||||
|
hid.read()
|
||||||
|
if hid.keys().down.start then break end
|
||||||
|
gfx.startFrame(gfx.GFX_TOP)
|
||||||
|
gfx.wrappedText(0, 0, err, gfx.TOP_WIDTH)
|
||||||
|
gfx.endFrame()
|
||||||
|
gfx.render()
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
until not file
|
until not file
|
||||||
52
sdcard/3ds/ctruLua/tests/audio/main.lua
Normal file
52
sdcard/3ds/ctruLua/tests/audio/main.lua
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
local ctr = require("ctr")
|
||||||
|
local hid = require("ctr.hid")
|
||||||
|
local gfx = require("ctr.gfx")
|
||||||
|
local audio = require("ctr.audio")
|
||||||
|
|
||||||
|
local test = assert(audio.load("test.wav"))
|
||||||
|
|
||||||
|
local channel = -1
|
||||||
|
local speed = 1
|
||||||
|
local leftBalance = 0.5
|
||||||
|
|
||||||
|
while true do
|
||||||
|
hid.read()
|
||||||
|
local keys = hid.keys()
|
||||||
|
if keys.down.start then break end
|
||||||
|
|
||||||
|
if keys.down.a then
|
||||||
|
channel = test:play()
|
||||||
|
end
|
||||||
|
|
||||||
|
if keys.down.up then
|
||||||
|
speed = speed + 0.01
|
||||||
|
test:speed(speed)
|
||||||
|
audio.speed(nil, speed)
|
||||||
|
end
|
||||||
|
if keys.down.down then
|
||||||
|
speed = speed - 0.01
|
||||||
|
test:speed(speed)
|
||||||
|
audio.speed(nil, speed)
|
||||||
|
end
|
||||||
|
|
||||||
|
if keys.down.left then
|
||||||
|
leftBalance = leftBalance + 0.1
|
||||||
|
test:mix(1-leftBalance, leftBalance)
|
||||||
|
audio.mix(nil, leftBalance, 1-leftBalance)
|
||||||
|
end
|
||||||
|
if keys.down.right then
|
||||||
|
leftBalance = leftBalance - 0.1
|
||||||
|
test:mix(1-leftBalance, leftBalance)
|
||||||
|
audio.mix(nil, leftBalance, 1-leftBalance)
|
||||||
|
end
|
||||||
|
|
||||||
|
gfx.startFrame(gfx.GFX_TOP)
|
||||||
|
gfx.text(5, 5, "Audio test! "..tostring(test:time()).."/"..tostring(test:duration()).."s")
|
||||||
|
gfx.text(5, 25, "Last audio played on channel "..tostring(channel))
|
||||||
|
gfx.text(5, 65, "Speed: "..(speed*100).."% - Left balance: "..(leftBalance*100).."%")
|
||||||
|
gfx.endFrame()
|
||||||
|
|
||||||
|
gfx.render()
|
||||||
|
end
|
||||||
|
|
||||||
|
test:unload()
|
||||||
BIN
sdcard/3ds/ctruLua/tests/audio/test.wav
Normal file
BIN
sdcard/3ds/ctruLua/tests/audio/test.wav
Normal file
Binary file not shown.
851
source/audio.c
Normal file
851
source/audio.c
Normal file
|
|
@ -0,0 +1,851 @@
|
||||||
|
/***
|
||||||
|
The `audio` module.
|
||||||
|
An audio channel can play only one audio object at a time.
|
||||||
|
There are 24 audio channels available, numbered from 0 to 23.
|
||||||
|
@module ctr.audio
|
||||||
|
@usage local audio = require("ctr.audio")
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <3ds.h>
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
#include <vorbis/codec.h>
|
||||||
|
#include <vorbis/vorbisfile.h>
|
||||||
|
|
||||||
|
// Audio object type
|
||||||
|
typedef enum {
|
||||||
|
TYPE_UNKNOWN = -1,
|
||||||
|
TYPE_OGG = 0,
|
||||||
|
TYPE_WAV = 1
|
||||||
|
} filetype;
|
||||||
|
|
||||||
|
// Audio object userdata
|
||||||
|
typedef struct {
|
||||||
|
filetype type; // file type
|
||||||
|
|
||||||
|
// OGG Vorbis specific
|
||||||
|
OggVorbis_File vf; // ogg vorbis file
|
||||||
|
|
||||||
|
// Needed for playback
|
||||||
|
float rate; // sample rate (per channel) (Hz)
|
||||||
|
u32 channels; // channel count
|
||||||
|
u32 encoding; // data encoding (NDSP_ENCODING_*)
|
||||||
|
u32 nsamples; // numbers of samples in the audio (per channel, not the total)
|
||||||
|
u32 size; // number of bytes in the audio (total, ie data size)
|
||||||
|
char* data; // raw audio data
|
||||||
|
|
||||||
|
// Playing parameters (type-independant)
|
||||||
|
float mix[12]; // mix parameters
|
||||||
|
ndspInterpType interp; // interpolation type
|
||||||
|
double speed; // playing speed
|
||||||
|
} audio_userdata;
|
||||||
|
|
||||||
|
// Indicate if NDSP was initialized or not.
|
||||||
|
// NDSP doesn't work on citra yet.
|
||||||
|
// Please only throw an error related to this when using a ndsp function, so other parts of the
|
||||||
|
// audio module are still available on citra, like audio.load() and audio:info().
|
||||||
|
bool isAudioInitialized = false;
|
||||||
|
|
||||||
|
// Array of the last audio_userdata sent to each channel; channels range from 0 to 23
|
||||||
|
audio_userdata* channels[24];
|
||||||
|
|
||||||
|
/***
|
||||||
|
Load an audio file.
|
||||||
|
OGG Vorbis and PCM WAV file format are currently supported.
|
||||||
|
(Most WAV files use the PCM encoding).
|
||||||
|
@function load
|
||||||
|
@tparam string path path to the file
|
||||||
|
@tparam[opt=detect] string type file type, `"ogg"` or `"wav"`.
|
||||||
|
If set to `"detect"`, will try to deduce the type from the filename.
|
||||||
|
@treturn[1] audio the loaded audio object
|
||||||
|
@treturn[2] nil if a error happened
|
||||||
|
@treturn[2] string error message
|
||||||
|
*/
|
||||||
|
static int audio_load(lua_State *L) {
|
||||||
|
const char *path = luaL_checkstring(L, 1);
|
||||||
|
const char* argType = luaL_optstring(L, 2, "detect");
|
||||||
|
|
||||||
|
// Create userdata
|
||||||
|
audio_userdata *audio = lua_newuserdata(L, sizeof(*audio));
|
||||||
|
luaL_getmetatable(L, "LAudio");
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
for (int i=0; i<12; i++) audio->mix[i] = 1;
|
||||||
|
audio->interp = NDSP_INTERP_LINEAR;
|
||||||
|
audio->speed = 1;
|
||||||
|
|
||||||
|
// Get file type
|
||||||
|
filetype type = TYPE_UNKNOWN;
|
||||||
|
if (strcmp(argType, "detect") == 0) {
|
||||||
|
const char *dot = strrchr(path, '.');
|
||||||
|
if (!dot || dot == path) dot = "";
|
||||||
|
const char *ext = dot + 1;
|
||||||
|
if (strncmp(ext, "ogg", 3) == 0) type = TYPE_OGG;
|
||||||
|
else if (strncmp(ext, "wav", 3) == 0) type = TYPE_WAV;
|
||||||
|
} else if (strcmp(argType, "ogg") == 0) {
|
||||||
|
type = TYPE_OGG;
|
||||||
|
} else if (strcmp(argType, "wav") == 0) {
|
||||||
|
type = TYPE_WAV;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open and read file
|
||||||
|
if (type == TYPE_OGG) {
|
||||||
|
audio->type = TYPE_OGG;
|
||||||
|
|
||||||
|
// Load audio file
|
||||||
|
if (ov_fopen(path, &audio->vf) < 0) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushstring(L, "input does not appear to be a valid ogg vorbis file or doesn't exist");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some Ogg Vorbis decoding parameters
|
||||||
|
bool bigendianp = false; // bigendianness
|
||||||
|
u8 word = 2; // word size; 1 or 2 (8bits/sample or 16bits/sample)
|
||||||
|
bool sgned = true; // signed data
|
||||||
|
|
||||||
|
// Decoding Ogg Vorbis bitstream
|
||||||
|
vorbis_info* vi = ov_info(&audio->vf, -1);
|
||||||
|
if (vi == NULL) luaL_error(L, "could not retrieve ogg audio stream informations");
|
||||||
|
|
||||||
|
audio->rate = vi->rate;
|
||||||
|
audio->channels = vi->channels;
|
||||||
|
audio->encoding = NDSP_ENCODING_PCM16;
|
||||||
|
audio->nsamples = ov_pcm_total(&audio->vf, -1);
|
||||||
|
audio->size = audio->nsamples * word;
|
||||||
|
|
||||||
|
if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available");
|
||||||
|
audio->data = linearAlloc(audio->size);
|
||||||
|
|
||||||
|
// Decoding loop
|
||||||
|
int offset = 0;
|
||||||
|
int eof = 0;
|
||||||
|
int current_section;
|
||||||
|
while (!eof) {
|
||||||
|
long ret = ov_read(&audio->vf, &audio->data[offset], 4096, bigendianp, word, sgned, ¤t_section);
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
eof = 1;
|
||||||
|
} else if (ret < 0) {
|
||||||
|
ov_clear(&audio->vf);
|
||||||
|
linearFree(audio->data);
|
||||||
|
luaL_error(L, "error in the ogg vorbis stream");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
// TODO handle multiple links (http://xiph.org/vorbis/doc/vorbisfile/decoding.html)
|
||||||
|
offset += ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
} else if (type == TYPE_WAV) {
|
||||||
|
audio->type = TYPE_WAV;
|
||||||
|
|
||||||
|
// Used this as a reference for the WAV format: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
|
||||||
|
|
||||||
|
// Load file
|
||||||
|
FILE *file = fopen(path, "rb");
|
||||||
|
if (file) {
|
||||||
|
bool valid = true; // if something goes wrong, this will be false
|
||||||
|
|
||||||
|
char buff[8];
|
||||||
|
|
||||||
|
// Master chunk
|
||||||
|
fread(buff, 4, 1, file); // ckId
|
||||||
|
if (strncmp(buff, "RIFF", 4) != 0) valid = false;
|
||||||
|
|
||||||
|
fseek(file, 4, SEEK_CUR); // skip ckSize
|
||||||
|
|
||||||
|
fread(buff, 4, 1, file); // WAVEID
|
||||||
|
if (strncmp(buff, "WAVE", 4) != 0) valid = false;
|
||||||
|
|
||||||
|
// fmt Chunk
|
||||||
|
fread(buff, 4, 1, file); // ckId
|
||||||
|
if (strncmp(buff, "fmt ", 4) != 0) valid = false;
|
||||||
|
|
||||||
|
fread(buff, 4, 1, file); // ckSize
|
||||||
|
if (*buff != 16) valid = false; // should be 16 for PCM format
|
||||||
|
|
||||||
|
fread(buff, 2, 1, file); // wFormatTag
|
||||||
|
if (*buff != 0x0001) valid = false; // PCM format
|
||||||
|
|
||||||
|
u16 channels;
|
||||||
|
fread(&channels, 2, 1, file); // nChannels
|
||||||
|
audio->channels = channels;
|
||||||
|
|
||||||
|
u32 rate;
|
||||||
|
fread(&rate, 4, 1, file); // nSamplesPerSec
|
||||||
|
audio->rate = rate;
|
||||||
|
|
||||||
|
fseek(file, 4, SEEK_CUR); // skip nAvgBytesPerSec
|
||||||
|
|
||||||
|
u16 byte_per_block; // 1 block = 1*channelCount samples
|
||||||
|
fread(&byte_per_block, 2, 1, file); // nBlockAlign
|
||||||
|
|
||||||
|
u16 byte_per_sample;
|
||||||
|
fread(&byte_per_sample, 2, 1, file); // wBitsPerSample
|
||||||
|
byte_per_sample /= 8; // bits -> bytes
|
||||||
|
|
||||||
|
// There may be some additionals chunks between fmt and data
|
||||||
|
// TODO handle some usefull chunks that may be here
|
||||||
|
fread(&buff, 4, 1, file); // ckId
|
||||||
|
while (strncmp(buff, "data", 4) != 0) {
|
||||||
|
u32 size;
|
||||||
|
fread(&size, 4, 1, file); // ckSize
|
||||||
|
|
||||||
|
fseek(file, size, SEEK_CUR); // skip chunk
|
||||||
|
|
||||||
|
int i = fread(&buff, 4, 1, file); // next chunk ckId
|
||||||
|
|
||||||
|
if (i < 4) { // reached EOF before finding a data chunk
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// data Chunk (ckId already read)
|
||||||
|
u32 size;
|
||||||
|
fread(&size, 4, 1, file); // ckSize
|
||||||
|
audio->size = size;
|
||||||
|
|
||||||
|
audio->nsamples = audio->size / byte_per_block;
|
||||||
|
|
||||||
|
if (byte_per_sample == 1) audio->encoding = NDSP_ENCODING_PCM8;
|
||||||
|
else if (byte_per_sample == 2) audio->encoding = NDSP_ENCODING_PCM16;
|
||||||
|
else luaL_error(L, "unknown encoding, needs to be PCM8 or PCM16");
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
fclose(file);
|
||||||
|
luaL_error(L, "invalid PCM wav file");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available");
|
||||||
|
audio->data = linearAlloc(audio->size);
|
||||||
|
|
||||||
|
fread(audio->data, audio->size, 1, file);
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushfstring(L, "error while opening wav file: %s", strerror(errno));;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
luaL_error(L, "unknown audio type");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Check if audio is currently playing on a channel.
|
||||||
|
@function playing
|
||||||
|
@tparam[opt] integer channel number; if `nil` will search the first channel playing an audio
|
||||||
|
@treturn boolean `true` if the channel is currently playing the audio, `false` otherwise.
|
||||||
|
If channel is not set, `false` means no audio is playing at all.
|
||||||
|
*/
|
||||||
|
static int audio_playing(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) {
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int channel = luaL_optinteger(L, 1, -1);
|
||||||
|
if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
|
||||||
|
// Search a channel playing audio
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i <= 23; i++) {
|
||||||
|
if (ndspChnIsPlaying(i)) {
|
||||||
|
lua_pushboolean(L, true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lua_pushboolean(L, ndspChnIsPlaying(channel));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Set the mix parameters (volumes) of a channel.
|
||||||
|
Volumes go from 0 (0%) to 1 (100%).
|
||||||
|
Note that when a new audio object will play on this channel, theses parameters will be
|
||||||
|
reset with the new audio object defaults (set in `audio:mix()`).
|
||||||
|
@function mix
|
||||||
|
@tparam[opt] integer channel the channel number, if `nil` will change the mix parmaters of all channels
|
||||||
|
@tparam[opt=1] number frontLeft front left volume
|
||||||
|
@tparam[opt=frontLeft] number frontRight front right volume
|
||||||
|
@tparam[opt=frontLeft] number backLeft back left volume
|
||||||
|
@tparam[opt=frontRight] number backRight back right volume
|
||||||
|
*/
|
||||||
|
static int audio_mix(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");
|
||||||
|
|
||||||
|
int channel = luaL_optinteger(L, 1, -1);
|
||||||
|
if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
|
||||||
|
float mix[12];
|
||||||
|
mix[0] = luaL_optnumber(L, 2, 1);
|
||||||
|
mix[1] = luaL_optnumber(L, 3, mix[0]);
|
||||||
|
mix[2] = luaL_optnumber(L, 4, mix[0]);
|
||||||
|
mix[3] = luaL_optnumber(L, 5, mix[2]);
|
||||||
|
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i=0; i<=23; i++) ndspChnSetMix(i, mix);
|
||||||
|
} else {
|
||||||
|
ndspChnSetMix(channel, mix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Set the interpolation type of a channel.
|
||||||
|
Note that when a new audio object will play on this channel, this parameter will be
|
||||||
|
reset with the new audio object default (set in `audio:interpolation()`).
|
||||||
|
@function interpolation
|
||||||
|
@tparam[opt] integer channel stop playing audio on this channel; if `nil` will change interpolation type on all channels
|
||||||
|
@tparam[opt=linear] string "none", "linear" or "polyphase"
|
||||||
|
*/
|
||||||
|
static int audio_interpolation(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");
|
||||||
|
|
||||||
|
int channel = luaL_optinteger(L, 1, -1);
|
||||||
|
if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
|
||||||
|
const char* interpArg = luaL_optstring(L, 2, "linear");
|
||||||
|
|
||||||
|
ndspInterpType interp;
|
||||||
|
if (strcmp(interpArg, "none") == 0)
|
||||||
|
interp = NDSP_INTERP_NONE;
|
||||||
|
else if (strcmp(interpArg, "linear") == 0)
|
||||||
|
interp = NDSP_INTERP_LINEAR;
|
||||||
|
else if (strcmp(interpArg, "polyphase") == 0)
|
||||||
|
interp = NDSP_INTERP_POLYPHASE;
|
||||||
|
else {
|
||||||
|
luaL_error(L, "unknown interpolation type");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i=0; i<=23; i++) ndspChnSetInterp(i, interp);
|
||||||
|
} else {
|
||||||
|
ndspChnSetInterp(channel, interp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Set the speed of the audio playing in a channel.
|
||||||
|
Speed is expressed as a percentage of the normal playing speed.
|
||||||
|
1 is 100% speed and 2 is 200%, etc.
|
||||||
|
Note that when a new audio object will play on this channel, this parameter will be
|
||||||
|
reset with the new audio object default (set in `audio:speed()`).
|
||||||
|
@function speed
|
||||||
|
@tparam[opt] integer channel stop playing audio on this channel; if `nil` will change interpolation type on all channels
|
||||||
|
@tparam[opt=1] number speed percentage
|
||||||
|
*/
|
||||||
|
static int audio_speed(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");
|
||||||
|
|
||||||
|
int channel = luaL_optinteger(L, 1, -1);
|
||||||
|
if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
|
||||||
|
double speed = luaL_optnumber(L, 2, 1);
|
||||||
|
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i=0; i<=23; i++) {
|
||||||
|
if (channels[i]) ndspChnSetRate(i, channels[i]->rate * speed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (channels[channel]) ndspChnSetRate(channel, channels[channel]->rate * speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Stop playing all audio on all channels or a specific channel.
|
||||||
|
@function stop
|
||||||
|
@tparam[opt] integer channel stop playing audio on this channel; if `nil` will stop audio on all channels
|
||||||
|
@treturn integer number of channels where audio was stopped
|
||||||
|
*/
|
||||||
|
static int audio_stop(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) {
|
||||||
|
lua_pushinteger(L, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int channel = luaL_optinteger(L, 1, -1);
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i <= 23; i++) {
|
||||||
|
if (ndspChnIsPlaying(i)) {
|
||||||
|
ndspChnWaveBufClear(i);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (channel < 0 || channel > 23) {
|
||||||
|
luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
} else {
|
||||||
|
if (ndspChnIsPlaying(channel)) {
|
||||||
|
ndspChnWaveBufClear(channel);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushinteger(L, n);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
audio object
|
||||||
|
@section Methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***
|
||||||
|
Returns the audio object duration.
|
||||||
|
@function :duration
|
||||||
|
@treturn number duration in seconds
|
||||||
|
*/
|
||||||
|
static int audio_object_duration(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
lua_pushnumber(L, (double)(audio->nsamples) / audio->rate);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Returns the current playing position.
|
||||||
|
@function :time
|
||||||
|
@tparam[opt] integer channel number; if `nil` will use the first channel found which played this audio
|
||||||
|
@treturn number time in seconds
|
||||||
|
*/
|
||||||
|
static int audio_object_time(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
int channel = luaL_optinteger(L, 2, -1);
|
||||||
|
if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
|
||||||
|
// Search a channel playing the audio object
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i <= 23; i++) {
|
||||||
|
if (channels[i] == audio) {
|
||||||
|
channel = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel == -1 || channels[channel] != audio || !isAudioInitialized) // audio not playing
|
||||||
|
lua_pushnumber(L, 0);
|
||||||
|
else
|
||||||
|
lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Check if the audio is currently playing.
|
||||||
|
@function :playing
|
||||||
|
@tparam[opt] integer channel channel number; if `nil` will search the first channel playing this audio
|
||||||
|
@treturn boolean true if the channel is currently playing the audio, false otherwise
|
||||||
|
*/
|
||||||
|
static int audio_object_playing(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) {
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
int channel = luaL_optinteger(L, 2, -1);
|
||||||
|
if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
|
||||||
|
// Search a channel playing the audio object
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i <= 23; i++) {
|
||||||
|
if (channels[i] == audio && ndspChnIsPlaying(i)) {
|
||||||
|
lua_pushboolean(L, true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lua_pushboolean(L, channels[channel] == audio && ndspChnIsPlaying(channel));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Set the mix parameters (volumes) of the audio.
|
||||||
|
Volumes go from 0 (0%) to 1 (100%).
|
||||||
|
@function :mix
|
||||||
|
@tparam[opt=1] number frontLeft front left volume
|
||||||
|
@tparam[opt=frontLeft] number frontRight front right volume
|
||||||
|
@tparam[opt=frontLeft] number backLeft back left volume
|
||||||
|
@tparam[opt=frontRight] number backRight back right volume
|
||||||
|
*/
|
||||||
|
static int audio_object_mix(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
audio->mix[0] = luaL_optnumber(L, 2, 1);
|
||||||
|
audio->mix[1] = luaL_optnumber(L, 3, audio->mix[0]);
|
||||||
|
audio->mix[2] = luaL_optnumber(L, 4, audio->mix[0]);
|
||||||
|
audio->mix[3] = luaL_optnumber(L, 5, audio->mix[2]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Set the interpolation type of the audio.
|
||||||
|
@function :interpolation
|
||||||
|
@tparam[opt=linear] string "none", "linear" or "polyphase"
|
||||||
|
*/
|
||||||
|
static int audio_object_interpolation(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
const char* interp = luaL_optstring(L, 2, "linear");
|
||||||
|
|
||||||
|
if (strcmp(interp, "none") == 0)
|
||||||
|
audio->interp = NDSP_INTERP_NONE;
|
||||||
|
else if (strcmp(interp, "linear") == 0)
|
||||||
|
audio->interp = NDSP_INTERP_LINEAR;
|
||||||
|
else if (strcmp(interp, "polyphase") == 0)
|
||||||
|
audio->interp = NDSP_INTERP_POLYPHASE;
|
||||||
|
else {
|
||||||
|
luaL_error(L, "unknown interpolation type");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Set the speed of the audio.
|
||||||
|
Speed is expressed as a percentage of the normal playing speed.
|
||||||
|
1 is 100% speed and 2 is 200%, etc.
|
||||||
|
@function :speed
|
||||||
|
@tparam[opt=1] number speed percentage
|
||||||
|
*/
|
||||||
|
static int audio_object_speed(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
audio->speed = luaL_optnumber(L, 2, 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Plays the audio file.
|
||||||
|
@function :play
|
||||||
|
@tparam[opt=false] boolean loop if the audio should loop or not
|
||||||
|
@tparam[opt] integer channel the channel to play the audio on (0-23); if `nil` will use the first available channel.
|
||||||
|
If the channel was playing another audio, it will be stopped and replaced by this audio.
|
||||||
|
If not set and no channel is available, will return nil plus an error message.
|
||||||
|
@treturn[1] integer channel number the audio is playing on
|
||||||
|
@treturn[2] nil an error happened and the audio was not played
|
||||||
|
@treturn[2] error the error message
|
||||||
|
*/
|
||||||
|
static int audio_object_play(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");
|
||||||
|
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
bool loop = lua_toboolean(L, 2);
|
||||||
|
int channel = luaL_optinteger(L, 3, -1);
|
||||||
|
|
||||||
|
// Find a free channel
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i <= 23; i++) {
|
||||||
|
if (!ndspChnIsPlaying(i)) {
|
||||||
|
channel = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (channel == -1) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushstring(L, "no audio channel is currently available");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (channel < 0 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
|
||||||
|
// Set channel parameters
|
||||||
|
ndspChnWaveBufClear(channel);
|
||||||
|
ndspChnReset(channel);
|
||||||
|
ndspChnInitParams(channel);
|
||||||
|
ndspChnSetMix(channel, audio->mix);
|
||||||
|
ndspChnSetInterp(channel, audio->interp);
|
||||||
|
ndspChnSetRate(channel, audio->rate * audio->speed); // maybe hackish way to set a different speed, but it works
|
||||||
|
ndspChnSetFormat(channel, NDSP_CHANNELS(audio->channels) | NDSP_ENCODING(audio->encoding));
|
||||||
|
|
||||||
|
// Send & play audio data
|
||||||
|
ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf));
|
||||||
|
|
||||||
|
waveBuf->data_vaddr = audio->data;
|
||||||
|
waveBuf->nsamples = audio->nsamples;
|
||||||
|
waveBuf->looping = loop;
|
||||||
|
|
||||||
|
DSP_FlushDataCache((u32*)audio->data, audio->size);
|
||||||
|
|
||||||
|
ndspChnWaveBufAdd(channel, waveBuf);
|
||||||
|
channels[channel] = audio;
|
||||||
|
|
||||||
|
lua_pushinteger(L, channel);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Stop playing an audio object.
|
||||||
|
@function :stop
|
||||||
|
@tparam[opt] integer channel stop playing the audio on this channel; if `nil` will stop all channels playing this audio.
|
||||||
|
If the channel is playing another audio object, this function will do nothing.
|
||||||
|
@treturn integer number of channels where this audio was stopped
|
||||||
|
*/
|
||||||
|
static int audio_object_stop(lua_State *L) {
|
||||||
|
if (!isAudioInitialized) {
|
||||||
|
lua_pushinteger(L, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
int channel = luaL_optinteger(L, 2, -1);
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i <= 23; i++) {
|
||||||
|
if (channels[i] == audio && ndspChnIsPlaying(i)) {
|
||||||
|
ndspChnWaveBufClear(i);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (channel < 0 || channel > 23) {
|
||||||
|
luaL_error(L, "channel number must be between 0 and 23");
|
||||||
|
} else {
|
||||||
|
if (channels[channel] == audio && ndspChnIsPlaying(channel)) {
|
||||||
|
ndspChnWaveBufClear(channel);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushinteger(L, n);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Returns the audio object type.
|
||||||
|
@function :type
|
||||||
|
@treturn string "ogg" or "wav"
|
||||||
|
*/
|
||||||
|
static int audio_object_type(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
if (audio->type == TYPE_OGG)
|
||||||
|
lua_pushstring(L, "ogg");
|
||||||
|
else if (audio->type == TYPE_WAV)
|
||||||
|
lua_pushstring(L, "wav");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Unload an audio object.
|
||||||
|
@function :unload
|
||||||
|
*/
|
||||||
|
static int audio_object_unload(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
// Stop playing the audio
|
||||||
|
if (isAudioInitialized) {
|
||||||
|
for (int i = 0; i <= 23; i++) {
|
||||||
|
if (channels[i] == audio) {
|
||||||
|
ndspChnWaveBufClear(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio->type == TYPE_OGG) ov_clear(&audio->vf);
|
||||||
|
|
||||||
|
// Free memory
|
||||||
|
linearFree(audio->data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
audio object (ogg-only).
|
||||||
|
Ogg Vorbis files specific methods.
|
||||||
|
Using one of theses methods will throw an error if used on an non-ogg audio object.
|
||||||
|
@section Methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***
|
||||||
|
Returns basic information about the audio in a vorbis bitstream.
|
||||||
|
@function :info
|
||||||
|
@treturn infoTable information table
|
||||||
|
*/
|
||||||
|
static int audio_object_info(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
if (audio->type != TYPE_OGG) luaL_error(L, "only avaible on OGG audio objects");
|
||||||
|
|
||||||
|
vorbis_info* vi = ov_info(&audio->vf, -1);
|
||||||
|
if (vi == NULL) luaL_error(L, "could not retrieve audio stream informations");
|
||||||
|
|
||||||
|
lua_createtable(L, 0, 6);
|
||||||
|
|
||||||
|
lua_pushinteger(L, vi->version);
|
||||||
|
lua_setfield(L, -2, "version");
|
||||||
|
|
||||||
|
lua_pushinteger(L, vi->channels);
|
||||||
|
lua_setfield(L, -2, "channels");
|
||||||
|
|
||||||
|
lua_pushinteger(L, vi->rate);
|
||||||
|
lua_setfield(L, -2, "rate");
|
||||||
|
|
||||||
|
lua_pushinteger(L, vi->bitrate_upper);
|
||||||
|
lua_setfield(L, -2, "bitrateUpper");
|
||||||
|
|
||||||
|
lua_pushinteger(L, vi->bitrate_nominal);
|
||||||
|
lua_setfield(L, -2, "bitrateNominal");
|
||||||
|
|
||||||
|
lua_pushinteger(L, vi->bitrate_lower);
|
||||||
|
lua_setfield(L, -2, "bitrateLower");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Returns the Ogg Vorbis bitstream comment.
|
||||||
|
@function :comment
|
||||||
|
@treturn commentTable comment table
|
||||||
|
*/
|
||||||
|
static int audio_object_comment(lua_State *L) {
|
||||||
|
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
|
||||||
|
|
||||||
|
if (audio->type != TYPE_OGG) luaL_error(L, "only avaible on OGG audio objects");
|
||||||
|
|
||||||
|
vorbis_comment *vc = ov_comment(&audio->vf, -1);
|
||||||
|
|
||||||
|
if (vc == NULL) luaL_error(L, "could not retrieve audio stream comment");
|
||||||
|
|
||||||
|
lua_createtable(L, 0, 5);
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
for (int i=0; i<vc->comments; i++) {
|
||||||
|
lua_pushstring(L, vc->user_comments[i]);
|
||||||
|
lua_seti(L, -2, i+1);
|
||||||
|
}
|
||||||
|
lua_setfield(L, -2, "userComments");
|
||||||
|
|
||||||
|
lua_pushstring(L, vc->vendor);
|
||||||
|
lua_setfield(L, -2, "vendor");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
Tables return.
|
||||||
|
The detailled table structures returned by some methods of audio objects.
|
||||||
|
@section
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***
|
||||||
|
Vorbis bitstream information, returned by audio:info().
|
||||||
|
If bitrateLower == bitrateNominal == bitrateUpper, the stream is fixed bitrate.
|
||||||
|
@table infoTable
|
||||||
|
@tfield integer version Vorbis encoder version used to create this bitstream
|
||||||
|
@tfield integer channels number of channels in bitstream
|
||||||
|
@tfield integer rate sampling rate of the bitstream
|
||||||
|
@tfield integer bitrateUpper the upper limit in a VBR bitstream; may be unset if no limit exists
|
||||||
|
@tfield integer bitrateNominal the average bitrate for a VBR bitstream; may be unset
|
||||||
|
@tfield integer bitrateLower the lower limit in a VBR bitstream; may be unset if no limit exists
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***
|
||||||
|
Vorbis bitstream comment, returned by audio:comment().
|
||||||
|
@table commentTable
|
||||||
|
@tfield table userComments list of all the user comment
|
||||||
|
@tfield string vendor information about the Vorbis implementation that encoded the file
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Audio object methods
|
||||||
|
static const struct luaL_Reg audio_object_methods[] = {
|
||||||
|
// common
|
||||||
|
{ "duration", audio_object_duration },
|
||||||
|
{ "time", audio_object_time },
|
||||||
|
{ "playing", audio_object_playing },
|
||||||
|
{ "mix", audio_object_mix },
|
||||||
|
{ "interpolation", audio_object_interpolation },
|
||||||
|
{ "speed", audio_object_speed },
|
||||||
|
{ "play", audio_object_play },
|
||||||
|
{ "stop", audio_object_stop },
|
||||||
|
{ "type", audio_object_type },
|
||||||
|
{ "unload", audio_object_unload },
|
||||||
|
{ "__gc", audio_object_unload },
|
||||||
|
// ogg only
|
||||||
|
{ "info", audio_object_info },
|
||||||
|
{ "comment", audio_object_comment },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Library functions
|
||||||
|
static const struct luaL_Reg audio_lib[] = {
|
||||||
|
{ "load", audio_load },
|
||||||
|
{ "playing", audio_playing },
|
||||||
|
{ "mix", audio_mix },
|
||||||
|
{ "interpolation", audio_interpolation },
|
||||||
|
{ "speed", audio_speed },
|
||||||
|
{ "stop", audio_stop },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_audio_lib(lua_State *L) {
|
||||||
|
luaL_newmetatable(L, "LAudio");
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_setfield(L, -2, "__index");
|
||||||
|
luaL_setfuncs(L, audio_object_methods, 0);
|
||||||
|
|
||||||
|
luaL_newlib(L, audio_lib);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_audio_lib(lua_State *L) {
|
||||||
|
isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success
|
||||||
|
|
||||||
|
luaL_requiref(L, "ctr.audio", luaopen_audio_lib, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unload_audio_lib(lua_State *L) {
|
||||||
|
if (isAudioInitialized) ndspExit();
|
||||||
|
}
|
||||||
11
source/ctr.c
11
source/ctr.c
|
|
@ -91,6 +91,14 @@ The `ctr.cam` module.
|
||||||
*/
|
*/
|
||||||
void load_cam_lib(lua_State *L);
|
void load_cam_lib(lua_State *L);
|
||||||
|
|
||||||
|
/***
|
||||||
|
The `ctr.audio` module.
|
||||||
|
@table audio
|
||||||
|
@see ctr.audio
|
||||||
|
*/
|
||||||
|
void load_audio_lib(lua_State *L);
|
||||||
|
void unload_audio_lib(lua_State *L);
|
||||||
|
|
||||||
/***
|
/***
|
||||||
Return whether or not the program should continue.
|
Return whether or not the program should continue.
|
||||||
@function run
|
@function run
|
||||||
|
|
@ -132,7 +140,8 @@ struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); }
|
||||||
{ "qtm", load_qtm_lib, NULL },
|
{ "qtm", load_qtm_lib, NULL },
|
||||||
{ "cfgu", load_cfgu_lib, NULL },
|
{ "cfgu", load_cfgu_lib, NULL },
|
||||||
{ "socket", load_socket_lib, NULL },
|
{ "socket", load_socket_lib, NULL },
|
||||||
{ "cam", load_cam_lib, NULL },
|
{ "cam", load_cam_lib, NULL },
|
||||||
|
{ "audio", load_audio_lib, unload_audio_lib },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ The `gfx` module.
|
||||||
|
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
|
|
||||||
bool isGfxInitialised = false;
|
bool isGfxInitialized = false;
|
||||||
bool is3DEnabled = false; //TODO: add a function for this in the ctrulib/sf2dlib.
|
bool is3DEnabled = false; //TODO: add a function for this in the ctrulib/sf2dlib.
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
|
@ -459,7 +459,7 @@ void load_gfx_lib(lua_State *L) {
|
||||||
sf2d_init();
|
sf2d_init();
|
||||||
sftd_init();
|
sftd_init();
|
||||||
|
|
||||||
isGfxInitialised = true;
|
isGfxInitialized = true;
|
||||||
|
|
||||||
luaL_requiref(L, "ctr.gfx", luaopen_gfx_lib, 0);
|
luaL_requiref(L, "ctr.gfx", luaopen_gfx_lib, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@
|
||||||
void load_ctr_lib(lua_State *L);
|
void load_ctr_lib(lua_State *L);
|
||||||
void unload_ctr_lib(lua_State *L);
|
void unload_ctr_lib(lua_State *L);
|
||||||
|
|
||||||
bool isGfxInitialised;
|
bool isGfxInitialized;
|
||||||
|
|
||||||
// Display an error
|
// Display an error
|
||||||
void error(const char *error) {
|
void error(const char *error) {
|
||||||
if (!isGfxInitialised) gfxInitDefault();
|
if (!isGfxInitialized) gfxInitDefault();
|
||||||
gfxSet3D(false);
|
gfxSet3D(false);
|
||||||
|
|
||||||
consoleInit(GFX_TOP, NULL);
|
consoleInit(GFX_TOP, NULL);
|
||||||
|
|
@ -28,7 +28,7 @@ void error(const char *error) {
|
||||||
gspWaitForVBlank();
|
gspWaitForVBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isGfxInitialised) gfxExit();
|
if (!isGfxInitialized) gfxExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue