musiclight2/main.c
Thomas Kolb d4a2e7ef4c Added lua scripting capabilities
- basic configuration is now done via a lua script
- all the animation is generated by a lua script (see pulsetunnel.lua
  and vumeter.lua for examples)
- basic calculations (FFT, RMS) are done in C and accessible on demand
  from the lua scripts
2012-07-28 01:30:20 +02:00

280 lines
6.4 KiB
C

/*
* vim: sw=2 ts=2 expandtab
*
* THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"):
* <cfr34k@tkolb.de> wrote this file. As long as you retain this notice you can
* do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a pizza in return. - Thomas Kolb
*/
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "lua_utils.h"
#include "lua_wrappers.h"
#include "fft.h"
#include "utils.h"
#include "ws2801.h"
#include "config.h"
// Frames per second
#define FPS ((double)BUFFER_PARTS * SAMPLE_RATE / BLOCK_LEN)
// Number of new samples put into the buffer each frame
#define READ_SAMPLES (BLOCK_LEN / BUFFER_PARTS)
double fft[BLOCK_LEN];
sample signal[BLOCK_LEN];
double rms;
double lastUpdateTime = 0;
sem_t fftSemaphore;
int running = 1;
void* fft_thread(void *param) {
sample buffer[BLOCK_LEN];
sample block[BLOCK_LEN];
double fftOutReal[BLOCK_LEN], fftOutImag[BLOCK_LEN];
double tmpFFT[BLOCK_LEN];
double tmpRMS;
double nextFrame = get_hires_time() + 0.05;
double curTime;
int i;
init_fft();
while(running) {
// shift the buffer left
memmove(buffer, buffer + READ_SAMPLES,
(BLOCK_LEN - READ_SAMPLES) * sizeof(sample));
size_t ret = fread(buffer + (BLOCK_LEN - READ_SAMPLES),
sizeof(sample), READ_SAMPLES, stdin);
if(ret != READ_SAMPLES) {
break;
}
memcpy(block, buffer, BLOCK_LEN * sizeof(sample));
apply_hanning(block);
fft_transform(block, fftOutReal, fftOutImag);
complex_to_absolute(fftOutReal, fftOutImag, tmpFFT);
tmpRMS = 0;
for(i = 0; i < BLOCK_LEN; i++) {
tmpRMS += buffer[i]*buffer[i];
}
tmpRMS = sqrt(tmpRMS/BLOCK_LEN);
// --- SAFE SECTION ---
sem_wait(&fftSemaphore);
memcpy(fft, tmpFFT, sizeof(fft));
memcpy(signal, buffer, sizeof(signal));
rms = tmpRMS;
curTime = get_hires_time();
lastUpdateTime = curTime;
sem_post(&fftSemaphore);
// --- END SAFE SECTION ---
if(curTime > nextFrame + 0.05) {
printf("Frame too late! Skipping.\n");
nextFrame = -1;
}
if(curTime < nextFrame - 0.05) {
printf("Frame too early.\n");
nextFrame = -1;
}
if(nextFrame < 0) {
printf("Frame time reset.\n");
nextFrame = curTime;
}
nextFrame += 1.000/FPS;
sleep_until(nextFrame);
}
return NULL;
}
double gamma_correct(double d, double gamma) {
return pow(d, gamma);
}
int main(int argc, char **argv) {
double nextFrame = get_hires_time() + LED_INTERVAL;
int i;
pthread_t fftThread;
int active = 1;
double *red;
double *green;
double *blue;
int useFading, fadeStep;
if(argc < 2) {
fprintf(stderr, "LUA script file must be given as command line argument!\n");
return 1;
}
// initialize lua
lua_State *L = lua_open();
// load the lua libraries
luaL_openlibs(L);
// register local functions
lua_register_funcs(L);
// load the configuration from "config.lua"
if(luaL_dofile(L, "config.lua")) {
lua_showerror(L, "luaL_dofile(config.lua) failed.");
return 1;
}
lua_getglobal(L, "WS2801_HOST");
if(!lua_isstring(L, -1)) return 2;
const char *host = lua_tostring(L, -1);
lua_getglobal(L, "WS2801_PORT");
if(!lua_isnumber(L, -1)) return 2;
unsigned short port = lua_tointeger(L, -1);
lua_getglobal(L, "GAMMA");
if(!lua_isnumber(L, -1)) return 2;
double gamma = lua_tonumber(L, -1);
lua_getglobal(L, "NUM_MODULES");
if(!lua_isnumber(L, -1)) return 2;
int num_modules = lua_tointeger(L, -1);
lua_getglobal(L, "CENTER_MODULE");
if(!lua_isnumber(L, -1)) return 2;
int center_module = lua_tointeger(L, -1);
// allocate arrays
red = malloc(num_modules * sizeof(double));
green = malloc(num_modules * sizeof(double));
blue = malloc(num_modules * sizeof(double));
// load and initialize the script
if(luaL_loadfile(L, argv[1])) {
lua_showerror(L, "luaL_loadfile(cmdline_argument) failed.");
}
// priming call: read the lua file to make functions known
if(lua_pcall(L, 0, 0, 0)) {
lua_showerror(L, "lua_pcall failed.");
}
// call the init function
lua_getglobal(L, "init");
lua_pushnumber(L, num_modules);
lua_pushnumber(L, center_module);
if(lua_pcall(L, 2, 1, 0)) {
lua_showerror(L, "lua_pcall(init) failed.");
}
fadeStep = lua_tointeger(L, -1);
useFading = fadeStep > 0;
if(useFading) {
ws2801_set_fadestep(fadeStep);
printf("Fading enabled with fadestep %i.\n", fadeStep);
}
// initialize the WS2801 library
printf("Connecting to %s:%i\n", host, port);
ws2801_init(host, port);
// create semaphores
sem_init(&fftSemaphore, 0, 1);
// run the fft thread
pthread_create(&fftThread, NULL, fft_thread, NULL);
while(running) {
if(active) {
// call the periodic() function from LUA
lua_getglobal(L, "periodic");
if(lua_pcall(L, 0, 3, 0)) { // no arguments, 3 return values
lua_showerror(L, "lua_pcall(periodic) failed.");
}
// read the return values (reverse order, as lua uses a stack)
lua_readdoublearray(L, blue, num_modules);
lua_readdoublearray(L, green, num_modules);
lua_readdoublearray(L, red, num_modules);
if(useFading) {
for(i = 0; i < num_modules; i++) {
ws2801_fade_color(i,
255 * gamma_correct(red[i], gamma),
255 * gamma_correct(green[i], gamma),
255 * gamma_correct(blue[i], gamma));
}
ws2801_commit();
} else {
for(i = 0; i < num_modules; i++) {
ws2801_set_color(i,
255 * gamma_correct(red[i], gamma),
255 * gamma_correct(green[i], gamma),
255 * gamma_correct(blue[i], gamma));
}
ws2801_commit();
}
if(lastUpdateTime < nextFrame - 1) {
printf("Idle for 1 second -> stopping updates.\n");
for(i = 0; i < num_modules; i++) {
ws2801_fade_color(i, 20, 20, 20);
}
ws2801_commit();
active = 0;
}
} else if(lastUpdateTime > nextFrame - 1) {
printf("Resuming updates.\n");
active = 1;
}
nextFrame += LED_INTERVAL;
sleep_until(nextFrame);
}
ws2801_shutdown();
// free arrays
free(red);
free(green);
free(blue);
pthread_join(fftThread, NULL);
lua_close(L);
return 0;
}