Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
547 lines (461 sloc) 12.7 KB
#include "luaav_state.hpp"
#include "lua_utility.h"
#include <stack>
void msg_synth_play(al_sec t, Synth * self, Context * ctx);
void msg_synth_stop(al_sec t, Synth * self);
void msg_synth_delete(al_sec t, Synth * self);
void msgAudioReleaseArrayHandle(al_sec t, ArrayHandle handle) {
handle->release();
}
void msgSetArrayHandle(al_sec t, ArrayHandle * dst, ArrayHandle handle) {
if (*dst) {
luaav_global_outbox().send(t, msgAudioReleaseArrayHandle, *dst);
}
*dst = handle;
}
static void msg_context_addsignal(al_sec t, Context * self, Signal * u) {
self->addSignal(u);
}
#define LuaAV_Bus_Metatable "LuaAV_Bus_Metatable"
static int bus_index(lua_State * L) {
// get num channels
int len = lua_objlen(L, 1);
if (!lua_isnumber(L, 2))
luaL_error(L, "bus can only accept integer indices");
int chan = abs(lua_tointeger(L, 2));
int n = 1+((chan+len-1) % len);
lua_rawgeti(L, 1, n);
return 1;
}
static int bus_newindex(lua_State * L) {
luaL_error(L, "bus is read only");
return 0;
}
static int signal_create(lua_State * L, Context * ctx, int chans) {
LuaAVState * S = luaav_fromstate(L);
lua_createtable(L, chans, 0);
for (int i=1; i<=chans; i++) {
Signal * u = new Signal();
Glue<Signal>::push(L, u); //lua_pushlightuserdata(L, b);
lua_rawseti(L, -2, i);
// send msg to add bus:
S->inbox().send(ctx->now(), msg_context_addsignal, ctx, u);
}
luaL_getmetatable(L, LuaAV_Bus_Metatable);
lua_setmetatable(L, -2);
return 1;
}
#pragma mark Context
template<>
const char * Glue<Context>::mt_name(lua_State * L) {
return "LuaAV_Context_Metatable";
}
template<>
const char * Glue<Context>::usr_name() {
return "Context";
}
template<>
void Glue<Context> :: usr_push(lua_State * L, Context * self) {
self->retain();
//printf("Context push %d\n", self->luarefs);
// context is a stack index 1;
lua_newtable(L);
if (lua_gettop(L) > 2) {
// inherit parent:
lua_pushvalue(L, 1);
lua_setfield(L, -2, "_parent");
}
lua_newtable(L);
lua_setfield(L, -2, "_busses");
lua_setfenv(L, -2);
}
template<>
void Glue<Context> :: usr_gc(lua_State * L, Context * self) {
LuaAVState * S = luaav_fromstate(L);
// clear metatable:
lua_pushnil(L);
lua_setmetatable(L, 1);
//printf("Context __gc %d--\n", self->luarefs);
// if Lua no longer references the Node, trigger the stopping of this node:
if (self->release()) {
//printf("releasing Context\n");
S->inbox().send(self->now(), msg_synth_stop, (Synth *)self);
}
}
template<> bool Glue<Context>::usr_has_index() { return true; }
template<>
void Glue<Context>::usr_index(lua_State * L, Context * u) {
lua_getmetatable(L, 1);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if(lua_isnil(L, -1)) {
lua_pop(L, 2);
lua_getfenv(L, 1);
lua_getfield(L, -1, "_busses");
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if(lua_isnil(L, -1)) {
lua_pop(L, 2);
lua_getfield(L, -1, "_parent");
if(!lua_isnil(L, -1)) {
lua_pushvalue(L, 2);
lua_gettable(L, -2);
}
}
}
}
template<>
void Glue<Context>::usr_newindex(lua_State * L, Context * u) {
int chans = luaL_checknumber(L, 3);
lua_getfenv(L, 1);
lua_getfield(L, -1, "_busses");
int busses = lua_gettop(L);
lua_pushvalue(L, 2);
lua_gettable(L, -2);
if (!lua_isnoneornil(L, -1)) {
//luaL_error(L, "cannot redefine existing %s", lua_tostring(L, 2));
return; // returns existing bus
}
lua_settop(L, busses);
signal_create(L, u, chans);
// set in fenv:
lua_pushvalue(L, 2); // key
lua_pushvalue(L, -2); // Bus
lua_rawset(L, busses);
}
template<> Context * Glue<Context>::usr_new(lua_State * L) {
Context * parent = Glue<Context>::to(L, 1);
std::string name(luaL_optstring(L, 2, "LContext"));
if (parent) {
Context * c = new Context(parent, name);
parent->inbox().send(parent->now(), msg_synth_play, (Synth *)c, parent);
return c;
} else {
return NULL;
}
}
template<> void Glue<Context>::usr_mt(lua_State * L) {
static luaL_reg methods[] = {
{ "stop", Glue<Context>::gc },
{ "close", Glue<Context>::gc },
{ NULL, NULL }
};
for(int i=0; methods[i].name; i++) {
lua_pushcfunction(L, methods[i].func);
lua_setfield(L, -2, methods[i].name);
}
}
Context * context_fromstate(lua_State * L, int idx) {
Context * c = Glue<Context>::to(L, idx);
if (!c) {
c = luaav_fromstate(L);
}
return c;
}
Context :: ~Context() {
// release nodes:
for (std::list<Synth *>::iterator it=mSynths.begin(); it!=mSynths.end(); it++) {
Synth * n = *it;
n->mStateL = n->mStateA = Synth::DEAD;
msg_synth_delete(0, n);
}
// release busses:
for (int i=0; i<mSignals.size(); i++) {
//printf("release bus %d\n", i);
mSignals[i]->release(); // except for the main IO signals that is...
}
}
void Context :: clear_synths() {
for (std::list<Synth *>::iterator it=mSynths.begin(); it!=mSynths.end();) {
Synth& n = **it++;
n.stop();
}
}
void Context :: audioCB() {
// clear signals:
for (int i=0; i<mSignals.size(); i++) {
mSignals[i]->zero();
}
// executing incoming messages:
luaav_audio_config cfg = luaav_audio_config_current();
al_sec until = (al_sec)(root()->elapsed + LUAAV_AUDIO_BLOCKSIZE) / cfg.samplerate;
inbox().executeUntil(until);
// execute sub-nodes:
// todo: use a schedule-list & flush, rather than pushing stack...
for (std::list<Synth *>::iterator it=mSynths.begin(); it!=mSynths.end();) {
Synth& n = **it;
if (!(n.mStateA == Synth::PLAYING || n.mStateA == Synth::STOPPING)) {
printf("ERROR: node should not be here\n");
continue;
}
int s = MAX(n.mStart, mStart);
int e = MIN(n.mEnd, mEnd);
n.prepare(s, e);
n.cbaudio(&n);
if (n.mStateA == Synth::STOPPING) {
//printf("(audio) stopped playing node %p\n", &n);
it = mSynths.erase(it);
n.mContext = NULL;
if (n.mLuaRefs == 0) {
// delete immediately:
n.mStateA = n.mStateL = Synth::DEAD;
// send message to main thread:
luaav_global_outbox().send(0, msg_synth_delete, &n);
} else {
n.mStateA = Synth::STOPPED;
}
} else {
n.mStart = 0;
it++;
}
}
}
int node_userdata_initialize(lua_State * L, int node_idx, int params_idx) {
// grab initialization params:
lua_pushnil(L); /* first key */
while (lua_next(L, params_idx) != 0) {
lua_pushvalue(L, -2); // extra copy of key for next loop
lua_insert(L, -2);
/* uses 'key' (at index -2) and 'value' (at index -1) */
lua_settable(L, node_idx);
}
return 0;
}
int lua_audio_Bus(lua_State * L) {
if (!lua_isuserdata(L, 1)) {
lua_getfield(L, LUA_REGISTRYINDEX, LUAAV_AUDIO_ROOT);
lua_insert(L, 1);
}
Context * ctx = Glue<Context>::to(L, 1);
luaL_checkstring(L, 2);
if (!lua_isnumber(L, 3)) {
lua_settop(L, 2);
lua_pushnumber(L, 1);
}
Glue<Context>::usr_newindex(L, ctx);
return 1;
}
int latency(lua_State * L) {
luaav_state * S = luaav_fromstate(L);
if (lua_isnumber(L, 1)) {
// change latency. Should be safe to do from main thread?
S->audio_latency = fabs(lua_tonumber(L, 1));
}
lua_pushnumber(L, S->audio_latency);
return 1;
}
int samplerate(lua_State * L) {
const luaav_audio_config cfg = luaav_audio_config_current();
lua_pushnumber(L, cfg.samplerate);
return 1;
}
int lua_audio_record(lua_State * L) {
LuaAVState * S = luaav_fromstate(L);
bool recording = lua_toboolean(L, 1);
if (recording) {
// or an optional string?
const char * str = luaL_checkstring(L, 2);
S->record(true, str);
} else {
S->record(false);
}
return 0;
}
int lua_audio_scope(lua_State * L) {
LuaAVState * S = luaav_fromstate(L);
if(lua_isnumber(L, 1)) {
int cidx = lua::to<int>(L, 1);
al::ArrayWrapper *c = S->scope.channel(cidx);
if(c) {
Glue<al::ArrayWrapper>::push(L, c);
}
else {
lua_pushnil(L);
}
return 1;
}
else if(lua_isboolean(L, 1)) {
bool enable = lua::to<bool>(L, 1);
S->scoping = enable;
}
else {
lua::push<int>(L, S->scope.position());
return 1;
}
return 0;
}
int lua_audio_clear(lua_State * L) {
LuaAVState * S = luaav_fromstate(L);
S->clear_synths();
return 0;
}
extern "C" int luaopen_audio(lua_State * L) {
const luaav_audio_config cfg = luaav_audio_config_current();
//Glue<Node>::define(L);
//Glue<Bus1>::define(L);
Glue<Signal>::define(L);
Glue<Context>::define(L);
LuaAVState * S = luaav_fromstate(L);
// define bus metatable:
luaL_newmetatable(L, LuaAV_Bus_Metatable);
lua_pushcfunction(L, bus_index); lua_setfield(L, -2, "__index");
lua_pushcfunction(L, bus_newindex); lua_setfield(L, -2, "__newindex");
lua_pop(L, 1);
struct luaL_reg lib[] = {
{ "latency", latency },
{ "samplerate", samplerate },
{ "Bus", lua_audio_Bus },
{ "record", lua_audio_record },
{ "scope", lua_audio_scope },
{ "clear", lua_audio_clear },
{ NULL, NULL },
};
luaL_register(L, "audio", lib);
Glue<Context>::register_ctor(L);
// if (S->A == NULL) {
// //printf("creating root context\n");
// S->A = new Context(S, NULL);
// }
// create a context:
Glue<Context>::push(L, S);
// // use it as upvalue for audio_play:
// lua_pushvalue(L, -1);
// lua_pushcclosure(L, audio_play, 1);
// lua_setfield(L, -3, "play");
// // install default output bus:
// lua_pushnumber(L, cfg.outchannels);
// lua_setfield(L, -2, "out");
// install pointers to raw IO busses
lua_getfenv(L, -1);
lua_getfield(L, -1, "_busses");
lua_createtable(L, cfg.outchannels, 0);
for (int i=0; i<cfg.outchannels; i++) {
//lua_pushlightuserdata(L, (void *)(S->outbusses+i));
Glue<Signal>::push(L, S->outsignals+i);
lua_rawseti(L, -2, i+1);
}
luaL_getmetatable(L, LuaAV_Bus_Metatable);
lua_setmetatable(L, -2);
lua_setfield(L, -2, "out");
lua_createtable(L, cfg.inchannels, 0);
for (int i=0; i<cfg.inchannels; i++) {
//lua_pushlightuserdata(L, (void *)(S->inbusses+i));
Glue<Signal>::push(L, S->insignals+i);
lua_rawseti(L, -2, i+1);
}
luaL_getmetatable(L, LuaAV_Bus_Metatable);
lua_setmetatable(L, -2);
lua_setfield(L, -2, "in");
lua_pop(L, 2);
lua_pushvalue(L, -1);
lua_setfield(L, LUA_REGISTRYINDEX, LUAAV_AUDIO_ROOT);
lua_setfield(L, -2, "root");
// make sure all
return 1;
}
#pragma mark Scope
Scope :: Scope(int nchannels, int bufferSize, int nbuffers)
: mSamplePos(0),
mBufferSize(bufferSize),
mNBuffers(nbuffers),
mNSamples(bufferSize*nbuffers)
{
for(int i=0; i < nchannels; i++) {
al::ArrayWrapper *c = new al::ArrayWrapper;
c->formatAligned(1, AlloFloat32Ty, bufferSize, nbuffers, 1);
c->retain();
mChannels.push_back(c);
}
}
Scope :: ~Scope() {
while(mChannels.size() > 0) {
al::ArrayWrapper *c = mChannels.back();
mChannels.pop_back();
c->release();
}
}
void Scope :: nchannels(int n) {
if(n > mChannels.size()) {
for(int i=mChannels.size(); i < n; i++) {
al::ArrayWrapper *c = new al::ArrayWrapper;
c->formatAligned(1, AlloFloat32Ty, mBufferSize, mNBuffers, 1);
c->retain();
mChannels.push_back(c);
}
}
else if(n < mChannels.size()) {
for(int i=mChannels.size()-1; i >= n; i--) {
al::ArrayWrapper *c = mChannels.back();
mChannels.pop_back();
c->release();
}
}
}
void Scope :: bufferSize(int sz) {
mNSamples = mBufferSize*mNBuffers;
if(sz != mBufferSize && sz > 0) {
for(int i=0; i < mChannels.size(); i++) {
mChannels[i]->formatAligned(1, AlloFloat32Ty, mBufferSize, mNBuffers, 1);
}
}
}
void Scope :: advance(int n) {
mSamplePos = (mSamplePos+n)%mNSamples;
}
/*
void Scope :: record(int channel, int n, float *samples) {
float *c = ((float *)mChannels[channel]->data.ptr);
for(int i=0, j=mSamplePos; i < n; j++, i++) {
j = j%mBufferSize;
c[j] = samples[i];
}
}
*/
void Scope :: write(float leftSample, float rightSample) {
float *left = ((float *)mChannels[0]->data.ptr);
float *right = ((float *)mChannels[1]->data.ptr);
left[mSamplePos] = leftSample;
right[mSamplePos] = rightSample;
}
#pragma mark Buffers
static std::stack<sample *> gBuffers;
static int gBuffersAllocated = 0;
sample * luaav_audiobuffer_request() {
sample * x;
if (gBuffers.empty()) {
x = new sample[LUAAV_AUDIO_BLOCKSIZE];
//x = (sample *)malloc(sizeof(sample) * LUAAV_AUDIO_BLOCKSIZE);
gBuffersAllocated++;
//printf("ALLOCATED BUFFER %i\n", gBuffersAllocated);
luaav_audiobuffer_zero(x);
} else {
x = gBuffers.top();
gBuffers.pop();
}
luaav_audiobuffer_zero(x);
return x;
}
void luaav_audiobuffer_release(sample * x) {
if (x) {
//printf("\nRECYCLED BUFFER %i\n", gBuffersAllocated);
gBuffers.push(x);
}
}
void luaav_audiobuffer_zero(sample * x) {
//memset(x, 0, sizeof(sample) * LUAAV_AUDIO_BLOCKSIZE);
for (int i=0; i<LUAAV_AUDIO_BLOCKSIZE; i++) { x[i] = 0; }
}
// returns the current buffer use count:
int luaav_audio_debug_buffers(lua_State * L) {
lua_pushnumber(L, gBuffersAllocated);
lua_pushnumber(L, gBuffers.size());
return 2;
}
extern "C" int luaopen_audio_debug(lua_State * L) {
struct luaL_reg lib[] = {
{"buffers", luaav_audio_debug_buffers },
{ NULL, NULL },
};
luaL_register(L, "audio.debug", lib);
return 1;
}