From cf8193e421e39ad2f3fe3f0bc029abd276da4951 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Fri, 13 Jul 2012 21:18:35 +0200 Subject: [PATCH] Initial commit von Musiclight 2 --- .gitignore | 5 + Makefile | 22 +++++ config.h | 54 +++++++++++ fft.c | 151 ++++++++++++++++++++++++++++++ fft.h | 22 +++++ main.c | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++++ run_alsa.sh | 3 + run_mpd.sh | 4 + run_pa.sh | 16 ++++ utils.c | 42 +++++++++ utils.h | 17 ++++ ws2801.c | 112 ++++++++++++++++++++++ ws2801.h | 21 +++++ 13 files changed, 732 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 config.h create mode 100644 fft.c create mode 100644 fft.h create mode 100644 main.c create mode 100755 run_alsa.sh create mode 100755 run_mpd.sh create mode 100755 run_pa.sh create mode 100644 utils.c create mode 100644 utils.h create mode 100644 ws2801.c create mode 100644 ws2801.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7e169a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +*.swp +musiclight2 +core +tags diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7535ab0 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +CC=gcc +CFLAGS+=-O3 -Wall -march=native -pedantic -std=c99 -D_POSIX_C_SOURCE=20120607L -D_XOPEN_SOURCE +LIBS=-lm -lpthread -lrt + +TARGET=musiclight2 +SOURCE=main.c fft.c utils.c ws2801.c +DEPS=config.h fft.h utils.h ws2801.h + +OBJ=$(patsubst %.c, %.o, $(SOURCE)) + +$(TARGET): $(OBJ) $(DEPS) + $(CC) -o $(TARGET) $(OBJ) $(LIBS) + +%.o: %.c $(DEPS) + $(CC) -c $(CFLAGS) -o $@ $< $(INCLUDES) + +doc: + doxygen doxygen.conf + +clean: + rm -f $(TARGET) + rm -f $(OBJ) diff --git a/config.h b/config.h new file mode 100644 index 0000000..4d2103b --- /dev/null +++ b/config.h @@ -0,0 +1,54 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include + +// configuration variables for musiclight2 + +// networking +#define HOST "192.168.23.222" +#define PORT 2703 + +// FFT transformation parameters +#define FFT_EXPONENT 10 +#define BLOCK_LEN (1 << FFT_EXPONENT) // 2^FFT_EXPONENT +#define SAMPLE_RATE 44100 +#define DATALEN (BLOCK_LEN / 2) + +// Number of parts in the sample buffer +#define BUFFER_PARTS 2 + +// update rate for the led strip (in seconds) +#define LED_INTERVAL 0.03 + +// number of modules in LED strip +#define NUM_MODULES 20 + +// frequency ranges for the base colors +#define RED_MIN_FREQ 0 +#define RED_MAX_FREQ 400 +#define GREEN_MIN_FREQ 400 +#define GREEN_MAX_FREQ 4000 +#define BLUE_MIN_FREQ 4000 +#define BLUE_MAX_FREQ SAMPLE_RATE/2 + +#define COLOR_MAX_REDUCTION_FACTOR 0.9998 + +#define CENTER_MODULE 10 + +#define GAMMA 2.0 + +// sample data types +typedef int16_t sample; +typedef int64_t sample_sum; + +#endif // CONFIG_H diff --git a/fft.c b/fft.c new file mode 100644 index 0000000..e217c84 --- /dev/null +++ b/fft.c @@ -0,0 +1,151 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 +#include +#include +#include + +#include "config.h" + +#include "fft.h" + +double hanning_buffer[BLOCK_LEN]; +int lookup_table[BLOCK_LEN]; + + +void init_fft(void) { + int i = 0; + int ri, b; + + for(i = 0; i < BLOCK_LEN; i++) { + hanning_buffer[i] = 0.5 * (1 - cos(2 * M_PI * i / BLOCK_LEN)); + } + + for(i = 0; i < BLOCK_LEN; i++) { + ri = 0; + + for(b = 0; b < FFT_EXPONENT; b++) + ri |= ((i >> b) & 1) << (FFT_EXPONENT - b - 1); + + lookup_table[i] = ri; + } +} + + + +void complex_to_absolute(double *re, double *im, double *result) { + int i; + + for(i = 0; i < DATALEN; i++) + { + result[i] = sqrt( re[i]*re[i] + im[i]*im[i] ); + } +} + + + +void apply_hanning(sample *dftinput) { + int i; + + for(i = 0; i < BLOCK_LEN; i++) + dftinput[i] *= hanning_buffer[i]; +} + + + +void fft_transform(sample *samples, double *resultRe, double *resultIm) { + int i; + int layer, part, element; + int num_parts, num_elements; + + int left, right; + + double x_left_re, x_left_im, x_right_re, x_right_im; + double param; + double sinval, cosval; + + // re-arrange the input array according to the lookup table + // and store it into the real output array (as the input is obviously real). + // zero the imaginary output + for(i = 0; i < BLOCK_LEN; i++) + { + resultRe[lookup_table[i]] = samples[i]; + resultIm[i] = 0; + } + + // walk layers + for(layer = 0; layer < FFT_EXPONENT; layer++) + { + // number of parts in current layer + num_parts = 1 << (FFT_EXPONENT - layer - 1); + + // walk parts of layer + for(part = 0; part < num_parts; part++) + { + // number of elements in current part + num_elements = (1 << layer); + + // walk elements in part + for(element = 0; element < num_elements; element++) + { + // calculate index of element in left and right half of part + left = (1 << (layer + 1)) * part + element; + right = left + (1 << layer); + + // buffer the elements for the calculation + x_left_re = resultRe[left]; + x_left_im = resultIm[left]; + x_right_re = resultRe[right]; + x_right_im = resultIm[right]; + + // precalculate the parameter for sin and cos + param = -M_PI * element / (1 << layer); + + // precalculate sinus and cosinus values for param + sinval = sin(param); + cosval = cos(param); + + // combine the values according to a butterfly diagram + resultRe[left] = x_right_re + x_left_re * cosval - x_left_im * sinval; + resultIm[left] = x_right_im + x_left_im * cosval + x_left_re * sinval; + resultRe[right] = x_right_re - x_left_re * cosval + x_left_im * sinval; + resultIm[right] = x_right_im - x_left_im * cosval - x_left_re * sinval; + } + } + } +} + +uint32_t find_loudest_frequency(double *absFFT) { + int maxPos = 0; + double maxVal = 0; + int i; + + for(i = 0; i < BLOCK_LEN; i++) { + if(absFFT[i] > maxVal) { + maxPos = i; + maxVal = absFFT[i]; + } + } + + return (double)maxPos * SAMPLE_RATE / BLOCK_LEN; +} + +double get_energy_in_band(double *fft, uint32_t minFreq, uint32_t maxFreq) { + int firstBlock = minFreq * BLOCK_LEN / SAMPLE_RATE; + int lastBlock = maxFreq * BLOCK_LEN / SAMPLE_RATE; + int i; + + double energy = 0; + for(i = firstBlock; i < lastBlock; i++) { + energy += fft[i]; + } + + return energy; +} diff --git a/fft.h b/fft.h new file mode 100644 index 0000000..69ac4ac --- /dev/null +++ b/fft.h @@ -0,0 +1,22 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 + */ + +#ifndef FFT_H +#define FFT_H + +#include "config.h" + +void init_fft(void); +void complex_to_absolute(double *re, double *im, double *result); +void apply_hanning(sample *dftinput); +void fft_transform(sample *samples, double *resultRe, double *resultIm); +uint32_t find_loudest_frequency(double *absFFT); +double get_energy_in_band(double *fft, uint32_t minFreq, uint32_t maxFreq); + +#endif // FFT_H diff --git a/main.c b/main.c new file mode 100644 index 0000000..e0e2c44 --- /dev/null +++ b/main.c @@ -0,0 +1,263 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 +#include + +#include +#include +#include +#include + +#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) + +#define COLORBUF_SIZE (2*(NUM_MODULES+1)) +#define CENTER_POS (2*CENTER_MODULE) + +double fft[BLOCK_LEN]; +double rms; +double redEnergy, greenEnergy, blueEnergy; +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 += block[i]*block[i]; + } + tmpRMS = sqrt(tmpRMS/BLOCK_LEN); + + // --- SAFE SECTION --- + sem_wait(&fftSemaphore); + + memcpy(fft, tmpFFT, sizeof(fft)); + rms = tmpRMS; + redEnergy = get_energy_in_band(fft, RED_MIN_FREQ, RED_MAX_FREQ); + greenEnergy = get_energy_in_band(fft, GREEN_MIN_FREQ, GREEN_MAX_FREQ); + blueEnergy = get_energy_in_band(fft, BLUE_MIN_FREQ, BLUE_MAX_FREQ); + + 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) { + return pow(d, GAMMA); +} + +void text_bar(double fill) { + int fillCnt = 10 * fill; + int i; + + for(i = 0; i < fillCnt; i++) { + printf("|"); + } + + for(; i < 10; i++) { + printf("-"); + } +} + +double weighted_avg(uint8_t colorBuf[COLORBUF_SIZE][3], int channel, int centerPos) { + return 0.20 * colorBuf[centerPos - 1][channel] + + 0.60 * colorBuf[centerPos][channel] + + 0.20 * colorBuf[centerPos + 1][channel]; +} + +void show_status(double curRed, double maxRed, double curGreen, double maxGreen, double curBlue, double maxBlue) { + printf("\r"); + + printf("[\033[31m"); + text_bar(curRed / maxRed); + printf("\033[0m] "); + printf("\033[1;31m%7.2e\033[0m ", maxRed / (RED_MAX_FREQ - RED_MIN_FREQ)); + + printf("[\033[32m"); + text_bar(curGreen / maxGreen); + printf("\033[0m] "); + printf("\033[1;32m%7.2e\033[0m ", maxGreen / (GREEN_MAX_FREQ - GREEN_MIN_FREQ)); + + printf("[\033[34m"); + text_bar(curBlue / maxBlue); + printf("\033[0m] "); + printf("\033[1;34m%7.2e\033[0m ", maxBlue / (BLUE_MAX_FREQ - BLUE_MIN_FREQ)); + + fflush(stdout); +} + +int main(int argc, char **argv) { + double nextFrame = get_hires_time() + LED_INTERVAL; + + int i, j; + pthread_t fftThread; + + int active = 1; + + uint8_t colorBuf[COLORBUF_SIZE][3]; + + double curRedEnergy, curGreenEnergy, curBlueEnergy; + double maxRedEnergy = 1, maxGreenEnergy = 1, maxBlueEnergy = 1; + + memset(colorBuf, 0, sizeof(colorBuf)); + + // create semaphores + sem_init(&fftSemaphore, 0, 1); + + // run the fft thread + pthread_create(&fftThread, NULL, fft_thread, NULL); + + ws2801_init(HOST, PORT); + + while(running) { + for(i = COLORBUF_SIZE-1; i >= 0; i--) { + int pos = CENTER_POS + i; + if(pos < COLORBUF_SIZE-1) { + for(j = 0; j < 3; j++) { + colorBuf[pos][j] = colorBuf[pos - 1][j]; + } + } + + pos = CENTER_POS - i; + if(pos >= 0) { + for(j = 0; j < 3; j++) { + colorBuf[pos][j] = colorBuf[pos + 1][j]; + } + } + } + + if(active) { + sem_wait(&fftSemaphore); + curRedEnergy = redEnergy; + curGreenEnergy = greenEnergy; + curBlueEnergy = blueEnergy; + sem_post(&fftSemaphore); + + maxRedEnergy *= COLOR_MAX_REDUCTION_FACTOR; + if(curRedEnergy > maxRedEnergy) { + maxRedEnergy = curRedEnergy; + } + + maxGreenEnergy *= COLOR_MAX_REDUCTION_FACTOR; + if(curGreenEnergy > maxGreenEnergy) { + maxGreenEnergy = curGreenEnergy; + } + + maxBlueEnergy *= COLOR_MAX_REDUCTION_FACTOR; + if(curBlueEnergy > maxBlueEnergy) { + maxBlueEnergy = curBlueEnergy; + } + + colorBuf[CENTER_POS][0] = 255 * gamma_correct(curRedEnergy / maxRedEnergy); + colorBuf[CENTER_POS][1] = 255 * gamma_correct(curGreenEnergy / maxGreenEnergy); + colorBuf[CENTER_POS][2] = 255 * gamma_correct(curBlueEnergy / maxBlueEnergy); + + /* + show_status(curRedEnergy, maxRedEnergy, curGreenEnergy, maxGreenEnergy, + curBlueEnergy, maxBlueEnergy); + */ + + for(i = 0; i < NUM_MODULES; i++) { + ws2801_set_color(i, + weighted_avg(colorBuf, 0, 2 * i + 1), + weighted_avg(colorBuf, 1, 2 * i + 1), + weighted_avg(colorBuf, 2, 2 * i + 1)); + } + 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(); + + pthread_join(fftThread, NULL); + + return 0; +} diff --git a/run_alsa.sh b/run_alsa.sh new file mode 100755 index 0000000..e8fee4e --- /dev/null +++ b/run_alsa.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +arecord -c 1 -f s16 -r 22050 | ./rtfft diff --git a/run_mpd.sh b/run_mpd.sh new file mode 100755 index 0000000..d63ebd7 --- /dev/null +++ b/run_mpd.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#dd if=/tmp/mpd.fifo bs=1024 | ./musiclight2 +./musiclight2 < /tmp/mpd.fifo diff --git a/run_pa.sh b/run_pa.sh new file mode 100755 index 0000000..1120439 --- /dev/null +++ b/run_pa.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +#parec -d "alsa_output.pci-0000_00_1b.0.analog-stereo.monitor" --channels=1 --format=s16 | mbuffer -R 88200 | ./musiclight2 + +case $1 in + mic) + #mikro + parec -d "alsa_input.pci-0000_00_1b.0.analog-stereo" --raw --rate=44100 --channels=1 --format=s16 | ./musiclight2 + ;; + + *) + # soundkarte + parec -d "alsa_output.pci-0000_00_1b.0.analog-stereo.monitor" --raw --rate=44100 --channels=1 --format=s16 | ./musiclight2 + ;; +esac + diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..0c0750e --- /dev/null +++ b/utils.c @@ -0,0 +1,42 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 + +#include +#include + +#include "utils.h" + +double get_hires_time(void) { + struct timespec clk; + clock_gettime(CLOCK_REALTIME, &clk); + return clk.tv_sec + 1e-9 * clk.tv_nsec; +} + +void fsleep(double d) { + struct timespec ts; + + ts.tv_sec = (time_t)d; + ts.tv_nsec = (long)(1e9 * (d - (long)d)); + + nanosleep(&ts, NULL); +} + +void sleep_until(double hires_time) { + struct timespec tv; + int ret; + + tv.tv_sec = hires_time; + tv.tv_nsec = (uint64_t)(1e9 * hires_time) % 1000000000; + do { + ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &tv, NULL); + } while(ret == EINTR); +} + diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..7c33f43 --- /dev/null +++ b/utils.h @@ -0,0 +1,17 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 + */ + +#ifndef UTILS_H +#define UTILS_H + +double get_hires_time(void); +void fsleep(double d); +void sleep_until(double hires_time); + +#endif // UTILS_H diff --git a/ws2801.c b/ws2801.c new file mode 100644 index 0000000..a0026ab --- /dev/null +++ b/ws2801.c @@ -0,0 +1,112 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 +#include +#include +#include + +#include +#include + +#include "ws2801.h" + +#define SET_COLOR 0 +#define FADE_COLOR 1 +#define ADD_COLOR 2 +#define SET_FADESTEP 3 + +struct WS2801Packet { + uint8_t metadata; + uint8_t data[3]; +}; + +int ws2801_socket = -1; +struct WS2801Packet packetQueue[50]; +int queueIndex = 0; + +// creates the socket needed for steering the LED strip +int ws2801_init(char *host, unsigned short port) { + struct addrinfo hints; + struct addrinfo *result; + char portstr[6]; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + sprintf(portstr, "%u", port); + + if(getaddrinfo(host, portstr, &hints, &result) != 0) { + perror("getaddrinfo() failed"); + return 1; + } + + ws2801_socket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (ws2801_socket == -1) { + perror("socket() failed"); + freeaddrinfo(result); + return 2; + } + + if (connect(ws2801_socket, result->ai_addr, result->ai_addrlen) == -1) { + perror("connect() failed"); + freeaddrinfo(result); + return 3; + } + + freeaddrinfo(result); + + return 0; +} + +void ws2801_set_color(uint8_t module, uint8_t r, uint8_t g, uint8_t b) { + packetQueue[queueIndex].metadata = (SET_COLOR << 6) | (module & 0x3F); + packetQueue[queueIndex].data[0] = r; + packetQueue[queueIndex].data[1] = g; + packetQueue[queueIndex].data[2] = b; + queueIndex++; +} + +void ws2801_fade_color(uint8_t module, uint8_t r, uint8_t g, uint8_t b) { + packetQueue[queueIndex].metadata = (FADE_COLOR << 6) | (module & 0x3F); + packetQueue[queueIndex].data[0] = r; + packetQueue[queueIndex].data[1] = g; + packetQueue[queueIndex].data[2] = b; + queueIndex++; +} + +void ws2801_add_color(uint8_t module, uint8_t r, uint8_t g, uint8_t b) { + packetQueue[queueIndex].metadata = (ADD_COLOR << 6) | (module & 0x3F); + packetQueue[queueIndex].data[0] = r; + packetQueue[queueIndex].data[1] = g; + packetQueue[queueIndex].data[2] = b; + queueIndex++; +} + +void ws2801_set_fadestep(uint8_t fadestep) { + packetQueue[queueIndex].metadata = (SET_FADESTEP << 6); + packetQueue[queueIndex].data[0] = fadestep; + queueIndex++; +} + +int ws2801_commit(void) { + if(send(ws2801_socket, packetQueue, queueIndex * sizeof(struct WS2801Packet), 0) == -1) { + return 1; + } + + queueIndex = 0; + return 0; +} + +void ws2801_shutdown() { + close(ws2801_socket); +} diff --git a/ws2801.h b/ws2801.h new file mode 100644 index 0000000..cb310e9 --- /dev/null +++ b/ws2801.h @@ -0,0 +1,21 @@ +/* + * vim: sw=2 ts=2 expandtab + * + * THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): + * 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 + */ + +#ifndef WS2801_H +#define WS2801_H + +int ws2801_init(char *host, unsigned short port); +void ws2801_set_color(uint8_t module, uint8_t r, uint8_t g, uint8_t b); +void ws2801_fade_color(uint8_t module, uint8_t r, uint8_t g, uint8_t b); +void ws2801_add_color(uint8_t module, uint8_t r, uint8_t g, uint8_t b); +void ws2801_set_fadestep(uint8_t fadestep); +int ws2801_commit(void); +void ws2801_shutdown(void); + +#endif // WS2801_H