rewrite
This commit is contained in:
parent
09c4686f79
commit
2ea30ea3d3
16 changed files with 722 additions and 237 deletions
6
README
6
README
|
@ -4,7 +4,11 @@ This is a simple c lib for controlling your fnordlichts.
|
|||
It's written by Steffen Vogel <info@steffenvogel.de> and licenced under GPLv3 or newer.
|
||||
Please contact me by mail for bug reports or annotations.
|
||||
|
||||
The source is including a small cli program and a vu meter to show your audio level on the lights.
|
||||
|
||||
To connect the fnordlichts to your computer you need a serial port or an USB to RS232 adapter and a RS232 to TTL level shifter (MAX232).
|
||||
Connect your CTS line to the interrupt line of the fnordlicht bus to be able to count the devices on the bus.
|
||||
|
||||
|
||||
fnctl & fnvum
|
||||
-------------------------------------------------------------------------------
|
||||
The source is including a small cli program and a vu meter to show your audio level on the lights.
|
||||
|
|
4
color.h
4
color.h
|
@ -26,10 +26,6 @@
|
|||
#ifndef COLOR_H
|
||||
#define COLOR_H
|
||||
|
||||
#if defined(PWM_CHANNELS) && PWM_CHANNELS != 3
|
||||
#error "PWM_CHANNELS is not 3, this is unsupported!"
|
||||
#endif
|
||||
|
||||
struct rgb_color_t
|
||||
{
|
||||
union {
|
||||
|
|
BIN
fnctl
BIN
fnctl
Binary file not shown.
327
fnctl.c
327
fnctl.c
|
@ -10,8 +10,26 @@
|
|||
|
||||
#include "libfn.h"
|
||||
|
||||
typedef struct { char * name; char * description; uint8_t cmd; } command_t;
|
||||
static command_t commands[] = {
|
||||
/* local commands (>= 0xA0) */
|
||||
#define LOCAL_CMD_EEPROM 0xA0
|
||||
#define LOCAL_CMD_COUNT 0xA1
|
||||
|
||||
#define DEFAULT_HOST "localhost"
|
||||
#define DEFAULT_PORT "7970"
|
||||
#define DEFAULT_DEVICE "/dev/ttyUSB0"
|
||||
|
||||
struct command_t {
|
||||
char * name;
|
||||
char * description;
|
||||
uint8_t cmd;
|
||||
};
|
||||
|
||||
enum connection_t {
|
||||
RS232,
|
||||
NET
|
||||
};
|
||||
|
||||
static struct command_t commands[] = {
|
||||
{"fade", "set color/fade to color", REMOTE_CMD_FADE_RGB},
|
||||
{"save", "save color to EEPROM", REMOTE_CMD_SAVE_RGB},
|
||||
{"modify", "modify current color", REMOTE_CMD_MODIFY_CURRENT},
|
||||
|
@ -20,7 +38,9 @@ static command_t commands[] = {
|
|||
{"powerdown", "power down the device", REMOTE_CMD_POWERDOWN},
|
||||
{"config", "configure startup & offsets", REMOTE_CMD_CONFIG_OFFSETS},
|
||||
{"reset", "reset fnordlichter", REMOTE_CMD_BOOTLOADER},
|
||||
{0, 0} /* stop condition for iterator */
|
||||
{"eeprom", "put sequence to EEPROM", LOCAL_CMD_EEPROM},
|
||||
{"count", "count modules on the bus", LOCAL_CMD_COUNT},
|
||||
{0} /* stop condition for iterator */
|
||||
};
|
||||
|
||||
static struct option long_options[] = {
|
||||
|
@ -28,16 +48,30 @@ static struct option long_options[] = {
|
|||
{"step", required_argument, 0, 's'},
|
||||
{"address", required_argument, 0, 'a'},
|
||||
{"mask", required_argument, 0, 'm'},
|
||||
{"slot", required_argument, 0, 't'},
|
||||
{"slot", required_argument, 0, 'w'},
|
||||
{"pause", required_argument, 0, 'p'},
|
||||
{"color", required_argument, 0, 'c'},
|
||||
{"start", required_argument, 0, 'f'},
|
||||
{"end", required_argument, 0, 't'},
|
||||
{"repeat", required_argument, 0, 'r'},
|
||||
{"help", required_argument, 0, 'h'},
|
||||
{"port", required_argument, 0, 'P'},
|
||||
{"host", required_argument, 0, 'H'},
|
||||
{"filename", required_argument, 0, 'F'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{0, 0, 0, 0} /* stop condition for iterator */
|
||||
{0} /* stop condition for iterator */
|
||||
};
|
||||
|
||||
struct rgb_color_t parse_color(char * identifier) {
|
||||
struct rgb_color_t color;
|
||||
if (strlen(identifier) != 6) {
|
||||
fprintf(stderr, "invalid color definition: %s", identifier);
|
||||
}
|
||||
|
||||
sscanf(identifier, "%2x%2x%2x", (unsigned int *) (&color.red), (unsigned int *) (&color.green), (unsigned int *) (&color.blue));
|
||||
return color;
|
||||
}
|
||||
|
||||
void print_cmd(struct remote_msg_t * msg) {
|
||||
printf("sending: ");
|
||||
|
||||
|
@ -52,7 +86,7 @@ void usage(char ** argv) {
|
|||
printf("usage: %s command [options]\n\n", argv[0]);
|
||||
printf(" following commands are available:\n");
|
||||
|
||||
command_t * cp = commands;
|
||||
struct command_t * cp = commands;
|
||||
while (cp->name) {
|
||||
printf("\t%s%s%s\n", cp->name, (strlen(cp->name) < 7) ? "\t\t" : "\t", cp->description);
|
||||
cp++;
|
||||
|
@ -68,17 +102,6 @@ void usage(char ** argv) {
|
|||
}
|
||||
}
|
||||
|
||||
struct rgb_color_t parse_color(char * color_def) {
|
||||
struct rgb_color_t color;
|
||||
|
||||
if (strlen(color_def) != 6)
|
||||
fprintf(stderr, "invalid color definition: %s", color_def);
|
||||
|
||||
sscanf(color_def, "%2x%2x%2x", (unsigned int *) (&color.red), (unsigned int *) (&color.green), (unsigned int *) (&color.blue));
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
/* options */
|
||||
uint8_t address = 255;
|
||||
|
@ -86,12 +109,19 @@ int main(int argc, char ** argv) {
|
|||
uint8_t slot = 0;
|
||||
uint8_t delay = 0;
|
||||
uint8_t pause = 0;
|
||||
char host[255];
|
||||
char port[255] = "7970";
|
||||
int verbose;
|
||||
|
||||
char mask[254] = "";
|
||||
char filename[1024] = "";
|
||||
|
||||
enum connection_t con_mode = RS232;
|
||||
char host[255] = DEFAULT_HOST;
|
||||
char port[255] = DEFAULT_PORT;
|
||||
char device[255] = DEFAULT_DEVICE;
|
||||
int verbose = 0;
|
||||
|
||||
struct rgb_color_t color;
|
||||
struct remote_msg_t msg;
|
||||
union program_params_t params;
|
||||
memset(&msg, 0, sizeof msg);
|
||||
|
||||
/* connection */
|
||||
|
@ -106,15 +136,17 @@ int main(int argc, char ** argv) {
|
|||
exit(-1);
|
||||
}
|
||||
|
||||
command_t * cp = commands;
|
||||
while (cp->name && strcmp(cp->name, argv[1]) != 0) { cp++; }
|
||||
struct command_t * cp = commands;
|
||||
while (cp->name && strcmp(cp->name, argv[1]) != 0) {
|
||||
cp++;
|
||||
}
|
||||
|
||||
/* parse cli arguments */
|
||||
while (1) {
|
||||
/* getopt_long stores the option index here. */
|
||||
int option_index = 0;
|
||||
|
||||
int c = getopt_long(argc, argv, "hva:m:d:s:d:p:c:P:H:", long_options, &option_index);
|
||||
int c = getopt_long(argc, argv, "hva:m:f:d:t:s:f:w:r:d:p:c:P:H:F:", long_options, &option_index);
|
||||
|
||||
/* Detect the end of the options. */
|
||||
if (c == -1)
|
||||
|
@ -126,8 +158,7 @@ int main(int argc, char ** argv) {
|
|||
break;
|
||||
|
||||
case 'm':
|
||||
fprintf(stderr, "not yet implemented\n");
|
||||
exit(-1);
|
||||
strcpy(mask, optarg);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
|
@ -138,7 +169,7 @@ int main(int argc, char ** argv) {
|
|||
delay = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
case 'w':
|
||||
slot = atoi(optarg);
|
||||
break;
|
||||
|
||||
|
@ -149,6 +180,30 @@ int main(int argc, char ** argv) {
|
|||
case 'c':
|
||||
color = parse_color(optarg);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
params.replay.start = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
params.replay.end = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
if (strcmp("none", optarg) == 0) {
|
||||
params.replay.repeat = REPEAT_NONE;
|
||||
}
|
||||
else if (strcmp("start", optarg) == 0) {
|
||||
params.replay.repeat = REPEAT_START;
|
||||
}
|
||||
else if (strcmp("reverse", optarg) == 0) {
|
||||
params.replay.repeat = REPEAT_REVERSE;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "invalid --repeat value: %s\n", optarg);
|
||||
exit(-1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'H': {
|
||||
char * ps = strrchr(optarg, ':');
|
||||
|
@ -159,34 +214,16 @@ int main(int argc, char ** argv) {
|
|||
else { /* without port */
|
||||
strcpy(host, optarg);
|
||||
}
|
||||
con_mode = NET;
|
||||
break;
|
||||
}
|
||||
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC; /* both IPv4 & IPv6 */
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
getaddrinfo(host, port, &hints, &res);
|
||||
|
||||
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
connect(fd, res->ai_addr, res->ai_addrlen);
|
||||
|
||||
if (fd < 0) {
|
||||
perror(port);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
fn_sync(fd);
|
||||
usleep(200000);
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
fd = open(optarg, O_RDWR | O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
perror(optarg);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
oldtio = fn_init(fd);
|
||||
strcpy(port, optarg);
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
strcpy(filename, optarg);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
|
@ -200,91 +237,179 @@ int main(int argc, char ** argv) {
|
|||
}
|
||||
}
|
||||
|
||||
/* connect to fnordlichter */
|
||||
if (con_mode == NET) {
|
||||
if (verbose) printf("connect via net: %s:%s\n", host, port);
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC; /* both IPv4 & IPv6 */
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
getaddrinfo(host, port, &hints, &res);
|
||||
|
||||
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
connect(fd, res->ai_addr, res->ai_addrlen);
|
||||
if (fd < 0) {
|
||||
perror(host);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (verbose) printf("connect via rs232: %s\n", device);
|
||||
fd = open(device, O_RDWR | O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
perror(port);
|
||||
exit(-1);
|
||||
}
|
||||
oldtio = fn_init(fd);
|
||||
}
|
||||
|
||||
fn_sync(fd);
|
||||
|
||||
/* check address */
|
||||
if (strlen(host) == 0 && address != 255 && address > fn_count_devices(fd)) {
|
||||
fprintf(stderr, "device with address %d not found\n", address);
|
||||
if (address > FN_MAX_DEVICES+1) {
|
||||
fprintf(stderr, "sorry, the fnordlicht bus can't address more the %d devices\n", FN_MAX_DEVICES);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
printf("command: %s: %s\n", cp->name, cp->description);
|
||||
printf("port: %s\n", port);
|
||||
printf("host: %s\n", host);
|
||||
printf("address: %d\n", address);
|
||||
printf("found %d fnordlichts\n", fn_count_devices(fd));
|
||||
}
|
||||
if (verbose) printf("command: %s (%s)\n", cp->name, cp->description);
|
||||
|
||||
switch (cp->cmd) {
|
||||
/* remote commands */
|
||||
case REMOTE_CMD_FADE_RGB: {
|
||||
struct remote_msg_fade_rgb_t * param = (void *) &msg;
|
||||
struct remote_msg_fade_rgb_t * cmsg = (void *) &msg;
|
||||
|
||||
cmsg->step = step;
|
||||
cmsg->delay = delay;
|
||||
cmsg->color = color;
|
||||
|
||||
param->step = step;
|
||||
param->delay = delay;
|
||||
param->color = color;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REMOTE_CMD_MODIFY_CURRENT: {
|
||||
struct remote_msg_modify_current_t * param = (void *) &msg;
|
||||
struct remote_msg_modify_current_t * cmsg = (void *) &msg;
|
||||
|
||||
struct rgb_color_offset_t ofs = {50,50,50};
|
||||
struct rgb_color_offset_t ofs = {50, 50, 50};
|
||||
|
||||
param->step = step;
|
||||
param->delay = delay;
|
||||
param->rgb = ofs;
|
||||
cmsg->step = step;
|
||||
cmsg->delay = delay;
|
||||
cmsg->rgb = ofs;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REMOTE_CMD_SAVE_RGB: {
|
||||
struct remote_msg_save_rgb_t * param = (void *) &msg;
|
||||
struct remote_msg_save_rgb_t * cmsg = (void *) &msg;
|
||||
|
||||
param->slot = slot;
|
||||
param->step = step;
|
||||
param->delay = delay;
|
||||
param->pause = pause;
|
||||
param->color = color;
|
||||
|
||||
}
|
||||
cmsg->slot = slot;
|
||||
cmsg->step = step;
|
||||
cmsg->delay = delay;
|
||||
cmsg->pause = pause;
|
||||
cmsg->color = color;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case REMOTE_CMD_START_PROGRAM: {
|
||||
struct remote_msg_start_program_t * param = (void *) &msg;
|
||||
|
||||
}
|
||||
struct remote_msg_start_program_t * cmsg = (void *) &msg;
|
||||
|
||||
cmsg->script = 2;
|
||||
cmsg->params = params;
|
||||
|
||||
break;
|
||||
|
||||
case REMOTE_CMD_STOP: {
|
||||
struct remote_msg_stop_t * param = (void *) &msg;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
/* no special parameters */
|
||||
case REMOTE_CMD_STOP:
|
||||
case REMOTE_CMD_POWERDOWN:
|
||||
case REMOTE_CMD_BOOTLOADER:
|
||||
break;
|
||||
|
||||
/* local commands */
|
||||
case LOCAL_CMD_COUNT:
|
||||
printf("%d\n", fn_count_devices(fd));
|
||||
break;
|
||||
|
||||
case LOCAL_CMD_EEPROM: {
|
||||
FILE *eeprom_file = fopen(filename, "r");
|
||||
char row[1024];
|
||||
|
||||
if (eeprom_file == NULL) {
|
||||
perror ("error opening eeprom file");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
while(!feof(eeprom_file)) {
|
||||
if (fgets(row, 1024, eeprom_file) && *row != '#') { /* ignore comments */
|
||||
struct remote_msg_save_rgb_t msg;
|
||||
memset(&msg, 0, sizeof msg);
|
||||
|
||||
msg.cmd = REMOTE_CMD_SAVE_RGB;
|
||||
|
||||
sscanf(row, "%d;%d;%2x%2x%2x;%d;%d;%d",
|
||||
(unsigned int *) &msg.slot,
|
||||
(unsigned int *) &msg.address,
|
||||
(unsigned int *) (&color.red),
|
||||
(unsigned int *) (&color.green),
|
||||
(unsigned int *) (&color.blue),
|
||||
(unsigned int *) &msg.step,
|
||||
(unsigned int *) &msg.delay,
|
||||
(unsigned int *) &msg.pause
|
||||
);
|
||||
|
||||
int p = fn_send(fd, (struct remote_msg_t *) &msg);
|
||||
if (verbose) print_cmd((struct remote_msg_t *) &msg);
|
||||
if (p < 0) {
|
||||
fprintf(stderr, "failed on writing %d bytes to fnordlichts", REMOTE_MSG_LEN);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
fprintf(stderr, "unknown subcomand: %s\n", argv[1]);
|
||||
usage(argv);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
msg.address = address;
|
||||
msg.cmd = cp->cmd;
|
||||
/* send remote commands to bus */
|
||||
if (cp->cmd < 0xA0) {
|
||||
if (verbose) printf("address: %d\n", address);
|
||||
msg.address = address;
|
||||
msg.cmd = cp->cmd;
|
||||
|
||||
int i = fn_send(fd, &msg);
|
||||
if (i < 0)
|
||||
fprintf(stderr, "failed on writing %d bytes to fnordlichts", REMOTE_MSG_LEN);
|
||||
|
||||
if (verbose) {
|
||||
print_cmd(&msg);
|
||||
}
|
||||
|
||||
if (strlen(host) == 0) {
|
||||
/* reset port to old state */
|
||||
tcsetattr(fd, TCSANOW, &oldtio);
|
||||
int c = strlen(mask);
|
||||
if (c > 0) {
|
||||
if (verbose) printf("sending to mask: %s\n", mask);
|
||||
int i;
|
||||
for (i = 0; i < c; i++) {
|
||||
if (mask[i] == '1') {
|
||||
msg.address = i;
|
||||
int p = fn_send(fd, &msg);
|
||||
if (verbose) print_cmd(&msg);
|
||||
if (p < 0) {
|
||||
fprintf(stderr, "failed on writing %d bytes to fnordlichts", REMOTE_MSG_LEN);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
else if (mask[i] != '0') {
|
||||
fprintf(stderr, "invalid mask! only '0' and '1' are allowed\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
int p = fn_send(fd, &msg);
|
||||
if (verbose) print_cmd(&msg);
|
||||
if (p < 0) {
|
||||
fprintf(stderr, "failed on writing %d bytes to fnordlichts", REMOTE_MSG_LEN);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (con_mode == RS232) tcsetattr(fd, TCSANOW, &oldtio); /* reset port to old state */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
BIN
fnctl.o
BIN
fnctl.o
Binary file not shown.
181
fnvum.c
Normal file
181
fnvum.c
Normal file
|
@ -0,0 +1,181 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <termios.h>
|
||||
#include <fcntl.h>
|
||||
#include <complex.h>
|
||||
|
||||
/* third party libs */
|
||||
#include "libfn.h"
|
||||
#include <SDL/SDL.h>
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/gccmacro.h>
|
||||
#include <fftw3.h>
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
#define SAMPLING_RATE 44100
|
||||
#define SCREEN_WIDTH (BUFFER_SIZE/2+1)
|
||||
#define SCREEN_HEIGHT (SCREEN_WIDTH / 2)
|
||||
#define TITLE "fnordlicht vumeter & spectrum"
|
||||
|
||||
void show_level(SDL_Surface *dst, float level) {
|
||||
SDL_Rect rect = dst->clip_rect;
|
||||
Uint32 color = SDL_MapRGB(dst->format, 0xff, 0, 0);
|
||||
Uint32 background = SDL_MapRGB(dst->format, 0, 0, 0);
|
||||
|
||||
SDL_FillRect(dst, &rect, background);
|
||||
|
||||
rect.w *= level;
|
||||
|
||||
SDL_FillRect(dst, &rect, color);
|
||||
SDL_Flip(dst);
|
||||
}
|
||||
|
||||
void show_spectrum(SDL_Surface * dst, complex * fft_data) {
|
||||
int x, p, start, end;
|
||||
double log_data[BUFFER_SIZE];
|
||||
|
||||
SDL_Rect rect;
|
||||
Uint32 color = SDL_MapRGB(dst->format, 0xff, 0, 0);
|
||||
Uint32 background = SDL_MapRGB(dst->format, 0, 0, 0);
|
||||
|
||||
SDL_FillRect(dst, &dst->clip_rect, background);
|
||||
|
||||
double ampl, db;
|
||||
rect.w = 1;
|
||||
|
||||
for (x = 0; x < BUFFER_SIZE/2+1; x++) {
|
||||
ampl = cabs(fft_data[x]);
|
||||
|
||||
rect.x = x;
|
||||
// rect.h = (ampl/3000000) * SCREEN_HEIGHT;
|
||||
rect.h = 10 * log10(ampl/300000) * SCREEN_HEIGHT;
|
||||
rect.y = SCREEN_HEIGHT - rect.h;
|
||||
|
||||
SDL_FillRect(dst, &rect, color);
|
||||
}
|
||||
SDL_Flip(dst);
|
||||
}
|
||||
|
||||
void fade_level(int fd, float level) {
|
||||
struct remote_msg_fade_rgb_t fncmd;
|
||||
memset(&fncmd, 0, sizeof (struct remote_msg_t));
|
||||
|
||||
fncmd.color.red = fncmd.color.green = fncmd.color.blue = (uint8_t) 255.0 * level;
|
||||
fncmd.address = 255;
|
||||
fncmd.cmd = REMOTE_CMD_FADE_RGB;
|
||||
fncmd.step = 25;
|
||||
fncmd.delay = 0;
|
||||
|
||||
fn_send(fd, (struct remote_msg_t *) &fncmd);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
/* The sample type to use */
|
||||
static const pa_sample_spec ss = {
|
||||
.format = PA_SAMPLE_S16LE,
|
||||
.rate = SAMPLING_RATE,
|
||||
.channels = 1
|
||||
};
|
||||
|
||||
|
||||
pa_simple *s = NULL;
|
||||
SDL_Surface *screen = NULL;
|
||||
SDL_Event event;
|
||||
|
||||
int error, counter = 0, fd = -1;
|
||||
uint32_t level;
|
||||
int16_t * pcm_data;
|
||||
complex * fft_data;
|
||||
fftw_plan fft_plan;
|
||||
|
||||
/* init fnordlichts */
|
||||
if (argc > 1) {
|
||||
fd = open(argv[1], O_RDWR | O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
perror(argv[0]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
fn_init(fd);
|
||||
}
|
||||
|
||||
/* init screen & window */
|
||||
if(SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||
fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* open sdl window */
|
||||
SDL_WM_SetCaption(TITLE, NULL);
|
||||
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 16, SDL_SWSURFACE|SDL_ANYFORMAT|SDL_RESIZABLE);
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "Unable to set video: %s\n", SDL_GetError());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* init fftw & get buffers*/
|
||||
pcm_data = (int16_t *) malloc(BUFFER_SIZE);
|
||||
fft_data = (complex *) fftw_malloc(BUFFER_SIZE * sizeof (complex));
|
||||
fft_plan = fftw_plan_dft_1d(BUFFER_SIZE, fft_data, fft_data, FFTW_FORWARD, 0);
|
||||
|
||||
/* Create the recording stream */
|
||||
if (!(s = pa_simple_new(NULL, TITLE, PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) {
|
||||
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
|
||||
exit(-1);
|
||||
}
|
||||
pa_simple_flush(s, &error); /* flush audio buffer */
|
||||
|
||||
while (1) {
|
||||
counter++;
|
||||
|
||||
/* handle SDL events */
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_QUIT) {
|
||||
printf("Good bye!\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* read PCM audio data */
|
||||
if (pa_simple_read(s, pcm_data, BUFFER_SIZE, &error) < 0) {
|
||||
fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* analyse audio data */
|
||||
uint16_t index, max = 0;
|
||||
uint32_t sum = 0;
|
||||
float level;
|
||||
for (index = 0; index < BUFFER_SIZE; index++) {
|
||||
sum += abs(pcm_data[index]);
|
||||
if (abs(pcm_data[index]) > max) max = pcm_data[index];
|
||||
|
||||
fft_data[index] = (double) pcm_data[index];
|
||||
}
|
||||
|
||||
/* execute fftw plan */
|
||||
fftw_execute(fft_plan);
|
||||
|
||||
level = (float) sum / (BUFFER_SIZE * 32767);
|
||||
|
||||
show_spectrum(screen, (complex *) fft_data);
|
||||
//show_level(screen, level);
|
||||
//fade_level(fd, level);
|
||||
|
||||
//printf("level: %f \tsum: %d\t max: %d\n", level, sum, max);
|
||||
}
|
||||
|
||||
/* housekeeping */
|
||||
SDL_Quit();
|
||||
free(pcm_data);
|
||||
fftw_free(fft_data);
|
||||
fftw_destroy(fft_plan);
|
||||
fftw_cleanup();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
This is a proof of concept piece of software. Its working for me. Feel free to add options and functions..
|
BIN
fnvum/fnvum
BIN
fnvum/fnvum
Binary file not shown.
128
fnvum/fnvum.c
128
fnvum/fnvum.c
|
@ -1,128 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "../libfn.h"
|
||||
#include <termios.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <SDL/SDL.h>
|
||||
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/gccmacro.h>
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
#define SCREEN_WIDTH 500
|
||||
#define SCREEN_HEIGHT (SCREEN_WIDTH / 20)
|
||||
#define TITLE "fnordlicht vumeter"
|
||||
#define DECAY 0.7
|
||||
|
||||
void show_level(SDL_Surface *dst, float level) {
|
||||
SDL_Rect rect = dst->clip_rect;;
|
||||
Uint32 color = SDL_MapRGB(dst->format, 0xff, 0, 0);
|
||||
Uint32 background = SDL_MapRGB(dst->format, 0, 0, 0);
|
||||
|
||||
SDL_FillRect(dst, &rect, background);
|
||||
|
||||
rect.w *= level;
|
||||
|
||||
SDL_FillRect(dst, &rect, color);
|
||||
SDL_Flip(dst);
|
||||
}
|
||||
|
||||
int main(int argc, char*argv[]) {
|
||||
/* The sample type to use */
|
||||
static const pa_sample_spec ss = {
|
||||
.format = PA_SAMPLE_S16LE,
|
||||
.rate = 44100,
|
||||
.channels = 1
|
||||
};
|
||||
|
||||
pa_simple *s = NULL;
|
||||
SDL_Surface *screen = NULL;
|
||||
SDL_Event event;
|
||||
|
||||
int error, counter = 0;
|
||||
uint32_t level;
|
||||
int16_t buffer[BUFFER_SIZE];
|
||||
|
||||
/* init fnordlichts */
|
||||
struct remote_msg_fade_rgb_t fncmd;
|
||||
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
perror("/dev/ttyUSB0");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
memset(&fncmd, 0, sizeof (struct remote_msg_t));
|
||||
fn_init(fd);
|
||||
|
||||
fncmd.address = 255;
|
||||
fncmd.cmd = REMOTE_CMD_FADE_RGB;
|
||||
fncmd.step = 25;
|
||||
fncmd.delay = 0;
|
||||
|
||||
/* init screen & window */
|
||||
if(SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||
fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
SDL_WM_SetCaption(TITLE, NULL);
|
||||
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 16, SDL_SWSURFACE|SDL_ANYFORMAT|SDL_RESIZABLE);
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "Unable to set 640x480 video: %s\n", SDL_GetError());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* Create the recording stream */
|
||||
if (!(s = pa_simple_new(NULL, TITLE, PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) {
|
||||
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
pa_simple_flush(s, &error); /* flush audio buffer */
|
||||
while (1) {
|
||||
counter++;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_QUIT) {
|
||||
printf("Good bye!\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (pa_simple_read(s, buffer, sizeof(buffer), &error) < 0) {
|
||||
fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int16_t * pos, max = 0;
|
||||
uint32_t sum = 0;
|
||||
float level;
|
||||
for (pos = buffer; pos - buffer < BUFFER_SIZE; pos++) {
|
||||
sum += abs(*pos);
|
||||
if (abs(*pos) > max) max = abs(*pos);
|
||||
}
|
||||
level = (float) sum / (BUFFER_SIZE * 32767) * 1.7;
|
||||
if (level > 1) level = 1;
|
||||
|
||||
if (counter % (3 * 44100 / 1024) == 0) {
|
||||
fn_sync(fd);
|
||||
pa_simple_flush(s, &error);
|
||||
printf("synced & flushed\n");
|
||||
}
|
||||
|
||||
show_level(screen, level);
|
||||
fncmd.color.red = fncmd.color.green = fncmd.color.blue = (uint8_t) 255.0 * level;
|
||||
fn_send(fd, (struct remote_msg_t *) &fncmd);
|
||||
printf("level: %d (%f) \tsum: %d\t max: %d\n", fncmd.color.red, level, sum, max);
|
||||
}
|
||||
|
||||
/* housekeeping */
|
||||
SDL_Quit();
|
||||
|
||||
}
|
BIN
iso-226_equal_loudness/iso_266_50_phon.ods
Normal file
BIN
iso-226_equal_loudness/iso_266_50_phon.ods
Normal file
Binary file not shown.
201
iso-226_equal_loudness/iso_266_50_phon.svg
Normal file
201
iso-226_equal_loudness/iso_266_50_phon.svg
Normal file
|
@ -0,0 +1,201 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.0"
|
||||
width="428.08844"
|
||||
height="334.96362"
|
||||
viewBox="0 0 6076.8403 4773.6932"
|
||||
id="svg2"
|
||||
style="fill-rule:evenodd"
|
||||
inkscape:version="0.48.0 r9654"
|
||||
sodipodi:docname="Lindos1.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<metadata
|
||||
id="metadata658">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="850"
|
||||
id="namedview656"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4824748"
|
||||
inkscape:cx="90.784352"
|
||||
inkscape:cy="140.87734"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<defs
|
||||
id="defs657">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 316 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="704 : 316 : 1"
|
||||
inkscape:persp3d-origin="352 : 211 : 1"
|
||||
id="perspective660" />
|
||||
</defs>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g2023"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g2025"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text2027">
|
||||
<tspan
|
||||
y="5515"
|
||||
id="tspan2029" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g2669"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g2671"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text2673">
|
||||
<tspan
|
||||
y="4625"
|
||||
id="tspan2675" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g3069"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g3071"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text3073">
|
||||
<tspan
|
||||
y="3087"
|
||||
id="tspan3075" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g3457"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g3459"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text3461">
|
||||
<tspan
|
||||
y="3798"
|
||||
id="tspan3463" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g3857"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g3859"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text3861">
|
||||
<tspan
|
||||
y="2316"
|
||||
id="tspan3863" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g3989"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g3991"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text3993">
|
||||
<tspan
|
||||
y="1515"
|
||||
id="tspan3995" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g5529"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g5531"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text5533">
|
||||
<tspan
|
||||
y="4161"
|
||||
id="tspan5535" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-2301.5214,-3353.4089)"
|
||||
id="g5917"
|
||||
style="font-size:635px;font-weight:400;visibility:visible;font-family:Helvetica">
|
||||
<g
|
||||
id="g5919"
|
||||
style="fill:#000000;stroke:none">
|
||||
<text
|
||||
id="text5921">
|
||||
<tspan
|
||||
y="3798"
|
||||
id="tspan5923" />
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<text
|
||||
x="-2049.7148"
|
||||
y="-496.28244"
|
||||
id="text5937"
|
||||
style="font-size:282px;font-weight:700;fill:#000000;visibility:visible;font-family:Helvetica">
|
||||
<tspan
|
||||
y="8336.7178"
|
||||
id="tspan5939" />
|
||||
</text>
|
||||
<path
|
||||
d="m 6054.3735,26.721334 c -14.8173,277.913896 -2.2039,2218.200166 -39.9925,3251.293166 -18.6661,510.3068 -97.2949,804.581 -281.6852,541.3939 -56.8579,-81.1555 -264.9693,-140.7507 -402.6776,-17.538 -185.065,165.5844 -331.169,428.5715 -331.169,428.5715 0,0 -331.168,530.8441 -496.753,516.2337 -165.584,-14.6104 -370.13,-102.2727 -467.532,-194.8052 -97.403,-92.5324 -194.805,-199.6753 -287.338,-185.0649 -92.532,14.6104 -194.805,92.5325 -194.805,92.5325 0,0 -112.013,121.7532 -545.455,14.6103 -433.441,-107.1428 -633.116,-258.1168 -633.116,-258.1168 0,0 -667.208,-394.4806 -1134.741,-1056.8182 0,0 -998.37603,-1178.5714 -1232.1430282,-1724.026"
|
||||
id="path7772"
|
||||
style="fill:none;stroke:#ff0000;stroke-width:53.44266891;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssscssscsccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 5.8 KiB |
BIN
iso-226_equal_loudness/lindos_1.png
Normal file
BIN
iso-226_equal_loudness/lindos_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
1
libfn.h
1
libfn.h
|
@ -6,6 +6,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "color.h"
|
||||
#include "static_programs.h"
|
||||
#include "remote-proto.h"
|
||||
|
||||
#define FN_BAUDRATE B19200
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
#define REMOTE_SYNC_LEN 15
|
||||
#define REMOTE_SYNC_BYTE 0x1b
|
||||
|
||||
/* number of color-configurations stored in EEPROM */
|
||||
#define CONFIG_EEPROM_COLORS 60
|
||||
|
||||
/* max mode for startup configuration is 1 */
|
||||
#define REMOTE_STARTUP_MAX_MODE 1
|
||||
/* maximum parameter size (for copy loop), size of structure storage_config_t,
|
||||
|
@ -59,7 +62,7 @@
|
|||
#define REMOTE_CMD_FLASH 0x86
|
||||
#define REMOTE_CMD_ENTER_APP 0x87
|
||||
|
||||
#define REMOTE_ADDR_BROADCAST 0xff
|
||||
#define REMOTE_ADDR_BROADCAST 0xff
|
||||
|
||||
/* normal commands */
|
||||
struct remote_msg_t
|
||||
|
@ -135,7 +138,7 @@ struct remote_msg_start_program_t
|
|||
uint8_t address;
|
||||
uint8_t cmd;
|
||||
uint8_t script;
|
||||
// union program_params_t params;
|
||||
union program_params_t params;
|
||||
};
|
||||
|
||||
struct remote_msg_stop_t
|
||||
|
|
22
replay.eeprom
Normal file
22
replay.eeprom
Normal file
|
@ -0,0 +1,22 @@
|
|||
# This CSV file is used to easily edit the slots of your fnordlichter
|
||||
# Author: Steffen Vogel <info@steffenvogel.de>
|
||||
#
|
||||
# There are 6 fields:
|
||||
# slot | slot in the EEPROM (0-59)
|
||||
# address | address of the fnordlicht on the bus (0-254, 255 for broadcast)
|
||||
# color | color in hex format (ex. 'ff31A4')
|
||||
# step | increment step for fading
|
||||
# delay | delay between steps when fading (in 10ms)
|
||||
# pause | time to wait before fading to next color (in 100ms)
|
||||
#
|
||||
# Lines beginning with a '#' are ignored as comments
|
||||
#
|
||||
# https://github.com/fd0/fnordlicht/raw/master/doc/PROTOCOL
|
||||
#
|
||||
#slot;address;color;step;delay;pause
|
||||
0;255;ffffff;30;2;5
|
||||
1;255;000000;30;2;5
|
||||
2;255;00ff00;30;2;10
|
||||
3;255;00ffff;30;2;10
|
||||
4;255;ffff00;30;2;10
|
||||
|
81
static_programs.h
Normal file
81
static_programs.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/* vim:ts=4 sts=4 et tw=80
|
||||
*
|
||||
* fnordlicht firmware
|
||||
*
|
||||
* for additional information please
|
||||
* see http://lochraster.org/fnordlichtmini
|
||||
*
|
||||
* (c) by Alexander Neumann <alexander@bumpern.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 3 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __STATIC_PROGRAMS_H
|
||||
#define __STATIC_PROGRAMS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define STATIC_PROGRAMS_LEN 3
|
||||
|
||||
/* configure maximal static program parameter size */
|
||||
#define PROGRAM_PARAMETER_SIZE 10
|
||||
|
||||
/* parameter structures (max 10 bytes) */
|
||||
struct colorwheel_params_t
|
||||
{
|
||||
uint8_t fade_step;
|
||||
uint8_t fade_delay;
|
||||
uint8_t fade_sleep;
|
||||
uint16_t hue_start;
|
||||
int16_t hue_step;
|
||||
int8_t add_addr;
|
||||
uint8_t saturation;
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
struct random_params_t
|
||||
{
|
||||
uint16_t seed;
|
||||
uint8_t use_address:1;
|
||||
uint8_t wait_for_fade:1;
|
||||
uint8_t reserved:6;
|
||||
uint8_t fade_step;
|
||||
uint8_t fade_delay;
|
||||
uint16_t fade_sleep;
|
||||
uint8_t saturation;
|
||||
uint8_t value;
|
||||
uint8_t min_distance;
|
||||
};
|
||||
|
||||
struct replay_params_t
|
||||
{
|
||||
uint8_t start;
|
||||
uint8_t end;
|
||||
enum {
|
||||
REPEAT_NONE = 0,
|
||||
REPEAT_START = 1,
|
||||
REPEAT_REVERSE = 2,
|
||||
} repeat;
|
||||
};
|
||||
|
||||
union program_params_t
|
||||
{
|
||||
/* parameters for static programs */
|
||||
uint8_t raw[PROGRAM_PARAMETER_SIZE];
|
||||
struct colorwheel_params_t colorwheel;
|
||||
struct random_params_t random;
|
||||
struct replay_params_t replay;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue