1
0
Fork 0
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:
Reuh 2015-12-27 19:54:58 +01:00
parent 716c42b849
commit bda9de4d1c
10 changed files with 985 additions and 17 deletions

View file

@ -55,7 +55,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
ASFLAGS := -g $(ARCH)
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
@ -142,7 +142,7 @@ $(BUILD):
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
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:
@make -C libs/sf2dlib/libsf2d build

View file

@ -4,4 +4,6 @@ libjpeg-*
libpng-*
sqlite-*
zlib-*
libogg-*
libvorbis-*
build/

View file

@ -28,6 +28,16 @@ ZLIB_VERSION := $(ZLIB)-1.2.8
ZLIB_SRC := $(ZLIB_VERSION).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 PATH := $(DEVKITARM)/bin:$(PATH)
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
@$(MAKE) -C $(FREETYPE_VERSION)
@make create_build_dir
@cp -srf $(CURDIR)/freetype-2.6/include/. $(CURDIR)/build/include
@cp -sf $(CURDIR)/freetype-2.6/objs/.libs/libfreetype.a $(CURDIR)/build/lib/libfreetype.a
@cp -srf $(CURDIR)/$(FREETYPE_VERSION)/include/. $(CURDIR)/build/include
@cp -sf $(CURDIR)/$(FREETYPE_VERSION)/objs/.libs/libfreetype.a $(CURDIR)/build/lib/libfreetype.a
$(LIBEXIF): $(LIBEXIF_SRC)
@[ -d $(LIBEXIF_VERSION) ] || tar -xf $<
@ -81,8 +91,8 @@ $(LIBJPEGTURBO): $(LIBJPEGTURBO_SRC)
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
@$(MAKE) CFLAGS+="\"-Drandom()=rand()\"" -C $(LIBJPEGTURBO_VERSION)
@make create_build_dir
@cp -sf $(CURDIR)/libjpeg-turbo-*/*.h $(CURDIR)/build/include
@cp -sf $(CURDIR)/libjpeg-turbo-*/.libs/libjpeg.a $(CURDIR)/build/lib/libjpeg.a
@cp -sf $(CURDIR)/$(LIBJPEGTURBO_VERSION)/*.h $(CURDIR)/build/include
@cp -sf $(CURDIR)/$(LIBJPEGTURBO_VERSION)/.libs/libjpeg.a $(CURDIR)/build/lib/libjpeg.a
$(LIBPNG): $(LIBPNG_SRC)
@[ -d $(LIBPNG_VERSION) ] || tar -xf $<
@ -90,8 +100,8 @@ $(LIBPNG): $(LIBPNG_SRC)
./configure --prefix=$(PORTLIBS) --host=arm-none-eabi --disable-shared --enable-static
@$(MAKE) -C $(LIBPNG_VERSION)
@make create_build_dir
@cp -sf $(CURDIR)/libpng-*/*.h $(CURDIR)/build/include
@cp -sf $(CURDIR)/libpng-*/.libs/*.a $(CURDIR)/build/lib/libpng.a
@cp -sf $(CURDIR)/$(LIBPNG_VERSION)/*.h $(CURDIR)/build/include
@cp -sf $(CURDIR)/$(LIBPNG_VERSION)/.libs/*.a $(CURDIR)/build/lib/libpng.a
# sqlite won't work with -ffast-math
$(SQLITE): $(SQLITE_SRC)
@ -107,8 +117,26 @@ $(ZLIB): $(ZLIB_SRC)
CHOST=arm-none-eabi ./configure --static --prefix=$(PORTLIBS)
@$(MAKE) -C $(ZLIB_VERSION)
@make create_build_dir
@cp -sf $(CURDIR)/zlib-*/*.h $(CURDIR)/build/include
@cp -sf $(CURDIR)/zlib-*/libz.a $(CURDIR)/build/lib/libz.a
@cp -sf $(CURDIR)/$(ZLIB_VERSION)/*.h $(CURDIR)/build/include
@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
$(ZLIB_SRC):
@ -129,6 +157,12 @@ $(LIBPNG_SRC):
$(SQLITE_SRC):
wget -O $@ $(SQLITE_DOWNLOAD)
$(LIBOGG_SRC):
wget -O $@ $(LIBOGG_DOWNLOAD)
$(LIBVORBIS_SRC):
wget -O $@ $(LIBVORBIS_DOWNLOAD)
install-zlib:
@$(MAKE) -C $(ZLIB_VERSION) install
@ -138,6 +172,8 @@ install:
@[ ! -d $(LIBJPEGTURBO_VERSION) ] || $(MAKE) -C $(LIBJPEGTURBO_VERSION) install
@[ ! -d $(LIBPNG_VERSION) ] || $(MAKE) -C $(LIBPNG_VERSION) install
@[ ! -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:
@$(RM) -r $(FREETYPE_VERSION)
@ -146,5 +182,7 @@ clean:
@$(RM) -r $(LIBPNG_VERSION)
@$(RM) -r $(SQLITE_VERSION)
@$(RM) -r $(ZLIB_VERSION)
@$(RM) -r $(LIBOGG_VERSION)
@$(RM) -r $(LIBVORBIS_VERSION)
@rm -rf $(CURDIR)/build
@rm -f $(CURDIR)/*.tar.*

View file

@ -8,6 +8,22 @@ repeat
local file = require("openfile")("Choose a Lua file to execute", nil, ".lua", "exist")
if file then
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
until not file

View 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()

Binary file not shown.

851
source/audio.c Normal file
View 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, &current_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();
}

View file

@ -91,6 +91,14 @@ The `ctr.cam` module.
*/
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.
@function run
@ -132,7 +140,8 @@ struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); }
{ "qtm", load_qtm_lib, NULL },
{ "cfgu", load_cfgu_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 }
};

View file

@ -16,7 +16,7 @@ The `gfx` module.
#include "font.h"
bool isGfxInitialised = false;
bool isGfxInitialized = false;
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();
sftd_init();
isGfxInitialised = true;
isGfxInitialized = true;
luaL_requiref(L, "ctr.gfx", luaopen_gfx_lib, 0);
}

View file

@ -7,11 +7,11 @@
void load_ctr_lib(lua_State *L);
void unload_ctr_lib(lua_State *L);
bool isGfxInitialised;
bool isGfxInitialized;
// Display an error
void error(const char *error) {
if (!isGfxInitialised) gfxInitDefault();
if (!isGfxInitialized) gfxInitDefault();
gfxSet3D(false);
consoleInit(GFX_TOP, NULL);
@ -28,7 +28,7 @@ void error(const char *error) {
gspWaitForVBlank();
}
if (!isGfxInitialised) gfxExit();
if (!isGfxInitialized) gfxExit();
}
// Main loop