diff --git a/l2ork_addons/raspberry_pi/disis_gpio/disis_gpio.c b/l2ork_addons/raspberry_pi/disis_gpio/disis_gpio.c new file mode 100644 index 0000000000000000000000000000000000000000..74a17243a3066bd8d6bdfa05471de6fd99af49fc --- /dev/null +++ b/l2ork_addons/raspberry_pi/disis_gpio/disis_gpio.c @@ -0,0 +1,149 @@ +/* gpio - Pi gpio pins via /sys/etc */ +/* see http://elinux.org/RPi_Low-level_peripherals */ + +/* Copyright Ivica Ico Bukic <ico@vt.edu> */ +/* Based on Miller Puckette's example - Copyright Miller Puckette - BSD license */ +/* with changes to make external rely on the gpio executable to sidestep permmission issues */ + +#include "m_pd.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +static t_class *disis_gpio_class; + +//#define FILE_PREFIX "/sys/class/gpio/" +//#define FILE_EXPORT FILE_PREFIX "export" + +typedef struct _disis_gpio +{ + t_object x_obj; + t_outlet *x_out1; + int x_pin; + int x_fdvalue; + int x_dir; +} t_disis_gpio; + +static void disis_gpio_enable(t_disis_gpio *x, t_float f, t_float dir) +{ + char buf[1024]; + if (f != 0) { + if (dir) { + sprintf(buf, "gpio export %d out\n", x->x_pin); + } else { + sprintf(buf, "gpio export %d in\n", x->x_pin); + } + } else sprintf(buf, "gpio unexport %d\n", x->x_pin); + system(buf); +} + +static void disis_gpio_output(t_disis_gpio *x, t_float f) +{ + char buf[1024]; + x->x_dir = f; + if (f != 0) + sprintf(buf, "gpio -g mode %d out\n", + x->x_pin); + else sprintf(buf, "gpio -g mode %d in\n", + x->x_pin); + system(buf); +} + +static void disis_gpio_open(t_disis_gpio *x, t_float f) +{ + char buf[1024]; + sprintf(buf, "/sys/class/gpio/gpio%d/value", x->x_pin); + if (f != 0) + { + if (x->x_fdvalue >= 0) + post("disis_gpio: already open"); + else + { + x->x_fdvalue = open(buf, O_RDWR); + if (x->x_fdvalue < 0) + post("%s: %s", buf, strerror(errno)); + } + } + else + { + if (x->x_fdvalue < 0) + post("disis_gpio: already closed"); + else + { + close(x->x_fdvalue); + x->x_fdvalue = -1; + } + } +} + +static void disis_gpio_float(t_disis_gpio *x, t_float f) +{ + char buf[1024]; + if (x->x_fdvalue < 0) + pd_error(x, "disis_gpio: not open"); + else + { + sprintf(buf, "%d\n", (f != 0)); + if (write(x->x_fdvalue, buf, strlen(buf)) < (int)strlen(buf)) + pd_error(x, "disis_gpio_float: %s", strerror(errno)); + } +} + +static void disis_gpio_bang(t_disis_gpio *x) +{ + char buf[1024]; + if (x->x_fdvalue < 0) + pd_error(x, "disis_gpio: not open"); + else + { + int rval = lseek(x->x_fdvalue, 0, 0); + if (rval < 0) + { + pd_error(x, "disis_gpio_bang (seek): %s", strerror(errno)); + return; + } + rval = read(x->x_fdvalue, buf, sizeof(buf)-1); + if (rval < 0) + { + pd_error(x, "disis_gpio_bang (read): %s", strerror(errno)); + return; + } + buf[rval] = 0; + if (sscanf(buf, "%d", &rval) < 1) + { + pd_error(x, "disis_gpio_bang: couldn't parse string: %s", buf); + return; + } + outlet_float(x->x_out1, rval); + } +} + +static void *disis_gpio_new(t_floatarg f) +{ + t_disis_gpio *x = (t_disis_gpio *)pd_new(disis_gpio_class); + x->x_out1 = outlet_new(&x->x_obj, gensym("float")); + x->x_fdvalue = -1; + x->x_pin = f; + x->x_dir = 0; + return (x); +} + + +void disis_gpio_setup(void) +{ + disis_gpio_class = class_new(gensym("disis_gpio"), (t_newmethod)disis_gpio_new, + 0, sizeof(t_disis_gpio), 0, A_DEFFLOAT, 0); + class_addmethod(disis_gpio_class, (t_method)disis_gpio_enable, gensym("enable"), + A_DEFFLOAT, A_DEFFLOAT, 0); + class_addmethod(disis_gpio_class, (t_method)disis_gpio_output, gensym("output"), + A_FLOAT, 0); + class_addmethod(disis_gpio_class, (t_method)disis_gpio_open, gensym("open"), + A_DEFFLOAT, 0); + class_addfloat(disis_gpio_class, disis_gpio_float); + class_addbang(disis_gpio_class, disis_gpio_bang); +} diff --git a/l2ork_addons/raspberry_pi/disis_gpio/makefile b/l2ork_addons/raspberry_pi/disis_gpio/makefile new file mode 100644 index 0000000000000000000000000000000000000000..d4f66235419c0b29a1595734d45df620a77a9c03 --- /dev/null +++ b/l2ork_addons/raspberry_pi/disis_gpio/makefile @@ -0,0 +1,4 @@ +NAME=disis_gpio +CSYM=$(NAME) + +include ../makefile.include diff --git a/l2ork_addons/raspberry_pi/disis_gpio/test-gpio.pd b/l2ork_addons/raspberry_pi/disis_gpio/test-gpio.pd new file mode 100644 index 0000000000000000000000000000000000000000..bd2e7c5ceeef7355c83dd99f53170340bc85cbc9 --- /dev/null +++ b/l2ork_addons/raspberry_pi/disis_gpio/test-gpio.pd @@ -0,0 +1,25 @@ +#N canvas 187 97 450 300 10; +#X obj 82 58 gpio 4; +#X msg 73 21 open 1; +#X msg 134 22 open 0; +#X obj 215 24 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X obj 255 24 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 +-1; +#X floatatom 94 96 5 0 0 0 - - -; +#X obj 95 122 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 +-1; +#X obj 231 62 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X obj 231 93 metro 1; +#X obj 175 149 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X obj 175 180 metro 1; +#X connect 1 0 0 0; +#X connect 2 0 0 0; +#X connect 3 0 0 0; +#X connect 4 0 0 0; +#X connect 5 0 6 0; +#X connect 7 0 8 0; +#X connect 8 0 0 0; +#X connect 9 0 10 0; diff --git a/l2ork_addons/raspberry_pi/disis_spi/disis_spi.c b/l2ork_addons/raspberry_pi/disis_spi/disis_spi.c new file mode 100644 index 0000000000000000000000000000000000000000..cbdaf5f998f48e15fc8504419c7e3aa25ba23616 --- /dev/null +++ b/l2ork_addons/raspberry_pi/disis_spi/disis_spi.c @@ -0,0 +1,251 @@ +/*********************************************************************** + * This header file contains the mcp3008Spi class definition. + * Its main purpose is to communicate with the MCP3008 chip using + * the userspace spidev facility. + * The class contains four variables: + * mode -> defines the SPI mode used. In our case it is SPI_MODE_0. + * bitsPerWord -> defines the bit width of the data transmitted. + * This is normally 8. Experimentation with other values + * didn't work for me + * speed -> Bus speed or SPI clock frequency. According to + * https://projects.drogon.net/understanding-spi-on-the-raspberry-pi/ + * It can be only 0.5, 1, 2, 4, 8, 16, 32 MHz. + * Will use 1MHz for now and test it further. + * spifd -> file descriptor for the SPI device + * + * The class contains two constructors that initialize the above + * variables and then open the appropriate spidev device using spiOpen(). + * The class contains one destructor that automatically closes the spidev + * device when object is destroyed by calling spiClose(). + * The spiWriteRead() function sends the data "data" of length "length" + * to the spidevice and at the same time receives data of the same length. + * Resulting data is stored in the "data" variable after the function call. + * ****************************************************************************/ +#include "m_pd.h" +#include <unistd.h> +#include <stdint.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/spi/spidev.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> + +static t_class *disis_spi_class; + +//#define FILE_PREFIX "/sys/class/gpio/" +//#define FILE_EXPORT FILE_PREFIX "export" + +typedef struct _disis_spi +{ + t_object x_obj; + t_outlet *x_out1; + t_outlet *x_out2; + t_symbol *spidev; + unsigned char mode; + unsigned char bitsPerWord; + unsigned int speed; + int spifd; +} t_disis_spi; + +static t_disis_spi *disis_spi_new(t_symbol *devspi); +static int disis_spi_write_read(t_disis_spi *spi, unsigned char *data, int length); +static void disis_spi_open(t_disis_spi *spi, t_symbol *devspi); +static int disis_spi_close(t_disis_spi *spi); +static void disis_spi_free(t_disis_spi *spi); + +/********************************************************** + * disis_spi_open() :function is called by the constructor. + * It is responsible for opening the spidev device + * "devspi" and then setting up the spidev interface. + * private member variables are used to configure spidev. + * They must be set appropriately by constructor before calling + * this function. + * *********************************************************/ +static void disis_spi_open(t_disis_spi *spi, t_symbol *devspi){ + int statusVal = 0; + if (devspi == &s_) + spi->spidev = gensym("/dev/spidev0.0"); + else + spi->spidev = devspi; + spi->spifd = open(devspi->s_name, O_RDWR); + if(spi->spifd < 0) { + pd_error(spi, "could not open SPI device"); + goto spi_output; + } + + statusVal = ioctl (spi->spifd, SPI_IOC_WR_MODE, &(spi->mode)); + if(statusVal < 0){ + pd_error(spi, "Could not set SPIMode (WR)...ioctl fail"); + disis_spi_close(spi); + goto spi_output; + } + + statusVal = ioctl (spi->spifd, SPI_IOC_RD_MODE, &(spi->mode)); + if(statusVal < 0) { + pd_error(spi, "Could not set SPIMode (RD)...ioctl fail"); + disis_spi_close(spi); + goto spi_output; + } + + statusVal = ioctl (spi->spifd, SPI_IOC_WR_BITS_PER_WORD, &(spi->bitsPerWord)); + if(statusVal < 0) { + pd_error(spi, "Could not set SPI bitsPerWord (WR)...ioctl fail"); + disis_spi_close(spi); + goto spi_output; + } + + statusVal = ioctl (spi->spifd, SPI_IOC_RD_BITS_PER_WORD, &(spi->bitsPerWord)); + if(statusVal < 0) { + pd_error(spi, "Could not set SPI bitsPerWord(RD)...ioctl fail"); + disis_spi_close(spi); + goto spi_output; + } + + statusVal = ioctl (spi->spifd, SPI_IOC_WR_MAX_SPEED_HZ, &(spi->speed)); + if(statusVal < 0) { + pd_error(spi, "Could not set SPI speed (WR)...ioctl fail"); + disis_spi_close(spi); + goto spi_output; + } + + statusVal = ioctl (spi->spifd, SPI_IOC_RD_MAX_SPEED_HZ, &(spi->speed)); + if(statusVal < 0) { + pd_error(spi, "Could not set SPI speed (RD)...ioctl fail"); + disis_spi_close(spi); + goto spi_output; + } +spi_output: + outlet_float(spi->x_out2, statusVal); +} + +/*********************************************************** + * disis_spi_close(): Responsible for closing the spidev interface. + * Called in destructor + * *********************************************************/ + +static int disis_spi_close(t_disis_spi *spi){ + int statusVal = -1; + if (spi->spifd == -1) { + pd_error(spi, "disis_spi: device not open\n"); + return(-1); + } + statusVal = close(spi->spifd); + if(statusVal < 0) { + pd_error(spi, "disis_spi: could not close SPI device"); + exit(1); + } + return statusVal; +} + +/******************************************************************** + * This function frees the object (destructor). + * ******************************************************************/ +static void disis_spi_free(t_disis_spi *spi){ + if (spi->spifd == 0) { + disis_spi_close(spi); + } +} + +/******************************************************************** + * This function writes data "data" of length "length" to the spidev + * device. Data shifted in from the spidev device is saved back into + * "data". + * ******************************************************************/ +static int disis_spi_write_read(t_disis_spi *spi, unsigned char *data, int length){ + + struct spi_ioc_transfer spid[length]; + int i = 0; + int retVal = -1; + +// one spi transfer for each byte + + for (i = 0 ; i < length ; i++){ + + spid[i].tx_buf = (unsigned long)(data + i); // transmit from "data" + spid[i].rx_buf = (unsigned long)(data + i); // receive into "data" + spid[i].len = sizeof(*(data + i)); + spid[i].delay_usecs = 0; + spid[i].speed_hz = spi->speed; + spid[i].bits_per_word = spi->bitsPerWord; + spid[i].cs_change = 0; + } + + retVal = ioctl(spi->spifd, SPI_IOC_MESSAGE(length), &spid); + + if(retVal < 0){ + pd_error(spi, "problem transmitting spi data..ioctl"); + } + + return retVal; +} + +/*********************************************************************** + * mcp3008 enabled eternal that by default interacts with /dev/spidev0.0 device using + * disis_spi_MODE_0 (MODE 0) (defined in linux/spi/spidev.h), speed = 1MHz & + * bitsPerWord=8. + * + * on bang call the spi_write_read function on the a2d object and make sure + * that conversion is configured for single ended conversion on CH0 + * i.e. transmit -> byte1 = 0b00000001 (start bit) + * byte2 = 0b1000000 (SGL/DIF = 1, D2=D1=D0=0) + * byte3 = 0b00000000 (Don't care) + * receive -> byte1 = junk + * byte2 = junk + b8 + b9 + * byte3 = b7 - b0 + * + * after conversion must merge data[1] and data[2] to get final result + * + * + * + * *********************************************************************/ + +static void disis_spi_bang(t_disis_spi *spi) +{ + int a2dVal = 0; + int a2dChannel = 0; + unsigned char data[3]; + + data[0] = 1; // first byte transmitted -> start bit + data[1] = 0b10000000 |( ((a2dChannel & 7) << 4)); // second byte transmitted -> (SGL/DIF = 1, D2=D1=D0=0) + data[2] = 0; // third byte transmitted....don't care + + disis_spi_write_read(spi, data, sizeof(data)); + + a2dVal = 0; + a2dVal = (data[1]<< 8) & 0b1100000000; //merge data[1] & data[2] to get result + a2dVal |= (data[2] & 0xff); + fprintf(stderr,"%d\n", a2dVal); + outlet_float(spi->x_out1, a2dVal); +} + +/************************************************* + * init function. lets user set obj variables + * ***********************************************/ +static t_disis_spi *disis_spi_new(t_symbol *devspi){ + t_disis_spi *spi = (t_disis_spi *)pd_new(disis_spi_class); + //fprintf(stderr,"devspi<%s>\n", devspi->s_name); + //t_disis_spi *a2d = disis_spi_new("/dev/spidev0.0", spi_MODE_0, 1000000, 8); + spi->x_out1 = outlet_new(&spi->x_obj, gensym("float")); + spi->x_out2 = outlet_new(&spi->x_obj, gensym("float")); + spi->spidev = devspi; + spi->mode = SPI_MODE_0; + spi->bitsPerWord = 8; + spi->speed = 1000000; + spi->spifd = -1; + + return(spi); +} + + +void disis_spi_setup(void) +{ + disis_spi_class = class_new(gensym("disis_spi"), (t_newmethod)disis_spi_new, + (t_method)disis_spi_free, sizeof(t_disis_spi), 0, A_DEFSYM, 0); + class_addmethod(disis_spi_class, (t_method)disis_spi_open, gensym("open"), + A_DEFSYM, 0); + class_addmethod(disis_spi_class, (t_method)disis_spi_close, gensym("close"), + 0, 0); + //class_addfloat(disis_gpio_class, disis_gpio_float); (later do sending data back to the spi) + class_addbang(disis_spi_class, disis_spi_bang); +} \ No newline at end of file diff --git a/l2ork_addons/raspberry_pi/disis_spi/makefile b/l2ork_addons/raspberry_pi/disis_spi/makefile new file mode 100644 index 0000000000000000000000000000000000000000..9bfb9ba3da4d78e45e221c0e51b3d62acaa432a7 --- /dev/null +++ b/l2ork_addons/raspberry_pi/disis_spi/makefile @@ -0,0 +1,4 @@ +NAME=disis_spi +CSYM=$(NAME) + +include ../makefile.include diff --git a/l2ork_addons/raspberry_pi/disis_spi/spi_original.c b/l2ork_addons/raspberry_pi/disis_spi/spi_original.c new file mode 100644 index 0000000000000000000000000000000000000000..81caae0bf0c9ba49fa54ca21ec1659509826db47 --- /dev/null +++ b/l2ork_addons/raspberry_pi/disis_spi/spi_original.c @@ -0,0 +1,224 @@ +/*********************************************************************** + * This header file contains the mcp3008Spi class definition. + * Its main purpose is to communicate with the MCP3008 chip using + * the userspace spidev facility. + * The class contains four variables: + * mode -> defines the SPI mode used. In our case it is SPI_MODE_0. + * bitsPerWord -> defines the bit width of the data transmitted. + * This is normally 8. Experimentation with other values + * didn't work for me + * speed -> Bus speed or SPI clock frequency. According to + * https://projects.drogon.net/understanding-spi-on-the-raspberry-pi/ + * It can be only 0.5, 1, 2, 4, 8, 16, 32 MHz. + * Will use 1MHz for now and test it further. + * spifd -> file descriptor for the SPI device + * + * The class contains two constructors that initialize the above + * variables and then open the appropriate spidev device using spiOpen(). + * The class contains one destructor that automatically closes the spidev + * device when object is destroyed by calling spiClose(). + * The spiWriteRead() function sends the data "data" of length "length" + * to the spidevice and at the same time receives data of the same length. + * Resulting data is stored in the "data" variable after the function call. + * ****************************************************************************/ +#include <unistd.h> +#include <stdint.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/spi/spidev.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> + +typedef struct _spi { + unsigned char mode; + unsigned char bitsPerWord; + unsigned int speed; + int spifd; +} t_spi; + +t_spi *spi_new(const char *devspi, unsigned char spiMode, unsigned int spiSpeed, unsigned char spibitsPerWord); +int spi_WriteRead(t_spi *spi, unsigned char *data, int length); +int spi_Open(t_spi *spi, const char *devspi); +int spi_Close(t_spi *spi); + +//using namespace std; +/********************************************************** + * spiOpen() :function is called by the constructor. + * It is responsible for opening the spidev device + * "devspi" and then setting up the spidev interface. + * private member variables are used to configure spidev. + * They must be set appropriately by constructor before calling + * this function. + * *********************************************************/ +int spi_Open(t_spi *spi, const char *devspi){ + int statusVal = -1; + spi->spifd = open(devspi, O_RDWR); + if(spi->spifd < 0) { + perror("could not open SPI device"); + exit(1); + } + + statusVal = ioctl (spi->spifd, SPI_IOC_WR_MODE, &(spi->mode)); + if(statusVal < 0){ + perror("Could not set SPIMode (WR)...ioctl fail"); + exit(1); + } + + statusVal = ioctl (spi->spifd, SPI_IOC_RD_MODE, &(spi->mode)); + if(statusVal < 0) { + perror("Could not set SPIMode (RD)...ioctl fail"); + exit(1); + } + + statusVal = ioctl (spi->spifd, SPI_IOC_WR_BITS_PER_WORD, &(spi->bitsPerWord)); + if(statusVal < 0) { + perror("Could not set SPI bitsPerWord (WR)...ioctl fail"); + exit(1); + } + + statusVal = ioctl (spi->spifd, SPI_IOC_RD_BITS_PER_WORD, &(spi->bitsPerWord)); + if(statusVal < 0) { + perror("Could not set SPI bitsPerWord(RD)...ioctl fail"); + exit(1); + } + + statusVal = ioctl (spi->spifd, SPI_IOC_WR_MAX_SPEED_HZ, &(spi->speed)); + if(statusVal < 0) { + perror("Could not set SPI speed (WR)...ioctl fail"); + exit(1); + } + + statusVal = ioctl (spi->spifd, SPI_IOC_RD_MAX_SPEED_HZ, &(spi->speed)); + if(statusVal < 0) { + perror("Could not set SPI speed (RD)...ioctl fail"); + exit(1); + } + return statusVal; +} + +/*********************************************************** + * spiClose(): Responsible for closing the spidev interface. + * Called in destructor + * *********************************************************/ + +int spi_free(t_spi *spi){ + int statusVal = -1; + statusVal = close(spi->spifd); + if(statusVal < 0) { + perror("Could not close SPI device"); + exit(1); + } + return statusVal; +} + +/******************************************************************** + * This function writes data "data" of length "length" to the spidev + * device. Data shifted in from the spidev device is saved back into + * "data". + * ******************************************************************/ +int spi_WriteRead(t_spi *spi, unsigned char *data, int length){ + + struct spi_ioc_transfer spid[length]; + int i = 0; + int retVal = -1; + +// one spi transfer for each byte + + for (i = 0 ; i < length ; i++){ + + spid[i].tx_buf = (unsigned long)(data + i); // transmit from "data" + spid[i].rx_buf = (unsigned long)(data + i); // receive into "data" + spid[i].len = sizeof(*(data + i)); + spid[i].delay_usecs = 0; + spid[i].speed_hz = spi->speed; + spid[i].bits_per_word = spi->bitsPerWord; + spid[i].cs_change = 0; + } + + retVal = ioctl(spi->spifd, SPI_IOC_MESSAGE(length), &spid); + + if(retVal < 0){ + perror("Problem transmitting spi data..ioctl"); + exit(1); + } + + return retVal; +} + +/************************************************* + * Default constructor. Set member variables to + * default values and then call spiOpen() + * ***********************************************/ +/* +t_spi *spi_new(){ + spi->mode = SPI_MODE_0 ; + spi->bitsPerWord = 8; + spi->speed = 1000000; + spi->spifd = -1; + + spi->spiOpen(std::string("/dev/spidev0.0")); + + } + */ +/************************************************* + * overloaded constructor. let user set member variables to + * and then call spiOpen() + * ***********************************************/ +t_spi *spi_new(const char *devspi, unsigned char spiMode, unsigned int spiSpeed, unsigned char spibitsPerWord){ + t_spi *spi = (t_spi *)malloc(sizeof(t_spi)); + spi->mode = spiMode; + spi->bitsPerWord = spibitsPerWord; + spi->speed = spiSpeed; + spi->spifd = -1; + + spi_Open(spi, devspi); + return spi; +} + +/*********************************************************************** + * mcp3008SpiTest.cpp. Sample program that tests the mcp3008Spi class. + * an mcp3008Spi class object (a2d) is created. the a2d object is instantiated + * using the overloaded constructor. which opens the spidev0.0 device with + * SPI_MODE_0 (MODE 0) (defined in linux/spi/spidev.h), speed = 1MHz & + * bitsPerWord=8. + * + * call the spiWriteRead function on the a2d object 20 times. Each time make sure + * that conversion is configured for single ended conversion on CH0 + * i.e. transmit -> byte1 = 0b00000001 (start bit) + * byte2 = 0b1000000 (SGL/DIF = 1, D2=D1=D0=0) + * byte3 = 0b00000000 (Don't care) + * receive -> byte1 = junk + * byte2 = junk + b8 + b9 + * byte3 = b7 - b0 + * + * after conversion must merge data[1] and data[2] to get final result + * + * + * + * *********************************************************************/ +//using namespace std; + +int main(void) +{ + t_spi *a2d = spi_new("/dev/spidev0.0", SPI_MODE_0, 1000000, 8); + int a2dVal = 0; + int a2dChannel = 0; + unsigned char data[3]; + + while(1) + { + data[0] = 1; // first byte transmitted -> start bit + data[1] = 0b10000000 |( ((a2dChannel & 7) << 4)); // second byte transmitted -> (SGL/DIF = 1, D2=D1=D0=0) + data[2] = 0; // third byte transmitted....don't care + + spi_WriteRead(a2d, data, sizeof(data)); + + a2dVal = 0; + a2dVal = (data[1]<< 8) & 0b1100000000; //merge data[1] & data[2] to get result + a2dVal |= (data[2] & 0xff); + usleep(10); + fprintf(stderr,"%d\n", a2dVal); + } + return 0; +} diff --git a/l2ork_addons/raspberry_pi/gpio/disis_gpio.c b/l2ork_addons/raspberry_pi/gpio/disis_gpio.c new file mode 100644 index 0000000000000000000000000000000000000000..2ee57174fd42470337a066279793658f4cd95622 --- /dev/null +++ b/l2ork_addons/raspberry_pi/gpio/disis_gpio.c @@ -0,0 +1,140 @@ +/* gpio - Pi gpio pins via /sys/etc */ +/* see http://elinux.org/RPi_Low-level_peripherals */ + +/* Copyright Miller Puckette - BSD license */ + +#include "m_pd.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +static t_class *gpio_class; + +#define FILE_PREFIX "/sys/class/gpio/" +#define FILE_EXPORT FILE_PREFIX "export" + +typedef struct _gpio +{ + t_object x_obj; + t_outlet *x_out1; + int x_pin; + int x_fdvalue; +} t_gpio; + +static void gpio_enable(t_gpio *x, t_float f) +{ + char buf[1024]; + if (f != 0) + sprintf(buf, "echo %d > /sys/class/gpio/export\n", x->x_pin); + else sprintf(buf, "echo %d > /sys/class/gpio/unexport\n", x->x_pin); + system(buf); +} + +static void gpio_output(t_gpio *x, t_float f) +{ + char buf[1024]; + if (f != 0) + sprintf(buf, "echo out > /sys/class/gpio/gpio%d/direction\n", + x->x_pin); + else sprintf(buf, "echo in > /sys/class/gpio/gpio%d/direction\n", + x->x_pin); + system(buf); +} + +static void gpio_open(t_gpio *x, t_float f) +{ + char buf[1024]; + sprintf(buf, "/sys/class/gpio/gpio%d/value", x->x_pin); + if (f != 0) + { + if (x->x_fdvalue >= 0) + post("gpio: already open"); + else + { + x->x_fdvalue = open(buf, O_RDWR); + if (x->x_fdvalue < 0) + post("%s: %s", buf, strerror(errno)); + } + } + else + { + if (x->x_fdvalue < 0) + post("gpio: already closed"); + else + { + close(x->x_fdvalue); + x->x_fdvalue = -1; + } + } +} + +static void gpio_float(t_gpio *x, t_float f) +{ + char buf[1024]; + if (x->x_fdvalue < 0) + pd_error(x, "gpio: not open"); + else + { + sprintf(buf, "%d\n", (f != 0)); + if (write(x->x_fdvalue, buf, strlen(buf)) < (int)strlen(buf)) + pd_error(x, "gpio_float: %s", strerror(errno)); + } +} + +static void gpio_bang(t_gpio *x) +{ + char buf[1024]; + if (x->x_fdvalue < 0) + pd_error(x, "gpio: not open"); + else + { + int rval = lseek(x->x_fdvalue, 0, 0); + if (rval < 0) + { + pd_error(x, "gpio_bang (seek): %s", strerror(errno)); + return; + } + rval = read(x->x_fdvalue, buf, sizeof(buf)-1); + if (rval < 0) + { + pd_error(x, "gpio_bang (read): %s", strerror(errno)); + return; + } + buf[rval] = 0; + if (sscanf(buf, "%d", &rval) < 1) + { + pd_error(x, "gpio_bang: couldn't parse string: %s", buf); + return; + } + outlet_float(x->x_out1, rval); + } +} + +static void *gpio_new(t_floatarg f) +{ + t_gpio *x = (t_gpio *)pd_new(gpio_class); + x->x_out1 = outlet_new(&x->x_obj, gensym("float")); + x->x_fdvalue = -1; + x->x_pin = f; + return (x); +} + + +void gpio_setup(void) +{ + gpio_class = class_new(gensym("gpio"), (t_newmethod)gpio_new, + 0, sizeof(t_gpio), 0, A_DEFFLOAT, 0); + class_addmethod(gpio_class, (t_method)gpio_enable, gensym("enable"), + A_FLOAT, 0); + class_addmethod(gpio_class, (t_method)gpio_output, gensym("output"), + A_FLOAT, 0); + class_addmethod(gpio_class, (t_method)gpio_open, gensym("open"), + A_FLOAT, 0); + class_addfloat(gpio_class, gpio_float); + class_addbang(gpio_class, gpio_bang); +} diff --git a/l2ork_addons/raspberry_pi/gpio/gpio.c b/l2ork_addons/raspberry_pi/gpio/gpio.c new file mode 100644 index 0000000000000000000000000000000000000000..2ee57174fd42470337a066279793658f4cd95622 --- /dev/null +++ b/l2ork_addons/raspberry_pi/gpio/gpio.c @@ -0,0 +1,140 @@ +/* gpio - Pi gpio pins via /sys/etc */ +/* see http://elinux.org/RPi_Low-level_peripherals */ + +/* Copyright Miller Puckette - BSD license */ + +#include "m_pd.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +static t_class *gpio_class; + +#define FILE_PREFIX "/sys/class/gpio/" +#define FILE_EXPORT FILE_PREFIX "export" + +typedef struct _gpio +{ + t_object x_obj; + t_outlet *x_out1; + int x_pin; + int x_fdvalue; +} t_gpio; + +static void gpio_enable(t_gpio *x, t_float f) +{ + char buf[1024]; + if (f != 0) + sprintf(buf, "echo %d > /sys/class/gpio/export\n", x->x_pin); + else sprintf(buf, "echo %d > /sys/class/gpio/unexport\n", x->x_pin); + system(buf); +} + +static void gpio_output(t_gpio *x, t_float f) +{ + char buf[1024]; + if (f != 0) + sprintf(buf, "echo out > /sys/class/gpio/gpio%d/direction\n", + x->x_pin); + else sprintf(buf, "echo in > /sys/class/gpio/gpio%d/direction\n", + x->x_pin); + system(buf); +} + +static void gpio_open(t_gpio *x, t_float f) +{ + char buf[1024]; + sprintf(buf, "/sys/class/gpio/gpio%d/value", x->x_pin); + if (f != 0) + { + if (x->x_fdvalue >= 0) + post("gpio: already open"); + else + { + x->x_fdvalue = open(buf, O_RDWR); + if (x->x_fdvalue < 0) + post("%s: %s", buf, strerror(errno)); + } + } + else + { + if (x->x_fdvalue < 0) + post("gpio: already closed"); + else + { + close(x->x_fdvalue); + x->x_fdvalue = -1; + } + } +} + +static void gpio_float(t_gpio *x, t_float f) +{ + char buf[1024]; + if (x->x_fdvalue < 0) + pd_error(x, "gpio: not open"); + else + { + sprintf(buf, "%d\n", (f != 0)); + if (write(x->x_fdvalue, buf, strlen(buf)) < (int)strlen(buf)) + pd_error(x, "gpio_float: %s", strerror(errno)); + } +} + +static void gpio_bang(t_gpio *x) +{ + char buf[1024]; + if (x->x_fdvalue < 0) + pd_error(x, "gpio: not open"); + else + { + int rval = lseek(x->x_fdvalue, 0, 0); + if (rval < 0) + { + pd_error(x, "gpio_bang (seek): %s", strerror(errno)); + return; + } + rval = read(x->x_fdvalue, buf, sizeof(buf)-1); + if (rval < 0) + { + pd_error(x, "gpio_bang (read): %s", strerror(errno)); + return; + } + buf[rval] = 0; + if (sscanf(buf, "%d", &rval) < 1) + { + pd_error(x, "gpio_bang: couldn't parse string: %s", buf); + return; + } + outlet_float(x->x_out1, rval); + } +} + +static void *gpio_new(t_floatarg f) +{ + t_gpio *x = (t_gpio *)pd_new(gpio_class); + x->x_out1 = outlet_new(&x->x_obj, gensym("float")); + x->x_fdvalue = -1; + x->x_pin = f; + return (x); +} + + +void gpio_setup(void) +{ + gpio_class = class_new(gensym("gpio"), (t_newmethod)gpio_new, + 0, sizeof(t_gpio), 0, A_DEFFLOAT, 0); + class_addmethod(gpio_class, (t_method)gpio_enable, gensym("enable"), + A_FLOAT, 0); + class_addmethod(gpio_class, (t_method)gpio_output, gensym("output"), + A_FLOAT, 0); + class_addmethod(gpio_class, (t_method)gpio_open, gensym("open"), + A_FLOAT, 0); + class_addfloat(gpio_class, gpio_float); + class_addbang(gpio_class, gpio_bang); +} diff --git a/l2ork_addons/raspberry_pi/gpio/gpio.l_arm b/l2ork_addons/raspberry_pi/gpio/gpio.l_arm new file mode 100755 index 0000000000000000000000000000000000000000..aca8fbd97b9dc1b20eb2ee09ee2f53fac03e37c6 Binary files /dev/null and b/l2ork_addons/raspberry_pi/gpio/gpio.l_arm differ diff --git a/l2ork_addons/raspberry_pi/gpio/makefile b/l2ork_addons/raspberry_pi/gpio/makefile new file mode 100644 index 0000000000000000000000000000000000000000..dd3e3df141950357184906fd7a83c9d6f905e4aa --- /dev/null +++ b/l2ork_addons/raspberry_pi/gpio/makefile @@ -0,0 +1,4 @@ +NAME=gpio +CSYM=$(NAME) + +include ../makefile.include diff --git a/l2ork_addons/raspberry_pi/gpio/test-gpio.pd b/l2ork_addons/raspberry_pi/gpio/test-gpio.pd new file mode 100644 index 0000000000000000000000000000000000000000..bd2e7c5ceeef7355c83dd99f53170340bc85cbc9 --- /dev/null +++ b/l2ork_addons/raspberry_pi/gpio/test-gpio.pd @@ -0,0 +1,25 @@ +#N canvas 187 97 450 300 10; +#X obj 82 58 gpio 4; +#X msg 73 21 open 1; +#X msg 134 22 open 0; +#X obj 215 24 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X obj 255 24 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 +-1; +#X floatatom 94 96 5 0 0 0 - - -; +#X obj 95 122 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 +-1; +#X obj 231 62 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X obj 231 93 metro 1; +#X obj 175 149 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X obj 175 180 metro 1; +#X connect 1 0 0 0; +#X connect 2 0 0 0; +#X connect 3 0 0 0; +#X connect 4 0 0 0; +#X connect 5 0 6 0; +#X connect 7 0 8 0; +#X connect 8 0 0 0; +#X connect 9 0 10 0; diff --git a/l2ork_addons/raspberry_pi/makefile.include b/l2ork_addons/raspberry_pi/makefile.include new file mode 100644 index 0000000000000000000000000000000000000000..23cef8b87c0431d6178f1f1f390af12d5c18a3ae --- /dev/null +++ b/l2ork_addons/raspberry_pi/makefile.include @@ -0,0 +1,134 @@ +current: l_i386 l_ia64 + +# which OS to compile for +UNAME := $(shell uname -s) +ifeq ($(UNAME),Linux) + OS_NAME = linux + EXTENSION = pd_linux + DYLIB_EXTENSION = so +endif +ifeq ($(UNAME),Darwin) + OS_NAME = darwin + EXTENSION = pd_darwin + DYLIB_EXTENSION = dylib +endif +ifeq (MINGW,$(findstring MINGW,$(UNAME))) + OS_NAME = windows + EXTENSION = dll + DYLIB_EXTENSION = dll +endif +ifeq (CYGWIN,$(findstring CYGWIN,$(UNAME))) + OS_NAME = windows + EXTENSION = dll + DYLIB_EXTENSION = dll +endif +# which CPU to compile for +UNAME_MACHINE := $(shell uname -m) +ifeq ($(UNAME_MACHINE),x86_64) + ARCH = l_ia64 +endif +ifeq ($(UNAME_MACHINE),i386) + ARCH = l_i386 +endif +ifeq ($(UNAME_MACHINE),i686) + ARCH = l_i386 +endif +ifeq ($(UNAME_MACHINE),ppc) + ARCH = powerpc +endif + +# ----------------------- Windows ----------------------- + +pd_nt: $(NAME).dll + +.SUFFIXES: .dll + +PDNTCFLAGS = /W3 /WX /DNT /DPD /nologo +VC="C:\Program Files\Microsoft Visual Studio\Vc98" + +PDNTINCLUDE = /I. /I..\pd\src /I..\..\pd\src /I..\..\..\pd\src /I$(VC)\include + +PDNTLDIR = $(VC)\lib +PDNTLIB = $(PDNTLDIR)\libc.lib \ + $(PDNTLDIR)\oldnames.lib \ + $(PDNTLDIR)\kernel32.lib \ + ..\..\bin\pd.lib + +.c.dll: + cl $(PDNTCFLAGS) $(PDNTINCLUDE) /c $*.c + link /dll /export:$(CSYM)_setup $*.obj $(PDNTLIB) + +# ----------------------- LINUX i386 and ia64 ----------------------- + +l_i386: $(NAME).l_i386 +l_ia64: $(NAME).l_ia64 + +.SUFFIXES: .l_i386 .l_ia64 .l_arm + +LINUXCFLAGS = -DPD -O2 -funroll-loops -fomit-frame-pointer \ + -fno-strict-aliasing -Wall -W -Wshadow -Wstrict-prototypes \ + -Wno-unused -Wno-parentheses -Wno-switch $(CFLAGS) + +UNIXINCLUDE = -I../pd/src -I../../pd/src -I../../../pd/src \ + -I../../../../pd/src -I../../../../../pd/src +LINUXINCLUDE = $(UNIXINCLUDE) + +.c.l_i386: + $(CC) $(LINUXCFLAGS) $(LINUXINCLUDE) -m32 -o $*.o -c $*.c + $(CC) -m32 -shared -o $*.l_i386 $*.o -lc -lm + strip --strip-unneeded $*.l_i386 + rm -f $*.o + mv $(NAME).l_i386 $(NAME).pd_linux + +.c.l_ia64: + cc $(LINUXCFLAGS) $(LINUXINCLUDE) -fPIC -o $*.o -c $*.c + ld -shared -o $*.l_ia64 $*.o -lc -lm + strip --strip-unneeded $*.l_ia64 + rm $*.o + mv $(NAME).l_ia64 $(NAME).pd_linux + +.c.l_arm: + cc $(LINUXCFLAGS) $(LINUXINCLUDE) -o $*.o -c $*.c + ld -shared -o $*.l_arm $*.o -lc -lm + strip --strip-unneeded $*.l_arm + rm $*.o + mv $(NAME).l_arm $(NAME).pd_linux + +linux: +ifeq ($(UNAME),Linux) +ifeq ($(ARCH),l_ia64) + make l_ia64 +endif +ifeq ($(ARCH),l_arm) + make l_arm +endif +ifeq ($(ARCH),l_i386) + make l_i386 +endif +endif + +# ----------------------- Mac OSX ----------------------- + +d_ppc: $(NAME).d_ppc +d_fat: $(NAME).d_fat + +.SUFFIXES: .d_ppc .d_fat + +DARWINCFLAGS = -DPD -O2 -Wall -W -Wshadow -Wstrict-prototypes \ + -Wno-unused -Wno-parentheses -Wno-switch + +.c.d_ppc: + $(CC) $(DARWINCFLAGS) $(UNIXINCLUDE) -o $*.o -c $*.c + $(CC) -bundle -undefined suppress -flat_namespace -o $*.pd_darwin $*.o + rm -f $*.o + +.c.d_fat: + $(CC) -arch i386 -arch ppc $(DARWINCFLAGS) $(UNIXINCLUDE) -o $*.o -c $*.c + $(CC) -arch i386 -arch ppc -bundle -undefined suppress -flat_namespace \ + -o $*.d_fat $*.o + rm -f $*.o + +# ---------------------------------------------------------- + +clean: + rm -f *.o *.pd_* *.d_ppc *.d_fat *.l_i386 *.l_ia64 *.dll diff --git a/l2ork_addons/raspberry_pi/netreceive_tilde/float_cast.h b/l2ork_addons/raspberry_pi/netreceive_tilde/float_cast.h new file mode 100644 index 0000000000000000000000000000000000000000..0ebc4bb448ef9d5108687c015aa9063cce6eff5c --- /dev/null +++ b/l2ork_addons/raspberry_pi/netreceive_tilde/float_cast.h @@ -0,0 +1,203 @@ +/* +** Copyright (C) 2001-2003 Erik de Castro Lopo <erikd@mega-nerd.com> +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** 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, write to the Free Software +** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* Version 1.3 */ + + +/*============================================================================ +** On Intel Pentium processors (especially PIII and probably P4), converting +** from float to int is very slow. To meet the C specs, the code produced by +** most C compilers targeting Pentium needs to change the FPU rounding mode +** before the float to int conversion is performed. +** +** Changing the FPU rounding mode causes the FPU pipeline to be flushed. It +** is this flushing of the pipeline which is so slow. +** +** Fortunately the ISO C99 specifications define the functions lrint, lrintf, +** llrint and llrintf which fix this problem as a side effect. +** +** On Unix-like systems, the configure process should have detected the +** presence of these functions. If they weren't found we have to replace them +** here with a standard C cast. +*/ + +/* +** The C99 prototypes for lrint and lrintf are as follows: +** +** long int lrintf (float x) ; +** long int lrint (double x) ; +*/ + +// #include "config.h" + +/* +** The presence of the required functions are detected during the configure +** process and the values HAVE_LRINT and HAVE_LRINTF are set accordingly in +** the config.h file. +*/ + +#define HAVE_LRINT_REPLACEMENT 0 + +#if (HAVE_LRINT && HAVE_LRINTF) + + /* + ** These defines enable functionality introduced with the 1999 ISO C + ** standard. They must be defined before the inclusion of math.h to + ** engage them. If optimisation is enabled, these functions will be + ** inlined. With optimisation switched off, you have to link in the + ** maths library using -lm. + */ + + #define _ISOC9X_SOURCE 1 + #define _ISOC99_SOURCE 1 + + #define __USE_ISOC9X 1 + #define __USE_ISOC99 1 + + #include <math.h> + +#elif (defined (WIN32) || defined (_WIN32)) + + #undef HAVE_LRINT_REPLACEMENT + #define HAVE_LRINT_REPLACEMENT 1 + #include <math.h> + + /* + ** Win32 doesn't seem to have these functions. + ** Therefore implement inline versions of these functions here. + */ + + __inline long int + lrint (double flt) + { int intgr; + + _asm + { fld flt + fistp intgr + } ; + + return intgr ; + } + + __inline long int + lrintf (float flt) + { int intgr; + + _asm + { fld flt + fistp intgr + } ; + + return intgr ; + } + +#elif (defined (__MWERKS__) && defined (macintosh)) + + /* This MacOS 9 solution was provided by Stephane Letz */ + + #undef HAVE_LRINT_REPLACEMENT + #define HAVE_LRINT_REPLACEMENT 1 + #include <math.h> + + #undef lrint + #undef lrintf + + #define lrint double2int + #define lrintf float2int + + inline int + float2int (register float in) + { long res [2] ; + + asm + { fctiw in,in + stfd in,res + } + return res [1] ; + } /* float2int */ + + inline int + double2int (register double in) + { long res [2] ; + + asm + { fctiw in,in + stfd in,res + } + return res [1] ; + } /* double2int */ + +#elif (defined (__MACH__) && defined (__APPLE__)) + + /* For Apple MacOSX. */ + + #undef HAVE_LRINT_REPLACEMENT + #define HAVE_LRINT_REPLACEMENT 1 + #include <math.h> + + #undef lrint + #undef lrintf + + #define lrint double2int + #define lrintf float2int + + inline static long int + float2int (register float in) + { int res [2] ; + + __asm__ __volatile__ + ( "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (in) /* Input */ + : "memory" + ) ; + + return res [1] ; + } /* lrintf */ + + inline static long int + double2int (register double in) + { int res [2] ; + + __asm__ __volatile__ + ( "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (in) /* Input */ + : "memory" + ) ; + + return res [1] ; + } /* lrint */ + +#else + #ifndef __sgi + #warning "Don't have the functions lrint() and lrintf()." + #warning "Replacing these functions with a standard C cast." + #endif + + #include <math.h> + + #define lrint(dbl) ((int) (dbl)) + #define lrintf(flt) ((int) (flt)) + +#endif + + + diff --git a/l2ork_addons/raspberry_pi/netreceive_tilde/makefile b/l2ork_addons/raspberry_pi/netreceive_tilde/makefile new file mode 100644 index 0000000000000000000000000000000000000000..be11c9101b3d37fe89ffd933e2f67ca8751f0828 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netreceive_tilde/makefile @@ -0,0 +1,4 @@ +NAME=netreceive~ +CSYM=netreceive_tilde + +include ../makefile.include diff --git a/l2ork_addons/raspberry_pi/netreceive_tilde/netreceive~.c b/l2ork_addons/raspberry_pi/netreceive_tilde/netreceive~.c new file mode 100644 index 0000000000000000000000000000000000000000..6ccf27a51558f5e3890d20bfbb7b273e5d35dc64 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netreceive_tilde/netreceive~.c @@ -0,0 +1,1358 @@ +/* ------------------------ netreceive~ --------------------------------------- */ +/* */ +/* Tilde object to receive uncompressed audio data from netsend~. */ +/* Written by Olaf Matthes <olaf.matthes@gmx.de>. */ +/* Based on streamin~ by Guenter Geiger. */ +/* Get source at http://www.akustische-kunst.org/ */ +/* */ +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ +/* */ +/* See file LICENSE for further informations on licensing terms. */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* This project was commissioned by the Society for Arts and Technology [SAT], */ +/* Montreal, Quebec, Canada, http://www.sat.qc.ca/. */ +/* */ +/* ---------------------------------------------------------------------------- */ + + +#ifdef PD +#include "m_pd.h" +#else +#include "ext.h" +#include "z_dsp.h" +#endif + +#include "netsend~.h" + +#ifdef USE_FAAC +#include "faad/faad.h" +#endif + +#include <sys/types.h> +#include <string.h> +#if defined(__linux__) || defined(__apple__) +#include <sys/socket.h> +#include <errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/time.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#define SOCKET_ERROR -1 +#else +#include <winsock.h> +#endif + +#ifndef SOL_IP +#define SOL_IP IPPROTO_IP +#endif + +#ifdef NT +#pragma warning( disable : 4244 ) +#pragma warning( disable : 4305 ) +#endif + +#define DEFAULT_AUDIO_BUFFER_FRAMES 16 /* a small circ. buffer for 16 frames */ +#define DEFAULT_AVERAGE_NUMBER 10 /* number of values we store for average history */ +#define DEFAULT_NETWORK_POLLTIME 1 /* interval in ms for polling for input data (Max/MSP only) */ +#define DEFAULT_QUEUE_LENGTH 3 /* min. number of buffers that can be used reliably on your hardware */ + + +#if defined(__linux__) || defined(__apple__) +#define CLOSESOCKET(fd) close(fd) +#endif +#ifdef _WINDOWS +#define CLOSESOCKET(fd) closesocket(fd) +#endif + +#ifdef PD +/* these would require to include some headers that are different + between pd 0.36 and later, so it's easier to do it like this! */ +EXTERN void sys_rmpollfn(int fd); +EXTERN void sys_addpollfn(int fd, void* fn, void *ptr); +#endif + +static int netreceive_tilde_sockerror(char *s) +{ +#ifdef NT + int err = WSAGetLastError(); + if (err == 10054) return 1; + else if (err == 10040) post("netsend~: %s: message too long (%d)", s, err); + else if (err == 10053) post("netsend~: %s: software caused connection abort (%d)", s, err); + else if (err == 10055) post("netsend~: %s: no buffer space available (%d)", s, err); + else if (err == 10060) post("netsend~: %s: connection timed out (%d)", s, err); + else if (err == 10061) post("netsend~: %s: connection refused (%d)", s, err); + else post("netreceive~: %s: %s (%d)", s, strerror(err), err); +#else + int err = errno; + post("netreceive~: %s: %s (%d)", s, strerror(err), err); +#endif +#ifdef NT + if (err == WSAEWOULDBLOCK) +#endif +#if defined(__linux__) || defined(__apple__) + if (err == EAGAIN) +#endif + { + return 1; /* recoverable error */ + } + return 0; /* indicate non-recoverable error */ +} + + +static int netreceive_tilde_setsocketoptions(int sockfd) +{ + int sockopt = 1; + if (setsockopt(sockfd, SOL_IP, TCP_NODELAY, (const char*)&sockopt, sizeof(int)) < 0) + post("setsockopt NODELAY failed"); + + sockopt = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&sockopt, sizeof(int)) < 0) + post("netreceive~: setsockopt REUSEADDR failed"); + return 0; +} + + + +/* ------------------------ netreceive~ ----------------------------- */ + + +static t_class *netreceive_tilde_class; +static t_symbol *ps_format, *ps_channels, *ps_framesize, *ps_overflow, *ps_underflow, + *ps_queuesize, *ps_average, *ps_sf_float, *ps_sf_16bit, *ps_sf_8bit, + *ps_sf_mp3, *ps_sf_aac, *ps_sf_unknown, *ps_bitrate, *ps_hostname, *ps_nothing; + + +typedef struct _netreceive_tilde +{ +#ifdef PD + t_object x_obj; + t_outlet *x_outlet1; + t_outlet *x_outlet2; +#else + t_pxobject x_obj; + void *x_outlet1; + void *x_outlet2; + void *x_connectpoll; + void *x_datapoll; +#endif + int x_socket; + int x_connectsocket; + int x_nconnections; + int x_ndrops; + int x_tcp; + t_symbol *x_hostname; + + /* buffering */ + int x_framein; + int x_frameout; + t_frame x_frames[DEFAULT_AUDIO_BUFFER_FRAMES]; + int x_maxframes; + long x_framecount; + int x_blocksize; + int x_blocksperrecv; + int x_blockssincerecv; + + int x_nbytes; + int x_counter; + int x_average[DEFAULT_AVERAGE_NUMBER]; + int x_averagecur; + int x_underflow; + int x_overflow; + +#ifdef USE_FAAC + faacDecHandle x_faac_decoder; + faacDecFrameInfo x_faac_frameInfo; + faacDecConfigurationPtr x_faac_config; + int x_faac_init; + unsigned char x_faac_buf[FAAD_MIN_STREAMSIZE * DEFAULT_AUDIO_CHANNELS]; + unsigned long x_faac_bytes; +#endif + + long x_samplerate; + int x_noutlets; + int x_vecsize; + t_int **x_myvec; /* vector we pass on to the DSP routine */ +} t_netreceive_tilde; + + + +/* prototypes (as needed) */ +static void netreceive_tilde_kick(t_netreceive_tilde *x); + + + +#ifdef USE_FAAC +/* open encoder and set default values */ +static void netreceive_tilde_faac_open(t_netreceive_tilde* x) +{ + x->x_faac_decoder = faacDecOpen(); + x->x_faac_config = faacDecGetCurrentConfiguration(x->x_faac_decoder); + x->x_faac_config->defSampleRate = x->x_samplerate; + x->x_faac_config->defObjectType = MAIN; // LC; + x->x_faac_config->outputFormat = FAAD_FMT_FLOAT; + faacDecSetConfiguration(x->x_faac_decoder, x->x_faac_config); + x->x_faac_init = 0; + x->x_faac_bytes = 0; +} + +static void netreceive_tilde_faac_close(t_netreceive_tilde* x) +{ + if (x->x_faac_decoder != NULL) + faacDecClose(x->x_faac_decoder); + x->x_faac_decoder = NULL; + x->x_faac_init = 0; +} + +/* init decoder when we get a new stream */ +static int netreceive_tilde_faac_init(t_netreceive_tilde* x, int frame) +{ + unsigned long samplerate; + unsigned char channels; + long bytes_consumed = 0; + + if ((bytes_consumed = faacDecInit(x->x_faac_decoder, x->x_faac_buf, x->x_faac_bytes, &samplerate, &channels)) < 0) + { + faacDecConfigurationPtr config; + error("netreceive~: faac: initializing decoder library failed"); + netreceive_tilde_faac_close(x); + return -1; + } + else if (samplerate != (unsigned long)x->x_samplerate) + { + error("netreceive~: incoming stream has wrong samplerate"); + netreceive_tilde_faac_close(x); + return -1; + } + + /* adjust accumulating AAC buffer */ + memmove(x->x_faac_buf, x->x_faac_buf + bytes_consumed, x->x_faac_bytes - bytes_consumed); + x->x_faac_bytes -= bytes_consumed; + + x->x_faac_init = 1; /* indicate that decoder is ready */ + return 0; +} + +/* decode AAC using FAAD2 library */ +static int netreceive_tilde_faac_decode(t_netreceive_tilde* x, int frame) +{ + unsigned int i, ret; + float *sample_buffer; + + /* open decoder, if not yet done */ + if (x->x_faac_decoder == NULL) + { + netreceive_tilde_faac_open(x); + } + + /* add new AAC data into buffer */ + memcpy(x->x_faac_buf + x->x_faac_bytes, x->x_frames[frame].data, x->x_frames[frame].tag.framesize); + x->x_faac_bytes += x->x_frames[frame].tag.framesize; + + /* in case we have more than FAAD_MIN_STREAMSIZE bytes per channel try decoding */ + if (x->x_faac_bytes >= (unsigned long)(FAAD_MIN_STREAMSIZE * x->x_frames[frame].tag.channels)) + { + /* init decoder, if not yet done */ + if (!x->x_faac_init) + { + ret = netreceive_tilde_faac_init(x, frame); + if (ret == -1) + { + return -1; + } + } + + /* decode data */ + memset(&x->x_faac_frameInfo, 0, sizeof(faacDecFrameInfo)); + sample_buffer = (float *)faacDecDecode(x->x_faac_decoder, &x->x_faac_frameInfo, x->x_faac_buf, x->x_faac_bytes); + if (x->x_faac_frameInfo.error != 0) + { + error("netreceive~: faac: %s", faacDecGetErrorMessage(x->x_faac_frameInfo.error)); + netreceive_tilde_faac_close(x); + return -1; + } + + /* adjust accumulating AAC buffer */ + memmove(x->x_faac_buf, x->x_faac_buf + x->x_faac_frameInfo.bytesconsumed, x->x_faac_bytes - x->x_faac_frameInfo.bytesconsumed); + x->x_faac_bytes -= x->x_faac_frameInfo.bytesconsumed; + + /* copy decoded PCM samples back to frame */ + memcpy(x->x_frames[frame].data, sample_buffer, x->x_faac_frameInfo.samples * sizeof(float)); + + /* return number of decoded PCM samples */ + return x->x_faac_frameInfo.samples * SF_SIZEOF(SF_FLOAT); + } + else + { + return 0; /* indicate we didn't get any new audio data */ + } +} +#endif /* USE_FAAC */ + + + + +/* remove all pollfunctions and close socket */ +static void netreceive_tilde_closesocket(t_netreceive_tilde* x) +{ +#ifdef PD + sys_rmpollfn(x->x_socket); + outlet_float(x->x_outlet1, 0); +#else + clock_unset(x->x_datapoll); + outlet_int(x->x_outlet1, 0); +#endif + CLOSESOCKET(x->x_socket); + x->x_socket = -1; +} + + + +#ifdef PD +static void netreceive_tilde_reset(t_netreceive_tilde* x, t_floatarg buffer) +#else +static void netreceive_tilde_reset(t_netreceive_tilde* x, double buffer) +#endif +{ + int i; + x->x_counter = 0; + x->x_nbytes = 0; + x->x_framein = 0; + x->x_frameout = 0; + x->x_blockssincerecv = 0; + x->x_blocksperrecv = x->x_blocksize / x->x_vecsize; +#ifdef USE_FAAC + x->x_faac_bytes = 0; +#endif + + for (i = 0; i < DEFAULT_AVERAGE_NUMBER; i++) + x->x_average[i] = x->x_maxframes; + x->x_averagecur = 0; + + if (buffer == 0.0) /* set default */ + x->x_maxframes = DEFAULT_QUEUE_LENGTH; + else + { + buffer = (float)CLIP((float)buffer, 0., 1.); + x->x_maxframes = (int)(DEFAULT_AUDIO_BUFFER_FRAMES * buffer); + x->x_maxframes = CLIP(x->x_maxframes, 1, DEFAULT_AUDIO_BUFFER_FRAMES - 1); + post("netreceive~: set buffer to %g (%d frames)", buffer, x->x_maxframes); + } + x->x_underflow = 0; + x->x_overflow = 0; +} + + +static void netreceive_tilde_datapoll(t_netreceive_tilde *x) +{ +#ifndef PD + int ret; + struct timeval timout; + fd_set readset; + timout.tv_sec = 0; + timout.tv_usec = 0; + FD_ZERO(&readset); + FD_SET(x->x_socket, &readset); + + ret = select(x->x_socket + 1, &readset, NULL, NULL, &timout); + if (ret < 0) + { + netreceive_tilde_sockerror("select"); + return; + } + + if (FD_ISSET(x->x_socket, &readset)) /* data available */ +#endif + { + int ret; + int n; + + if (x->x_tcp) + { + n = x->x_nbytes; + + if (x->x_nbytes == 0) /* we ate all the samples and need a new header tag */ + { + /* get the new tag */ + ret = recv(x->x_socket, (char*)&x->x_frames[x->x_framein].tag, sizeof(t_tag), MSG_PEEK); + if (ret == 0) /* disconnect */ + { + post("netreceive~: EOF on socket %d", x->x_socket); + netreceive_tilde_closesocket(x); + x->x_socket = -1; + x->x_counter = 0; + return; + } + if (ret < 0) /* error */ + { + if (netreceive_tilde_sockerror("recv tag")) + goto bail; + netreceive_tilde_closesocket(x); + x->x_socket = -1; + x->x_counter = 0; + return; + } + else if (ret != sizeof(t_tag)) + { + /* incomplete header tag: return and try again later */ + /* in the hope that more data will be available */ + return; + } + + /* receive header tag */ + ret = recv(x->x_socket, (char*)&x->x_frames[x->x_framein].tag, sizeof(t_tag), 0); + + /* adjust byte order if neccessarry */ + if (x->x_frames[x->x_framein].tag.version != SF_BYTE_NATIVE) + { + x->x_frames[x->x_framein].tag.count = netsend_long(x->x_frames[x->x_framein].tag.count); + x->x_frames[x->x_framein].tag.framesize = netsend_long(x->x_frames[x->x_framein].tag.framesize); + } + + /* get info from header tag */ + if (x->x_frames[x->x_framein].tag.channels > x->x_noutlets) + { + error("netreceive~: incoming stream has too many channels (%d), kicking client", x->x_frames[x->x_framein].tag.channels); + netreceive_tilde_kick(x); + x->x_socket = -1; + x->x_counter = 0; + return; + } + x->x_nbytes = n = x->x_frames[x->x_framein].tag.framesize; + + /* check whether the data packet has the correct count */ + if ((x->x_framecount != x->x_frames[x->x_framein].tag.count) + && (x->x_frames[x->x_framein].tag.count > 2)) + { + error("netreceive~: we lost %d frames", (int)(x->x_frames[x->x_framein].tag.count - x->x_framecount)); + post("netreceive~: current package is %d, expected %d", x->x_frames[x->x_framein].tag.count, x->x_framecount); + } + x->x_framecount = x->x_frames[x->x_framein].tag.count + 1; + } + else /* we already have the header tag or some data and need more */ + { + ret = recv(x->x_socket, (char*)x->x_frames[x->x_framein].data + x->x_frames[x->x_framein].tag.framesize - n, n, 0); + if (ret > 0) + { + n -= ret; + } + else if (ret < 0) /* error */ + { + if (netreceive_tilde_sockerror("recv data")) + goto bail; + netreceive_tilde_closesocket(x); + x->x_socket = -1; + x->x_counter = 0; + return; + } + + x->x_nbytes = n; + if (n == 0) /* a complete packet is received */ + { +#ifdef USE_FAAC /* decode aac data if format is SF_AAC */ + if (x->x_frames[x->x_framein].tag.format == SF_AAC) + { + ret = netreceive_tilde_faac_decode(x, x->x_framein); + if (ret == -1) + { + netreceive_tilde_kick(x); + x->x_socket = -1; + x->x_counter = 0; + return; + } + else + { + /* update framesize */ + x->x_frames[x->x_framein].tag.framesize = ret; + } + } +#else + if (x->x_frames[x->x_framein].tag.format == SF_AAC) + { + error("netreceive~: don't know how to decode AAC format"); + netreceive_tilde_kick(x); + x->x_socket = -1; + x->x_counter = 0; + return; + } +#endif + + x->x_counter++; + x->x_framein++; + x->x_framein %= DEFAULT_AUDIO_BUFFER_FRAMES; + + /* check for buffer overflow */ + if (x->x_framein == x->x_frameout) + { + x->x_overflow++; + } + } + } + } + else /* UDP */ + { + n = x->x_nbytes; + + if (x->x_nbytes == 0) /* we ate all the samples and need a new header tag */ + { + /* receive header tag */ + ret = recv(x->x_socket, (char*)&x->x_frames[x->x_framein].tag, sizeof(t_tag), 0); + if (ret <= 0) /* error */ + { + if (netreceive_tilde_sockerror("recv tag")) + goto bail; + netreceive_tilde_reset(x, 0); + x->x_counter = 0; + return; + } + else if (ret != sizeof(t_tag)) + { + /* incomplete header tag: return and try again later */ + /* in the hope that more data will be available */ + error("netreceive~: got incomplete header tag"); + return; + } + /* adjust byte order if neccessarry */ + if (x->x_frames[x->x_framein].tag.version != SF_BYTE_NATIVE) + { + x->x_frames[x->x_framein].tag.count = netsend_long(x->x_frames[x->x_framein].tag.count); + x->x_frames[x->x_framein].tag.framesize = netsend_long(x->x_frames[x->x_framein].tag.framesize); + } + /* get info from header tag */ + if (x->x_frames[x->x_framein].tag.channels > x->x_noutlets) + { + error("netreceive~: incoming stream has too many channels (%d)", x->x_frames[x->x_framein].tag.channels); + x->x_counter = 0; + return; + } + x->x_nbytes = n = x->x_frames[x->x_framein].tag.framesize; + } + else /* we already have header tag or some data and need more */ + { + ret = recv(x->x_socket, (char*)x->x_frames[x->x_framein].data + x->x_frames[x->x_framein].tag.framesize - n, n, 0); + if (ret > 0) + { + n -= ret; + } + else if (ret < 0) /* error */ + { + if (netreceive_tilde_sockerror("recv data")) + goto bail; + netreceive_tilde_reset(x, 0); + x->x_counter = 0; + return; + } + + x->x_nbytes = n; + if (n == 0) /* a complete packet is received */ + { +#ifdef USE_FAAC /* decode aac data if format is SF_AAC and update framesize */ + if (x->x_frames[x->x_framein].tag.format == SF_AAC) + { + ret = netreceive_tilde_faac_decode(x, x->x_framein); + if (ret == -1) + { + return; + } + else + { + /* update framesize */ + x->x_frames[x->x_framein].tag.framesize = ret; + } + } +#else + if (x->x_frames[x->x_framein].tag.format == SF_AAC) + { + error("netreceive~: don't know how to decode AAC format"); + return; + } +#endif + x->x_counter++; + x->x_framein++; + x->x_framein %= DEFAULT_AUDIO_BUFFER_FRAMES; + + /* check for buffer overflow */ + if (x->x_framein == x->x_frameout) + { + x->x_overflow++; + } + } + } + } + } +bail: + ; +#ifndef PD + clock_delay(x->x_datapoll, DEFAULT_NETWORK_POLLTIME); +#endif +} + + +static void netreceive_tilde_connectpoll(t_netreceive_tilde *x) +{ +#ifndef PD + int ret; + struct timeval timout; + fd_set readset; + timout.tv_sec = 0; + timout.tv_usec = 0; + FD_ZERO(&readset); + FD_SET(x->x_connectsocket, &readset); + + ret = select(x->x_connectsocket + 1, &readset, NULL, NULL, &timout); + if (ret < 0) + { + netreceive_tilde_sockerror("select"); + return; + } + + if (FD_ISSET(x->x_connectsocket, &readset)) /* pending connection */ +#endif + { + int sockaddrl = (int)sizeof(struct sockaddr); + struct sockaddr_in incomer_address; + int fd = accept(x->x_connectsocket, (struct sockaddr*)&incomer_address, &sockaddrl); + if (fd < 0) + { + post("netreceive~: accept failed"); + return; + } +#ifdef O_NONBLOCK + fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + if (x->x_socket != -1) + { + post("netreceive~: new connection"); + netreceive_tilde_closesocket(x); + } + + netreceive_tilde_reset(x, 0); + x->x_socket = fd; + x->x_nbytes = 0; + x->x_hostname = gensym(inet_ntoa(incomer_address.sin_addr)); +#ifdef PD + sys_addpollfn(fd, netreceive_tilde_datapoll, x); + outlet_float(x->x_outlet1, 1); +#else + clock_delay(x->x_datapoll, 0); + outlet_int(x->x_outlet1, 1); +#endif + } +#ifndef PD + clock_delay(x->x_connectpoll, DEFAULT_NETWORK_POLLTIME); +#endif +} + + +static int netreceive_tilde_createsocket(t_netreceive_tilde* x, int portno) +{ + struct sockaddr_in server; + int sockfd; + int tcp = x->x_tcp; + + /* create a socket */ + if (!tcp) + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + else + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (sockfd < 0) + { + netreceive_tilde_sockerror("socket"); + return 0; + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* assign server port number */ + + server.sin_port = htons((u_short)portno); + post("listening to port number %d", portno); + + netreceive_tilde_setsocketoptions(sockfd); + + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + netreceive_tilde_sockerror("bind"); + CLOSESOCKET(sockfd); + return 0; + } + + + if (!tcp) + { + x->x_socket = sockfd; + x->x_nbytes = 0; +#ifdef PD + sys_addpollfn(sockfd, netreceive_tilde_datapoll, x); +#else + clock_delay(x->x_datapoll, 0); +#endif + } + else + { + if (listen(sockfd, 5) < 0) + { + netreceive_tilde_sockerror("listen"); + CLOSESOCKET(sockfd); + return 0; + } + else + { + x->x_connectsocket = sockfd; + /* start polling for connection requests */ +#ifdef PD + sys_addpollfn(sockfd, netreceive_tilde_connectpoll, x); +#else + clock_delay(x->x_connectpoll, 0); +#endif + } + } + return 1; +} + + + +/* kick connected client */ +static void netreceive_tilde_kick(t_netreceive_tilde *x) +{ + if (x->x_tcp) + { + if (x->x_socket != -1) + { + shutdown(x->x_socket, 1); + netreceive_tilde_closesocket(x); + post("netreceive~: kicked client!"); + } + else error("netreceive~: no client to kick"); + } + else error("netreceive~: kicking clients in UDP mode not possible"); +} + + +#define QUEUESIZE (int)((x->x_framein + DEFAULT_AUDIO_BUFFER_FRAMES - x->x_frameout) % DEFAULT_AUDIO_BUFFER_FRAMES) +#define BLOCKOFFSET (x->x_blockssincerecv * x->x_vecsize * x->x_frames[x->x_frameout].tag.channels) + +static t_int *netreceive_tilde_perform(t_int *w) +{ + t_netreceive_tilde *x = (t_netreceive_tilde*) (w[1]); + int n = (int)(w[2]); + t_float *out[DEFAULT_AUDIO_CHANNELS]; + const int offset = 3; + const int channels = x->x_frames[x->x_frameout].tag.channels; + int i = 0; + + for (i = 0; i < x->x_noutlets; i++) + { + out[i] = (t_float *)(w[offset + i]); + } + + if (n != x->x_vecsize) + { + x->x_vecsize = n; + x->x_blocksperrecv = x->x_blocksize / x->x_vecsize; + x->x_blockssincerecv = 0; + } + + /* check whether there is enough data in buffer */ + if (x->x_counter < x->x_maxframes) + { + goto bail; + } + + /* check for buffer underflow */ + if (x->x_framein == x->x_frameout) + { + x->x_underflow++; + goto bail; + } + + + /* queue balancing */ + x->x_average[x->x_averagecur] = QUEUESIZE; + if (++x->x_averagecur >= DEFAULT_AVERAGE_NUMBER) + x->x_averagecur = 0; + + switch (x->x_frames[x->x_frameout].tag.format) + { + case SF_FLOAT: + { + t_float* buf = (t_float *)x->x_frames[x->x_frameout].data + BLOCKOFFSET; + + if (x->x_frames[x->x_frameout].tag.version == SF_BYTE_NATIVE) + { + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = *buf++; + } + for (i = channels; i < x->x_noutlets; i++) + { + *out[i]++ = 0.; + } + } + } + else /* swap bytes */ + { + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = netsend_float(*buf++); + } + for (i = channels; i < x->x_noutlets; i++) + { + *out[i]++ = 0.; + } + } + } + break; + } + case SF_16BIT: + { + short* buf = (short *)x->x_frames[x->x_frameout].data + BLOCKOFFSET; + + if (x->x_frames[x->x_frameout].tag.version == SF_BYTE_NATIVE) + { + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = (t_float)(*buf++ * 3.051850e-05); + } + for (i = channels; i < x->x_noutlets; i++) + { + *out[i]++ = 0.; + } + } + } + else /* swap bytes */ + { + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = (t_float)(netsend_short(*buf++) * 3.051850e-05); + } + for (i = channels; i < x->x_noutlets; i++) + { + *out[i]++ = 0.; + } + } + } + break; + } + case SF_8BIT: + { + unsigned char* buf = (char *)x->x_frames[x->x_frameout].data + BLOCKOFFSET; + + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = (t_float)((0.0078125 * (*buf++)) - 1.0); + } + for (i = channels; i < x->x_noutlets; i++) + { + *out[i]++ = 0.; + } + } + break; + } + case SF_MP3: + { + post("netreceive~: mp3 format not supported"); + if (x->x_tcp) + netreceive_tilde_kick(x); + break; + } + case SF_AAC: + { +#ifdef USE_FAAC + t_float* buf = (t_float *)x->x_frames[x->x_frameout].data + BLOCKOFFSET; + + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = (t_float)(*buf++); + } + for (i = channels; i < x->x_noutlets; i++) + { + *out[i]++ = 0.; + } + } + break; +#else + post("netreceive~: aac format not supported"); + if (x->x_tcp) + netreceive_tilde_kick(x); +#endif + break; + } + default: + post("netreceive~: unknown format (%d)",x->x_frames[x->x_frameout].tag.format); + if (x->x_tcp) + netreceive_tilde_kick(x); + break; + } + + if (!(x->x_blockssincerecv < x->x_blocksperrecv - 1)) + { + x->x_blockssincerecv = 0; + x->x_frameout++; + x->x_frameout %= DEFAULT_AUDIO_BUFFER_FRAMES; + } + else + { + x->x_blockssincerecv++; + } + + return (w + offset + x->x_noutlets); + +bail: + /* set output to zero */ + while (n--) + { + for (i = 0; i < x->x_noutlets; i++) + { + *(out[i]++) = 0.; + } + } + return (w + offset + x->x_noutlets); +} + + + +static void netreceive_tilde_dsp(t_netreceive_tilde *x, t_signal **sp) +{ + int i; + + x->x_myvec[0] = (t_int*)x; + x->x_myvec[1] = (t_int*)sp[0]->s_n; + + x->x_samplerate = (long)sp[0]->s_sr; + + if (DEFAULT_AUDIO_BUFFER_SIZE % sp[0]->s_n) + { + error("netsend~: signal vector size too large (needs to be even divisor of %d)", DEFAULT_AUDIO_BUFFER_SIZE); + } + else + { +#ifdef PD + for (i = 0; i < x->x_noutlets; i++) + { + x->x_myvec[2 + i] = (t_int*)sp[i + 1]->s_vec; + } + dsp_addv(netreceive_tilde_perform, x->x_noutlets + 2, (t_int*)x->x_myvec); +#else + for (i = 0; i < x->x_noutlets; i++) + { + x->x_myvec[2 + i] = (t_int*)sp[i]->s_vec; + } + dsp_addv(netreceive_tilde_perform, x->x_noutlets + 2, (void **)x->x_myvec); +#endif /* PD */ + } +} + + +/* send stream info when banged */ +static void netreceive_tilde_bang(t_netreceive_tilde *x) +{ + t_atom list[2]; + t_symbol *sf_format; + t_float bitrate; + int i, avg = 0; + for (i = 0; i < DEFAULT_AVERAGE_NUMBER; i++) + avg += x->x_average[i]; + + bitrate = (t_float)((SF_SIZEOF(x->x_frames[x->x_frameout].tag.format) * x->x_samplerate * 8 * x->x_frames[x->x_frameout].tag.channels) / 1000.); + + switch (x->x_frames[x->x_frameout].tag.format) + { + case SF_FLOAT: + { + sf_format = ps_sf_float; + break; + } + case SF_16BIT: + { + sf_format = ps_sf_16bit; + break; + } + case SF_8BIT: + { + sf_format = ps_sf_8bit; + break; + } + case SF_MP3: + { + sf_format = ps_sf_mp3; + break; + } + case SF_AAC: + { + sf_format = ps_sf_aac; + break; + } + default: + { + sf_format = ps_sf_unknown; + break; + } + } + +#ifdef PD + /* --- stream information (t_tag) --- */ + /* audio format */ + SETSYMBOL(list, (t_symbol *)sf_format); + outlet_anything(x->x_outlet2, ps_format, 1, list); + + /* channels */ + SETFLOAT(list, (t_float)x->x_frames[x->x_frameout].tag.channels); + outlet_anything(x->x_outlet2, ps_channels, 1, list); + + /* framesize */ + SETFLOAT(list, (t_float)x->x_frames[x->x_frameout].tag.framesize); + outlet_anything(x->x_outlet2, ps_framesize, 1, list); + + /* bitrate */ + SETFLOAT(list, (t_float)bitrate); + outlet_anything(x->x_outlet2, ps_bitrate, 1, list); + + /* --- internal info (buffer and network) --- */ + /* overflow */ + SETFLOAT(list, (t_float)x->x_overflow); + outlet_anything(x->x_outlet2, ps_overflow, 1, list); + + /* underflow */ + SETFLOAT(list, (t_float)x->x_underflow); + outlet_anything(x->x_outlet2, ps_underflow, 1, list); + + /* queuesize */ + SETFLOAT(list, (t_float)QUEUESIZE); + outlet_anything(x->x_outlet2, ps_queuesize, 1, list); + + /* average queuesize */ + SETFLOAT(list, (t_float)((t_float)avg / (t_float)DEFAULT_AVERAGE_NUMBER)); + outlet_anything(x->x_outlet2, ps_average, 1, list); + + if (x->x_tcp) + { + /* IP address */ + SETSYMBOL(list, (t_symbol *)x->x_hostname); + outlet_anything(x->x_outlet2, ps_hostname, 1, list); + } +#else + /* --- stream information (t_tag) --- */ + /* audio format */ + SETSYM(list, ps_format); + SETSYM(list + 1, (t_symbol *)sf_format); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* channels */ + SETSYM(list, ps_channels); + SETLONG(list + 1, (int)x->x_frames[x->x_frameout].tag.channels); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* framesize */ + SETSYM(list, ps_framesize); + SETLONG(list + 1, (int)x->x_frames[x->x_frameout].tag.framesize); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* bitrate */ + SETSYM(list, ps_bitrate); + SETFLOAT(list + 1, (t_float)bitrate); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* --- internal info (buffer and network) --- */ + /* overflow */ + SETSYM(list, ps_overflow); + SETLONG(list + 1, (int)x->x_overflow); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* underflow */ + SETSYM(list, ps_underflow); + SETLONG(list + 1, (int)x->x_underflow); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* queuesize */ + SETSYM(list, ps_queuesize); + SETLONG(list + 1, (int)QUEUESIZE); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* average queuesize */ + SETSYM(list, ps_average); + SETFLOAT(list + 1, (t_float)((t_float)avg / (t_float)DEFAULT_AVERAGE_NUMBER)); + outlet_list(x->x_outlet2, NULL, 2, list); + + if (x->x_tcp) + { + /* IP address */ + SETSYM(list, (t_symbol *)ps_hostname); + SETSYM(list + 1, x->x_hostname); + outlet_list(x->x_outlet2, NULL, 2, list); + } +#endif +} + + + +static void netreceive_tilde_print(t_netreceive_tilde* x) +{ + int i, avg = 0; + for (i = 0; i < DEFAULT_AVERAGE_NUMBER; i++) + avg += x->x_average[i]; + post("netreceive~: last size = %d, avg size = %g, %d underflows, %d overflows", QUEUESIZE, (float)((float)avg / (float)DEFAULT_AVERAGE_NUMBER), x->x_underflow, x->x_overflow); + post("netreceive~: channels = %d, framesize = %d, packets = %d", x->x_frames[x->x_framein].tag.channels, x->x_frames[x->x_framein].tag.framesize, x->x_counter); +} + + + +#ifdef PD +static void *netreceive_tilde_new(t_floatarg fportno, t_floatarg outlets, t_floatarg prot) +#else +static void *netreceive_tilde_new(long fportno, long outlets, long prot) +#endif +{ + t_netreceive_tilde *x; + int i; + + if (fportno == 0) fportno = DEFAULT_PORT; + +#ifdef PD + x = (t_netreceive_tilde *)pd_new(netreceive_tilde_class); + if (x) + { + for (i = sizeof(t_object); i < (int)sizeof(t_netreceive_tilde); i++) + ((char *)x)[i] = 0; + } + + x->x_noutlets = CLIP((int)outlets, 1, DEFAULT_AUDIO_CHANNELS); + for (i = 0; i < x->x_noutlets; i++) + outlet_new(&x->x_obj, &s_signal); + if (!prot) + x->x_outlet1 = outlet_new(&x->x_obj, &s_anything); /* outlet for connection state (TCP/IP) */ + x->x_outlet2 = outlet_new(&x->x_obj, &s_anything); +#else + x = (t_netreceive_tilde *)newobject(netreceive_tilde_class); + if (x) + { + for (i = sizeof(t_pxobject); i < (int)sizeof(t_netreceive_tilde); i++) + ((char *)x)[i] = 0; + } + + dsp_setup((t_pxobject *)x, 0); /* no signal inlets */ + x->x_noutlets = CLIP((int)outlets, 1, DEFAULT_AUDIO_CHANNELS); + x->x_outlet2 = listout(x); /* outlet for info list */ + if (!prot) + x->x_outlet1 = listout(x); /* outlet for connection state (TCP/IP) */ + for (i = 0 ; i < x->x_noutlets; i++) + outlet_new(x, "signal"); + x->x_connectpoll = clock_new(x, (method)netreceive_tilde_connectpoll); + x->x_datapoll = clock_new(x, (method)netreceive_tilde_datapoll); +#endif + + x->x_myvec = (t_int **)t_getbytes(sizeof(t_int *) * (x->x_noutlets + 3)); + if (!x->x_myvec) + { + error("netreceive~: out of memory"); + return NULL; + } + +#ifdef USE_FAAC + x->x_faac_decoder = NULL; + x->x_faac_init = 0; +#endif + + x->x_connectsocket = -1; + x->x_socket = -1; + x->x_tcp = 1; + x->x_nconnections = 0; + x->x_ndrops = 0; + x->x_underflow = 0; + x->x_overflow = 0; + x->x_hostname = ps_nothing; + + for (i = 0; i < DEFAULT_AUDIO_BUFFER_FRAMES; i++) + { + x->x_frames[i].data = (char *)t_getbytes(DEFAULT_AUDIO_BUFFER_SIZE * x->x_noutlets * sizeof(t_float)); + } + x->x_framein = 0; + x->x_frameout = 0; + x->x_maxframes = DEFAULT_QUEUE_LENGTH; + x->x_vecsize = 64; /* we'll update this later */ + x->x_blocksize = DEFAULT_AUDIO_BUFFER_SIZE; /* LATER make this dynamic */ + x->x_blockssincerecv = 0; + x->x_blocksperrecv = x->x_blocksize / x->x_vecsize; + + if (prot) + x->x_tcp = 0; + + if (!netreceive_tilde_createsocket(x, (int)fportno)) + { + error("netreceive~: failed to create listening socket"); + return (NULL); + } + + return (x); +} + + + +static void netreceive_tilde_free(t_netreceive_tilde *x) +{ + int i; + + if (x->x_connectsocket != -1) + { +#ifdef PD + sys_rmpollfn(x->x_connectsocket); +#else + clock_unset(x->x_connectpoll); +#endif + CLOSESOCKET(x->x_connectsocket); + } + if (x->x_socket != -1) + { +#ifdef PD + sys_rmpollfn(x->x_socket); +#else + clock_unset(x->x_datapoll); +#endif + CLOSESOCKET(x->x_socket); + } + +#ifndef PD + dsp_free((t_pxobject *)x); /* free the object */ + clock_free(x->x_connectpoll); + clock_free(x->x_datapoll); +#endif + +#ifdef USE_FAAC + netreceive_tilde_faac_close(x); +#endif + + /* free memory */ + t_freebytes(x->x_myvec, sizeof(t_int *) * (x->x_noutlets + 3)); + for (i = 0; i < DEFAULT_AUDIO_BUFFER_FRAMES; i++) + { + t_freebytes(x->x_frames[i].data, DEFAULT_AUDIO_BUFFER_SIZE * x->x_noutlets * sizeof(t_float)); + } +} + + + +#ifdef PD +void netreceive_tilde_setup(void) +{ + netreceive_tilde_class = class_new(gensym("netreceive~"), + (t_newmethod) netreceive_tilde_new, (t_method) netreceive_tilde_free, + sizeof(t_netreceive_tilde), 0, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, A_NULL); + + class_addmethod(netreceive_tilde_class, nullfn, gensym("signal"), 0); + class_addbang(netreceive_tilde_class, (t_method)netreceive_tilde_bang); + class_addmethod(netreceive_tilde_class, (t_method)netreceive_tilde_dsp, gensym("dsp"), 0); + class_addmethod(netreceive_tilde_class, (t_method)netreceive_tilde_print, gensym("print"), 0); + class_addmethod(netreceive_tilde_class, (t_method)netreceive_tilde_kick, gensym("kick"), 0); + class_addmethod(netreceive_tilde_class, (t_method)netreceive_tilde_reset, gensym("reset"), A_DEFFLOAT, 0); + class_addmethod(netreceive_tilde_class, (t_method)netreceive_tilde_reset, gensym("buffer"), A_DEFFLOAT, 0); + class_sethelpsymbol(netreceive_tilde_class, gensym("netsend~")); + post("netreceive~ v%s, (c) 2004 Olaf Matthes", VERSION); + + ps_format = gensym("format"); + ps_channels = gensym("channels"); + ps_framesize = gensym("framesize"); + ps_bitrate = gensym("bitrate"); + ps_overflow = gensym("overflow"); + ps_underflow = gensym("underflow"); + ps_queuesize = gensym("queuesize"); + ps_average = gensym("average"); + ps_hostname = gensym("ipaddr"); + ps_sf_float = gensym("_float_"); + ps_sf_16bit = gensym("_16bit_"); + ps_sf_8bit = gensym("_8bit_"); + ps_sf_mp3 = gensym("_mp3_"); + ps_sf_aac = gensym("_aac_"); + ps_sf_unknown = gensym("_unknown_"); + ps_nothing = gensym(""); +} + +#else + +void netreceive_tilde_assist(t_netreceive_tilde *x, void *b, long m, long a, char *s) +{ + switch (m) + { + case 1: /* inlet */ + sprintf(s, "(Anything) Control Messages"); + break; + case 2: /* outlets */ + sprintf(s, "(Signal) Audio Channel %d", (int)(a + 1)); + break; + break; + } + +} + +void main() +{ +#ifdef _WINDOWS + short version = MAKEWORD(2, 0); + WSADATA nobby; +#endif /* _WINDOWS */ + + setup((t_messlist **)&netreceive_tilde_class, (method)netreceive_tilde_new, (method)netreceive_tilde_free, + (short)sizeof(t_netreceive_tilde), 0L, A_DEFLONG, A_DEFLONG, A_DEFLONG, 0); + addmess((method)netreceive_tilde_dsp, "dsp", A_CANT, 0); + addmess((method)netreceive_tilde_assist, "assist", A_CANT, 0); + addmess((method)netreceive_tilde_print, "print", 0); + addmess((method)netreceive_tilde_kick, "kick", 0); + addmess((method)netreceive_tilde_reset, "reset", A_DEFFLOAT, 0); + addmess((method)netreceive_tilde_reset, "buffer", A_DEFFLOAT, 0); + addbang((method)netreceive_tilde_bang); + dsp_initclass(); + finder_addclass("System", "netreceive~"); + post("netreceive~ v%s, © 2004 Olaf Matthes", VERSION); + + ps_format = gensym("format"); + ps_channels = gensym("channels"); + ps_framesize = gensym("framesize"); + ps_bitrate = gensym("bitrate"); + ps_overflow = gensym("overflow"); + ps_underflow = gensym("underflow"); + ps_queuesize = gensym("queuesize"); + ps_average = gensym("average"); + ps_hostname = gensym("ipaddr"); + ps_sf_float = gensym("_float_"); + ps_sf_16bit = gensym("_16bit_"); + ps_sf_8bit = gensym("_8bit_"); + ps_sf_mp3 = gensym("_mp3_"); + ps_sf_aac = gensym("_aac_"); + ps_sf_unknown = gensym("_unknown_"); + ps_nothing = gensym(""); + +#ifdef _WINDOWS + if (WSAStartup(version, &nobby)) error("netreceive~: WSAstartup failed"); +#endif /* _WINDOWS */ +} +#endif /* PD */ diff --git a/l2ork_addons/raspberry_pi/netreceive_tilde/netsend~.h b/l2ork_addons/raspberry_pi/netreceive_tilde/netsend~.h new file mode 100644 index 0000000000000000000000000000000000000000..874b63f25b48ea82ee4e7e6d38d9ff67db4e4425 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netreceive_tilde/netsend~.h @@ -0,0 +1,154 @@ +/* ------------------------ netsend~ ------------------------------------------ */ +/* */ +/* Tilde object to send uncompressed audio data to netreceive~. */ +/* Written by Olaf Matthes <olaf.matthes@gmx.de>. */ +/* Based on streamout~ by Guenter Geiger. */ +/* Get source at http://www.akustische-kunst.org/ */ +/* */ +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ +/* */ +/* See file LICENSE for further informations on licensing terms. */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* This project was commissioned by the Society for Arts and Technology [SAT], */ +/* Montreal, Quebec, Canada, http://www.sat.qc.ca/. */ +/* */ +/* ---------------------------------------------------------------------------- */ + + +/* This file is based on and inspired by stream.h (C) Guenter Geiger 1999. */ +/* Some enhancements have been made with the goal of keeping compatibility */ +/* between the stream formats of streamout~/in~ and netsend~/receive~. */ + +#define VERSION "1.0alpha11" + +#define DEFAULT_AUDIO_CHANNELS 32 /* nax. number of audio channels we support */ +#define DEFAULT_AUDIO_BUFFER_SIZE 1024 /* number of samples in one audio block */ +#define DEFAULT_UDP_PACKT_SIZE 8192 /* number of bytes we send in one UDP datagram (OS X only) */ +#define DEFAULT_PORT 8000 /* default network port number */ + +#ifdef _WINDOWS +#ifndef HAVE_INT32_T +typedef int int32_t; +#define HAVE_INT32_T +#endif +#ifndef HAVE_INT16_T +typedef short int16_t; +#define HAVE_INT16_T +#endif +#ifndef HAVE_U_INT32_T +typedef unsigned int u_int32_t; +#define HAVE_U_INT32_T +#endif +#ifndef HAVE_U_INT16_T +typedef unsigned short u_int16_t; +#define HAVE_U_INT16_T +#endif +#endif + +#ifndef CLIP +#define CLIP(a, lo, hi) ( (a)>(lo)?( (a)<(hi)?(a):(hi) ):(lo) ) +#endif + + +/* swap 32bit t_float. Is there a better way to do that???? */ +#ifdef _WINDOWS +__inline static float netsend_float(float f) +#else +inline static float netsend_float(float f) +#endif +{ + union + { + float f; + unsigned char b[4]; + } dat1, dat2; + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +/* swap 32bit long int */ +#ifdef _WINDOWS +__inline static long netsend_long(long n) +#else +inline static long netsend_long(long n) +#endif +{ + return (((n & 0xff) << 24) | ((n & 0xff00) << 8) | + ((n & 0xff0000) >> 8) | ((n & 0xff000000) >> 24)); +} + +/* swap 16bit short int */ +#ifdef _WINDOWS +__inline static long netsend_short(long n) +#else +inline static short netsend_short(short n) +#endif +{ + return (((n & 0xff) << 8) | ((n & 0xff00) >> 8)); +} + + +/* format specific stuff */ + +#define SF_FLOAT 1 +#define SF_DOUBLE 2 /* not implemented */ +#define SF_8BIT 10 +#define SF_16BIT 11 +#define SF_32BIT 12 /* not implemented */ +#define SF_ALAW 20 /* not implemented */ +#define SF_MP3 30 /* not implemented */ +#define SF_AAC 31 /* AAC encoding using FAAC */ +#define SF_VORBIS 40 /* not implemented */ +#define SF_FLAC 50 /* not implemented */ + +#define SF_SIZEOF(a) (a == SF_FLOAT ? sizeof(t_float) : \ + a == SF_16BIT ? sizeof(short) : 1) + + +/* version / byte-endian specific stuff */ + +#define SF_BYTE_LE 1 /* little endian */ +#define SF_BYTE_BE 2 /* big endian */ + +#if defined(_WINDOWS) || defined(__linux__) || defined(IRIX) +#define SF_BYTE_NATIVE SF_BYTE_LE +#else /* must be __APPLE__ */ +#define SF_BYTE_NATIVE SF_BYTE_BE +#endif + + +typedef struct _tag { /* size (bytes) */ + char version; /* 1 */ + char format; /* 1 */ + long count; /* 4 */ + char channels; /* 1 */ + long framesize; /* 4 */ + char extension[5]; /* 5 */ +} t_tag; /*--------------*/ + /* 16 */ + + +typedef struct _frame { + t_tag tag; + char *data; +} t_frame; + diff --git a/l2ork_addons/raspberry_pi/netreceive_tilde/test-netsend~.pd b/l2ork_addons/raspberry_pi/netreceive_tilde/test-netsend~.pd new file mode 100644 index 0000000000000000000000000000000000000000..d3d9cbf5f99a069fe7bc787d5560a95ef58c8131 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netreceive_tilde/test-netsend~.pd @@ -0,0 +1,72 @@ +#N canvas 291 209 642 596 10; +#X obj 18 178 netreceive~ 8008 4; +#X obj 19 514 netsend~ 4; +#X obj 18 221 dac~ 1; +#X obj 68 221 dac~ 2; +#X obj 121 221 dac~ 3; +#X obj 175 222 dac~ 4; +#X obj 74 480 osc~ 440; +#X obj 145 481 osc~ 880; +#X obj 214 480 osc~ 990; +#X obj 281 481 osc~ 220; +#X text 245 515 sends 4 dsp-channels to localhost:8008; +#X msg 62 325 disconnect; +#X msg 76 350 format float; +#X msg 86 374 format 16bit; +#X msg 102 398 format 8bit; +#X text 189 358 format defines the resolution of the sent signal; +#X text 347 480 the signals to send; +#X floatatom 19 558 5 0 0 0 - - -; +#X text 63 559 status: 1 = connected 0 = disconnected; +#X msg 19 273 connect localhost 8008; +#X text 15 13 netreceive~/netsend~; +#X text 15 31 written by Olaf Matthes <olaf.matthes@gmx.de>; +#X text 161 181 receives 4 channels on port 8808; +#X text 186 274 connect to <hostname> <port>; +#X text 203 421 change number of channels; +#X text 200 376 float is the most expensive with the best resolution +(32bit) \, default is 16bit; +#X msg 109 422 channels 2; +#X msg 38 298 connect 255.255.255.255 8008; +#X text 14 50 commissioned by the Society for Arts and Technology [SAT] +\, Montreal \, Quebec \, Canada \, http://www.sat.qc.at/; +#X obj 269 222 print; +#X obj 18 96 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X msg 46 97 kick; +#X msg 51 122 buffer 0.25; +#X msg 62 149 print; +#X obj 87 539 print; +#X floatatom 224 224 5 0 0 0 - - -; +#X text 220 298 broadcast to everybody on your local subnet listening +on the specified port (in UDP mode only!); +#X obj 112 448 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X text 137 448 get info; +#X text 226 252 connect to <hostname> <port>; +#X msg 23 250 connect 192.168.1.10 8008; +#X connect 0 0 2 0; +#X connect 0 1 3 0; +#X connect 0 2 4 0; +#X connect 0 3 5 0; +#X connect 0 4 35 0; +#X connect 0 5 29 0; +#X connect 1 0 17 0; +#X connect 1 1 34 0; +#X connect 6 0 1 0; +#X connect 7 0 1 1; +#X connect 8 0 1 2; +#X connect 9 0 1 3; +#X connect 11 0 1 0; +#X connect 12 0 1 0; +#X connect 13 0 1 0; +#X connect 14 0 1 0; +#X connect 19 0 1 0; +#X connect 26 0 1 0; +#X connect 27 0 1 0; +#X connect 30 0 0 0; +#X connect 31 0 0 0; +#X connect 32 0 0 0; +#X connect 33 0 0 0; +#X connect 37 0 1 0; +#X connect 40 0 1 0; diff --git a/l2ork_addons/raspberry_pi/netsend_tilde/float_cast.h b/l2ork_addons/raspberry_pi/netsend_tilde/float_cast.h new file mode 100644 index 0000000000000000000000000000000000000000..0ebc4bb448ef9d5108687c015aa9063cce6eff5c --- /dev/null +++ b/l2ork_addons/raspberry_pi/netsend_tilde/float_cast.h @@ -0,0 +1,203 @@ +/* +** Copyright (C) 2001-2003 Erik de Castro Lopo <erikd@mega-nerd.com> +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** 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, write to the Free Software +** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* Version 1.3 */ + + +/*============================================================================ +** On Intel Pentium processors (especially PIII and probably P4), converting +** from float to int is very slow. To meet the C specs, the code produced by +** most C compilers targeting Pentium needs to change the FPU rounding mode +** before the float to int conversion is performed. +** +** Changing the FPU rounding mode causes the FPU pipeline to be flushed. It +** is this flushing of the pipeline which is so slow. +** +** Fortunately the ISO C99 specifications define the functions lrint, lrintf, +** llrint and llrintf which fix this problem as a side effect. +** +** On Unix-like systems, the configure process should have detected the +** presence of these functions. If they weren't found we have to replace them +** here with a standard C cast. +*/ + +/* +** The C99 prototypes for lrint and lrintf are as follows: +** +** long int lrintf (float x) ; +** long int lrint (double x) ; +*/ + +// #include "config.h" + +/* +** The presence of the required functions are detected during the configure +** process and the values HAVE_LRINT and HAVE_LRINTF are set accordingly in +** the config.h file. +*/ + +#define HAVE_LRINT_REPLACEMENT 0 + +#if (HAVE_LRINT && HAVE_LRINTF) + + /* + ** These defines enable functionality introduced with the 1999 ISO C + ** standard. They must be defined before the inclusion of math.h to + ** engage them. If optimisation is enabled, these functions will be + ** inlined. With optimisation switched off, you have to link in the + ** maths library using -lm. + */ + + #define _ISOC9X_SOURCE 1 + #define _ISOC99_SOURCE 1 + + #define __USE_ISOC9X 1 + #define __USE_ISOC99 1 + + #include <math.h> + +#elif (defined (WIN32) || defined (_WIN32)) + + #undef HAVE_LRINT_REPLACEMENT + #define HAVE_LRINT_REPLACEMENT 1 + #include <math.h> + + /* + ** Win32 doesn't seem to have these functions. + ** Therefore implement inline versions of these functions here. + */ + + __inline long int + lrint (double flt) + { int intgr; + + _asm + { fld flt + fistp intgr + } ; + + return intgr ; + } + + __inline long int + lrintf (float flt) + { int intgr; + + _asm + { fld flt + fistp intgr + } ; + + return intgr ; + } + +#elif (defined (__MWERKS__) && defined (macintosh)) + + /* This MacOS 9 solution was provided by Stephane Letz */ + + #undef HAVE_LRINT_REPLACEMENT + #define HAVE_LRINT_REPLACEMENT 1 + #include <math.h> + + #undef lrint + #undef lrintf + + #define lrint double2int + #define lrintf float2int + + inline int + float2int (register float in) + { long res [2] ; + + asm + { fctiw in,in + stfd in,res + } + return res [1] ; + } /* float2int */ + + inline int + double2int (register double in) + { long res [2] ; + + asm + { fctiw in,in + stfd in,res + } + return res [1] ; + } /* double2int */ + +#elif (defined (__MACH__) && defined (__APPLE__)) + + /* For Apple MacOSX. */ + + #undef HAVE_LRINT_REPLACEMENT + #define HAVE_LRINT_REPLACEMENT 1 + #include <math.h> + + #undef lrint + #undef lrintf + + #define lrint double2int + #define lrintf float2int + + inline static long int + float2int (register float in) + { int res [2] ; + + __asm__ __volatile__ + ( "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (in) /* Input */ + : "memory" + ) ; + + return res [1] ; + } /* lrintf */ + + inline static long int + double2int (register double in) + { int res [2] ; + + __asm__ __volatile__ + ( "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (in) /* Input */ + : "memory" + ) ; + + return res [1] ; + } /* lrint */ + +#else + #ifndef __sgi + #warning "Don't have the functions lrint() and lrintf()." + #warning "Replacing these functions with a standard C cast." + #endif + + #include <math.h> + + #define lrint(dbl) ((int) (dbl)) + #define lrintf(flt) ((int) (flt)) + +#endif + + + diff --git a/l2ork_addons/raspberry_pi/netsend_tilde/makefile b/l2ork_addons/raspberry_pi/netsend_tilde/makefile new file mode 100644 index 0000000000000000000000000000000000000000..c13e789281d0459e0acb02c0c97d8d867c84b8d0 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netsend_tilde/makefile @@ -0,0 +1,4 @@ +NAME=netsend~ +CSYM=netsend_tilde + +include ../makefile.include diff --git a/l2ork_addons/raspberry_pi/netsend_tilde/netsend~.c b/l2ork_addons/raspberry_pi/netsend_tilde/netsend~.c new file mode 100644 index 0000000000000000000000000000000000000000..7bee7c3e5947dc06dda2ac7f9d2cef01fd370ed7 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netsend_tilde/netsend~.c @@ -0,0 +1,1056 @@ +/* ------------------------ netsend~ ------------------------------------------ */ +/* */ +/* Tilde object to send uncompressed audio data to netreceive~. */ +/* Written by Olaf Matthes <olaf.matthes@gmx.de>. */ +/* Based on streamout~ by Guenter Geiger. */ +/* Get source at http://www.akustische-kunst.org/ */ +/* */ +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ +/* */ +/* See file LICENSE for further informations on licensing terms. */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* This project was commissioned by the Society for Arts and Technology [SAT], */ +/* Montreal, Quebec, Canada, http://www.sat.qc.ca/. */ +/* */ +/* ---------------------------------------------------------------------------- */ + +#ifdef PD +#include "m_pd.h" +#else +#include "ext.h" +#include "z_dsp.h" +#endif + +#include "netsend~.h" +#include "float_cast.h" /* tools for fast conversion from float to int */ + +#ifdef USE_FAAC +#include "faac/faac.h" +#endif + +#include <sys/types.h> +#include <string.h> +#include <stdlib.h> +#if defined(__linux__) || defined(__apple__) +#include <sys/socket.h> +#include <errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/time.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <pthread.h> +#define SOCKET_ERROR -1 +#endif +#ifdef _WINDOWS +#include <winsock.h> +#include "pthread.h" +#endif + +#ifdef _WINDOWS +#pragma warning( disable : 4244 ) +#pragma warning( disable : 4305 ) +#endif + +#ifdef MSG_NOSIGNAL +#define SEND_FLAGS /*MSG_DONTWAIT|*/MSG_NOSIGNAL +#else +#define SEND_FLAGS 0 +#endif + +#ifndef SOL_IP +#define SOL_IP IPPROTO_IP +#endif + + +/* Utility functions */ + +static int netsend_tilde_sockerror(char *s) +{ +#ifdef _WINDOWS + int err = WSAGetLastError(); + if (err == 10054) return 1; + else if (err == 10053) post("netsend~: %s: software caused connection abort (%d)", s, err); + else if (err == 10055) post("netsend~: %s: no buffer space available (%d)", s, err); + else if (err == 10060) post("netsend~: %s: connection timed out (%d)", s, err); + else if (err == 10061) post("netsend~: %s: connection refused (%d)", s, err); + else post("netsend~: %s: %s (%d)", s, strerror(err), err); +#else + int err = errno; + post("netsend~: %s: %s (%d)", s, strerror(err), err); +#endif +#ifdef _WINDOWS + if (err == WSAEWOULDBLOCK) +#endif +#if defined(__linux__) || defined(__apple__) + if (err == EAGAIN) +#endif + { + return 1; /* recoverable error */ + } + return 0; /* indicate non-recoverable error */ +} + + + +static void netsend_tilde_closesocket(int fd) +{ +#if defined(__linux__) || defined(__apple__) + close(fd); +#endif +#ifdef NT + closesocket(fd); +#endif +} + + +/* ------------------------ netsend~ ----------------------------- */ + + +#ifdef PD +static t_class *netsend_tilde_class; +#else +static void *netsend_tilde_class; +#endif + +static t_symbol *ps_nothing, *ps_localhost; +static t_symbol *ps_format, *ps_channels, *ps_framesize, *ps_overflow, *ps_underflow; +static t_symbol *ps_queuesize, *ps_average, *ps_sf_float, *ps_sf_16bit, *ps_sf_8bit; +static t_symbol *ps_sf_mp3, *ps_sf_aac, *ps_sf_unknown, *ps_bitrate, *ps_hostname; + + +typedef struct _netsend_tilde +{ +#ifdef PD + t_object x_obj; + t_outlet *x_outlet; + t_outlet *x_outlet2; + t_clock *x_clock; +#else + t_pxobject x_obj; + void *x_outlet; + void *x_outlet2; + void *x_clock; +#endif + int x_fd; + int x_protocol; + t_tag x_tag; + t_symbol* x_hostname; + int x_portno; + int x_connectstate; + char *x_cbuf; + int x_cbufsize; + int x_blocksize; + int x_blockspersend; + int x_blockssincesend; + + long x_samplerate; /* samplerate we're running at */ + int x_vecsize; /* current DSP signal vector size */ + int x_ninlets; /* number of inlets */ + int x_channels; /* number of channels we want to stream */ + int x_format; /* format of streamed audio data */ + int x_bitrate; /* specifies bitrate for compressed formats */ + int x_count; /* total number of audio frames */ + t_int **x_myvec; /* vector we pass on in the DSP routine */ + +#ifdef USE_FAAC + unsigned char *x_faacbuf; /* data returned by encoder */ + faacEncHandle x_faac; +#endif + + pthread_mutex_t x_mutex; + pthread_cond_t x_requestcondition; + pthread_cond_t x_answercondition; + pthread_t x_childthread; +} t_netsend_tilde; + + + +static void netsend_tilde_notify(t_netsend_tilde *x) +{ + pthread_mutex_lock(&x->x_mutex); + x->x_childthread = 0; + outlet_float(x->x_outlet, x->x_connectstate); + pthread_mutex_unlock(&x->x_mutex); +} + + +static void netsend_tilde_disconnect(t_netsend_tilde *x) +{ + pthread_mutex_lock(&x->x_mutex); + if (x->x_fd != -1) + { + netsend_tilde_closesocket(x->x_fd); + x->x_fd = -1; + x->x_connectstate = 0; + outlet_float(x->x_outlet, 0); + } + pthread_mutex_unlock(&x->x_mutex); +} + + +static void *netsend_tilde_doconnect(void *zz) +{ + t_netsend_tilde *x = (t_netsend_tilde *)zz; + struct sockaddr_in server; + struct hostent *hp; + int intarg = 1; + int sockfd; + int portno; + t_symbol *hostname; + + pthread_mutex_lock(&x->x_mutex); + hostname = x->x_hostname; + portno = x->x_portno; + pthread_mutex_unlock(&x->x_mutex); + + /* create a socket */ + sockfd = socket(AF_INET, x->x_protocol, 0); + if (sockfd < 0) + { + post("netsend~: connection to %s on port %d failed", hostname->s_name,portno); + netsend_tilde_sockerror("socket"); + x->x_childthread = 0; + return (0); + } + + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(x->x_hostname->s_name); + if (hp == 0) + { + post("netsend~: bad host?"); + x->x_childthread = 0; + return (0); + } + + /* for stream (TCP) sockets, specify "nodelay" */ + if (x->x_protocol == SOCK_STREAM) + { + intarg = 1; + if (setsockopt(sockfd, SOL_IP, TCP_NODELAY, (const char *)&intarg, sizeof(intarg)) < 0) + error("netsend~: setsockopt(TCP_NODELAY) failed"); + } + +#ifdef SO_PRIORITY + /* set high priority, LINUX only */ + intarg = 6; /* select a priority between 0 and 7 */ + if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, (const char*)&intarg, sizeof(int)) < 0) + { + error("netsend~: setsockopt(SO_PRIORITY) failed"); + } +#endif + + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((unsigned short)portno); + + /* try to connect */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + netsend_tilde_sockerror("connecting stream socket"); + netsend_tilde_closesocket(sockfd); + x->x_childthread = 0; + return (0); + } + + post("netsend~: connected host %s on port %d", hostname->s_name, portno); + + pthread_mutex_lock(&x->x_mutex); + x->x_fd = sockfd; + x->x_connectstate = 1; + clock_delay(x->x_clock, 0); + pthread_mutex_unlock(&x->x_mutex); + return (0); +} + + + +#ifdef PD +static void netsend_tilde_connect(t_netsend_tilde *x, t_symbol *host, t_floatarg fportno) +#else +static void netsend_tilde_connect(t_netsend_tilde *x, t_symbol *host, long fportno) +#endif +{ + pthread_mutex_lock(&x->x_mutex); + if (x->x_childthread != 0) + { + pthread_mutex_unlock(&x->x_mutex); + post("netsend~: already trying to connect"); + return; + } + if (x->x_fd != -1) + { + pthread_mutex_unlock(&x->x_mutex); + post("netsend~: already connected"); + return; + } + + if (host != ps_nothing) + x->x_hostname = host; + else + x->x_hostname = ps_localhost; + + if (!fportno) + x->x_portno = DEFAULT_PORT; + else + x->x_portno = (int)fportno; + x->x_count = 0; + + /* start child thread to connect */ + pthread_create(&x->x_childthread, 0, netsend_tilde_doconnect, x); + pthread_mutex_unlock(&x->x_mutex); +} + + + +#ifdef USE_FAAC +static void netsend_tilde_faac_deinit(t_netsend_tilde *x) +{ + if (x->x_faac) + faacEncClose(x->x_faac); + x->x_faac = NULL; +} + + +static int netsend_tilde_faac_init(t_netsend_tilde *x) +{ + faacEncConfigurationPtr faac_format; + char *faac_id_string; + char *faac_copyright_string; + unsigned long samplesInput, maxBytesOutput; + unsigned int mpegVersion = MPEG2; + unsigned int channels = x->x_channels; + + if (!faacEncGetVersion(&faac_id_string, &faac_copyright_string) == FAAC_CFG_VERSION) + return -1; + + /* open the encoder library */ + x->x_faac = faacEncOpen(x->x_samplerate, channels, &samplesInput, &maxBytesOutput); + + /* put the options in the configuration struct */ + faac_format = faacEncGetCurrentConfiguration(x->x_faac); + faac_format->aacObjectType = MAIN; // LOW; /* MAIN, LOW or LTP */ + faac_format->mpegVersion = MPEG2; + faac_format->useTns = 1; + if (channels >= 6) + { + faac_format->useLfe = 1; + faac_format->allowMidside = 0; + } + else + { + faac_format->useLfe = 0; + faac_format->allowMidside = 1; + } + if (x->x_bitrate) + faac_format->bitRate = (x->x_bitrate * 1000) / channels; + faac_format->bandWidth = 0; + faac_format->outputFormat = 1; /* 0 = Raw; 1 = ADTS */ + faac_format->inputFormat = FAAC_INPUT_FLOAT; /* input is float but scaled by 32768 */ + if (!faacEncSetConfiguration(x->x_faac, faac_format)) + { + error("netsend~: faac: unsupported format settings"); + faacEncClose(x->x_faac); + x->x_faac = NULL; + return -1; + } + return 0; +} + + +static int netsend_tilde_faac_encode(t_netsend_tilde *x) +{ + static const int encbufsize = 1.25 * DEFAULT_AUDIO_BUFFER_SIZE + 7200; + unsigned int samples = x->x_blocksize * x->x_tag.channels; + float *bp = (float *)x->x_cbuf; + + /* encode AAC */ + int ret = faacEncEncode(x->x_faac, (int32_t *)bp, samples, x->x_faacbuf, encbufsize); + + /* check result */ + if (ret < 0) + { + error("netsend~: faac: encoding failed (%d)", ret); + faacEncClose(x->x_faac); + x->x_faac = NULL; + return -1; + } + + return ret; /* return total number of AAC samples (bytes) we got */ +} +#endif + +static t_int *netsend_tilde_perform(t_int *w) +{ + t_netsend_tilde* x = (t_netsend_tilde*) (w[1]); + int n = (int)(w[2]); + t_float *in[DEFAULT_AUDIO_CHANNELS]; + const int offset = 3; + char* bp = NULL; + int i, length = x->x_blocksize * SF_SIZEOF(x->x_tag.format) * x->x_tag.channels; + int sent = 0; + + pthread_mutex_lock(&x->x_mutex); + + for (i = 0; i < x->x_ninlets; i++) + in[i] = (t_float *)(w[offset + i]); + + if (n != x->x_vecsize) /* resize buffer */ + { + x->x_vecsize = n; + x->x_blockspersend = x->x_blocksize / x->x_vecsize; + x->x_blockssincesend = 0; + length = x->x_blocksize * SF_SIZEOF(x->x_tag.format) * x->x_tag.channels; + } + + /* format the buffer */ + switch (x->x_tag.format) + { + case SF_FLOAT: + { + t_float* fbuf = (t_float *)x->x_cbuf + (x->x_blockssincesend * x->x_vecsize * x->x_tag.channels); + while (n--) + for (i = 0; i < x->x_tag.channels; i++) + *fbuf++ = *(in[i]++); + break; + } + case SF_16BIT: + { + short* cibuf = (short *)x->x_cbuf + (x->x_blockssincesend * x->x_vecsize * x->x_tag.channels); + while (n--) + for (i = 0; i < x->x_tag.channels; i++) + *cibuf++ = (short)lrint(32767.0 * *(in[i]++)); + break; + } + case SF_8BIT: + { + unsigned char* cbuf = (unsigned char*)x->x_cbuf + (x->x_blockssincesend * x->x_vecsize * x->x_tag.channels); + while (n--) + for (i = 0; i < x->x_tag.channels; i++) + *cbuf++ = (unsigned char)(128. * (1.0 + *(in[i]++))); + break; + } +#ifdef USE_FAAC + case SF_AAC: + { + /* same as SF_FLOAT but * 32767 and no byteswapping required */ + t_float* fbuf = (t_float *)x->x_cbuf + (x->x_blockssincesend * x->x_vecsize * x->x_tag.channels); + while (n--) + for (i = 0; i < x->x_tag.channels; i++) + *fbuf++ = 32767.0 * *(in[i]++); + break; + } +#endif + default: + break; + } + + if (!(x->x_blockssincesend < x->x_blockspersend - 1)) /* time to send the buffer */ + { + x->x_blockssincesend = 0; + x->x_count++; /* count data packet we're going to send */ + + if (x->x_fd != -1) + { +#ifdef USE_FAAC + if (x->x_tag.format == SF_AAC) + { + /* encode PCM to AAC and return new framesize */ + length = netsend_tilde_faac_encode(x); + if (length <= 0) + { + if (length == 0) error("netsend~: skipping empty aac data frame"); + else error("netsend~: error encoding AAC"); + x->x_count--; + goto bail; + } + bp = (char *)x->x_faacbuf; + } + else +#endif + bp = (char *)x->x_cbuf; + + /* fill in the header tag */ + x->x_tag.framesize = length; + x->x_tag.count = x->x_count; + + /* send the format tag */ + if (send(x->x_fd, (char*)&x->x_tag, sizeof(t_tag), SEND_FLAGS) < 0) + { + netsend_tilde_sockerror("send tag"); + pthread_mutex_unlock(&x->x_mutex); + netsend_tilde_disconnect(x); + return (w + offset + x->x_ninlets); + } + + if (x->x_protocol == SOCK_STREAM) /* TCP: send all the data at once */ + { + /* send the buffer */ + for (sent = 0; sent < length;) + { + int ret = 0; + ret = send(x->x_fd, bp, length - sent, SEND_FLAGS); + if (ret <= 0) + { + netsend_tilde_sockerror("send data"); + pthread_mutex_unlock(&x->x_mutex); + netsend_tilde_disconnect(x); + return (w + offset + x->x_ninlets); + } + else + { + sent += ret; + bp += ret; + } + } + } + else /* UDP: max. packet size is 64k (incl. headers) so we have to split */ + { +#ifdef __APPLE__ + /* WARING: due to a 'bug' (maybe Apple would call it a feature?) in OS X + send calls with data packets larger than 16k fail with error number 40! + Thus we have to split the data packets into several packets that are + 16k in size. The other side will reassemble them again. */ + + int size = DEFAULT_UDP_PACKT_SIZE; + if (length < size) /* maybe data fits into one packet? */ + size = length; + + /* send the buffer */ + for (sent = 0; sent < length;) + { + int ret = 0; + ret = send(x->x_fd, bp, size, SEND_FLAGS); + if (ret <= 0) + { + netsend_tilde_sockerror("send data"); + pthread_mutex_unlock(&x->x_mutex); + netsend_tilde_disconnect(x); + return (w + offset + x->x_ninlets); + } + else + { + bp += ret; + sent += ret; + if ((length - sent) < size) + size = length - sent; + } + } +#else + /* send the buffer, the OS might segment it into smaller packets */ + int ret = send(x->x_fd, bp, length, SEND_FLAGS); + if (ret <= 0) + { + netsend_tilde_sockerror("send data"); + pthread_mutex_unlock(&x->x_mutex); + netsend_tilde_disconnect(x); + return (w + offset + x->x_ninlets); + } +#endif + } + } + +#ifdef USE_FAAC +bail: +#endif + /* check whether user has updated any parameters */ + if (x->x_tag.channels != x->x_channels) + { +#ifdef USE_FAAC + /* reinit FAAC if format is SF_AAC */ + if (x->x_tag.format == SF_AAC) + { + netsend_tilde_faac_deinit(x); + netsend_tilde_faac_init(x); + } +#endif + x->x_tag.channels = x->x_channels; + } + if (x->x_tag.format != x->x_format) + { +#ifdef USE_FAAC + /* deinit FAAC if format was SF_AAC */ + if ((x->x_format != SF_AAC) && (x->x_tag.format == SF_AAC)) + { + netsend_tilde_faac_deinit(x); + } +#endif + x->x_tag.format = x->x_format; + } + } + else + { + x->x_blockssincesend++; + } + pthread_mutex_unlock(&x->x_mutex); + return (w + offset + x->x_ninlets); +} + + + +static void netsend_tilde_dsp(t_netsend_tilde *x, t_signal **sp) +{ + int i; + + pthread_mutex_lock(&x->x_mutex); + + x->x_myvec[0] = (t_int*)x; + x->x_myvec[1] = (t_int*)sp[0]->s_n; + + x->x_samplerate = sp[0]->s_sr; + + for (i = 0; i < x->x_ninlets; i++) + { + x->x_myvec[2 + i] = (t_int*)sp[i]->s_vec; + } + + pthread_mutex_unlock(&x->x_mutex); + + if (DEFAULT_AUDIO_BUFFER_SIZE % sp[0]->s_n) + { + error("netsend~: signal vector size too large (needs to be even divisor of %d)", DEFAULT_AUDIO_BUFFER_SIZE); + } + else + { +#ifdef PD + dsp_addv(netsend_tilde_perform, x->x_ninlets + 2, (t_int*)x->x_myvec); +#else + dsp_addv(netsend_tilde_perform, x->x_ninlets + 2, (void**)x->x_myvec); +#endif + } +} + + +#ifdef PD +static void netsend_tilde_channels(t_netsend_tilde *x, t_floatarg channels) +#else +static void netsend_tilde_channels(t_netsend_tilde *x, long channels) +#endif +{ + pthread_mutex_lock(&x->x_mutex); + if (channels >= 0 && channels <= DEFAULT_AUDIO_CHANNELS) + { + x->x_channels = (int)channels; + post("netsend~: channels set to %d", (int)channels); + } + pthread_mutex_unlock(&x->x_mutex); +} + + + +#ifdef PD +static void netsend_tilde_format(t_netsend_tilde *x, t_symbol* form, t_floatarg bitrate) +#else +static void netsend_tilde_format(t_netsend_tilde *x, t_symbol* form, long bitrate) +#endif +{ + pthread_mutex_lock(&x->x_mutex); + if (!strncmp(form->s_name,"float", 5) && x->x_tag.format != SF_FLOAT) + { + x->x_format = (int)SF_FLOAT; + } + else if (!strncmp(form->s_name,"16bit", 5) && x->x_tag.format != SF_16BIT) + { + x->x_format = (int)SF_16BIT; + } + else if (!strncmp(form->s_name,"8bit", 4) && x->x_tag.format != SF_8BIT) + { + x->x_format = (int)SF_8BIT; + } + else if (!strncmp(form->s_name,"mp3", 3) && x->x_tag.format != SF_MP3) + { + error("netsend~: not compiled with mp3 support"); + pthread_mutex_unlock(&x->x_mutex); + return; + } + else if (!strncmp(form->s_name,"aac", 3) && x->x_tag.format != SF_AAC) + { +#ifdef USE_FAAC + x->x_bitrate = (int)bitrate; + if (netsend_tilde_faac_init(x) < 0) + { + pthread_mutex_unlock(&x->x_mutex); + return; + } + x->x_format = (int)SF_AAC; +#else + error("netsend~: not compiled with aac support"); + pthread_mutex_unlock(&x->x_mutex); + return; +#endif + } + + post("netsend~: format set to %s", form->s_name); + pthread_mutex_unlock(&x->x_mutex); +} + + +/* set hostname to send to */ +static void netsend_tilde_host(t_netsend_tilde *x, t_symbol* host) +{ + pthread_mutex_lock(&x->x_mutex); + if (host != ps_nothing) + x->x_hostname = host; + else + x->x_hostname = ps_localhost; + + if (x->x_fd != -1) + { + pthread_mutex_unlock(&x->x_mutex); + netsend_tilde_connect(x,x->x_hostname, (float)x->x_portno); + return; + } + pthread_mutex_unlock(&x->x_mutex); +} + + + +#ifdef PD +static void netsend_tilde_float(t_netsend_tilde* x, t_floatarg arg) +#else +static void netsend_tilde_float(t_netsend_tilde* x, double arg) +#endif +{ + if (arg == 0.0) + netsend_tilde_disconnect(x); + else + netsend_tilde_connect(x,x->x_hostname,(float) x->x_portno); +} + + +/* send stream info when banged */ +static void netsend_tilde_bang(t_netsend_tilde *x) +{ + t_atom list[2]; + t_symbol *sf_format; + t_float bitrate; + + bitrate = (t_float)((SF_SIZEOF(x->x_tag.format) * x->x_samplerate * 8 * x->x_tag.channels) / 1000.); + + switch (x->x_tag.format) + { + case SF_FLOAT: + { + sf_format = ps_sf_float; + break; + } + case SF_16BIT: + { + sf_format = ps_sf_16bit; + break; + } + case SF_8BIT: + { + sf_format = ps_sf_8bit; + break; + } + case SF_MP3: + { + sf_format = ps_sf_mp3; + break; + } + case SF_AAC: + { + sf_format = ps_sf_aac; + break; + } + default: + { + sf_format = ps_sf_unknown; + break; + } + } + +#ifdef PD + /* --- stream information (t_tag) --- */ + /* audio format */ + SETSYMBOL(list, (t_symbol *)sf_format); + outlet_anything(x->x_outlet2, ps_format, 1, list); + + /* channels */ + SETFLOAT(list, (t_float)x->x_tag.channels); + outlet_anything(x->x_outlet2, ps_channels, 1, list); + + /* framesize */ + SETFLOAT(list, (t_float)x->x_tag.framesize); + outlet_anything(x->x_outlet2, ps_framesize, 1, list); + + /* bitrate */ + SETFLOAT(list, (t_float)bitrate); + outlet_anything(x->x_outlet2, ps_bitrate, 1, list); + + /* IP address */ + SETSYMBOL(list, (t_symbol *)x->x_hostname); + outlet_anything(x->x_outlet2, ps_hostname, 1, list); +#else + /* --- stream information (t_tag) --- */ + /* audio format */ + SETSYM(list, ps_format); + SETSYM(list + 1, (t_symbol *)sf_format); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* channels */ + SETSYM(list, ps_channels); + SETLONG(list + 1, (int)x->x_tag.channels); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* framesize */ + SETSYM(list, ps_framesize); + SETLONG(list + 1, (int)x->x_tag.framesize); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* bitrate */ + SETSYM(list, ps_bitrate); + SETFLOAT(list + 1, (t_float)bitrate); + outlet_list(x->x_outlet2, NULL, 2, list); + + /* IP address */ + SETSYM(list, (t_symbol *)ps_hostname); + SETSYM(list + 1, x->x_hostname); + outlet_list(x->x_outlet2, NULL, 2, list); +#endif +} + + +#ifdef PD +static void *netsend_tilde_new(t_floatarg inlets, t_floatarg prot) +#else +static void *netsend_tilde_new(long inlets, long prot) +#endif +{ + int i; + +#ifdef PD + t_netsend_tilde *x = (t_netsend_tilde *)pd_new(netsend_tilde_class); + if (x) + { + for (i = sizeof(t_object); i < (int)sizeof(t_netsend_tilde); i++) + ((char *)x)[i] = 0; + } + + x->x_ninlets = CLIP((int)inlets, 1, DEFAULT_AUDIO_CHANNELS); + for (i = 1; i < x->x_ninlets; i++) + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal); + + x->x_outlet = outlet_new(&x->x_obj, &s_float); + x->x_outlet2 = outlet_new(&x->x_obj, &s_list); + x->x_clock = clock_new(x, (t_method)netsend_tilde_notify); +#else + t_netsend_tilde *x = (t_netsend_tilde *)newobject(netsend_tilde_class); + if (x) + { + for (i = sizeof(t_pxobject); i < sizeof(t_netsend_tilde); i++) + ((char *)x)[i] = 0; + } + + x->x_ninlets = CLIP((int)inlets, 1, DEFAULT_AUDIO_CHANNELS); + dsp_setup((t_pxobject *)x, x->x_ninlets); + x->x_outlet2 = outlet_new(x, "list"); + x->x_outlet = outlet_new(x, "int"); + x->x_clock = clock_new(x, (method)netsend_tilde_notify); +#endif + + x->x_myvec = (t_int **)t_getbytes(sizeof(t_int *) * (x->x_ninlets + 3)); + if (!x->x_myvec) + { + error("netsend~: out of memory"); + return NULL; + } + +#ifdef USE_FAAC + /* allocate a buffer for encoded data */ + x->x_faacbuf = (unsigned char *)t_getbytes(sizeof(char *) * (1.25 * DEFAULT_AUDIO_BUFFER_SIZE + 7200)); + if (!x->x_faacbuf) + { + error("netsend~: out of memory"); + return NULL; + } + x->x_faac = NULL; /* encoder not yet nitialized */ +#endif + + pthread_mutex_init(&x->x_mutex, 0); + pthread_cond_init(&x->x_requestcondition, 0); + pthread_cond_init(&x->x_answercondition, 0); + + x->x_hostname = ps_localhost; + x->x_portno = 3000; + x->x_connectstate = 0; + x->x_childthread = 0; + x->x_fd = -1; + x->x_protocol = SOCK_STREAM; + + if (prot) /* user wants UDP */ + { + x->x_protocol = SOCK_DGRAM; + } + + x->x_tag.format = x->x_format = SF_FLOAT; + x->x_tag.channels = x->x_channels = x->x_ninlets; + x->x_tag.version = SF_BYTE_NATIVE; /* native endianness */ + x->x_vecsize = 64; /* we'll update this later */ + x->x_bitrate = 0; /* not specified, use default */ + x->x_cbuf = NULL; + x->x_blocksize = DEFAULT_AUDIO_BUFFER_SIZE; + x->x_blockspersend = x->x_blocksize / x->x_vecsize; + x->x_blockssincesend = 0; + x->x_cbufsize = x->x_blocksize * sizeof(t_float) * x->x_ninlets; + x->x_cbuf = (char *)t_getbytes(x->x_cbufsize); + +#if defined(__linux__) || defined(__apple__) + /* we don't want to get signaled in case send() fails */ + signal(SIGPIPE, SIG_IGN); +#endif + + return (x); +} + + + +static void netsend_tilde_free(t_netsend_tilde* x) +{ + netsend_tilde_disconnect(x); + +#ifndef PD + dsp_free((t_pxobject *)x); /* free the object */ +#endif + + /* free the memory */ + if (x->x_cbuf)t_freebytes(x->x_cbuf, x->x_cbufsize); + if (x->x_myvec)t_freebytes(x->x_myvec, sizeof(t_int) * (x->x_ninlets + 3)); + +#ifdef USE_FAAC + if (x->x_faacbuf)t_freebytes(x->x_faacbuf, sizeof(char *) * (1.25 * DEFAULT_AUDIO_BUFFER_SIZE + 7200)); + netsend_tilde_faac_deinit(x); +#endif + + clock_free(x->x_clock); + + pthread_cond_destroy(&x->x_requestcondition); + pthread_cond_destroy(&x->x_answercondition); + pthread_mutex_destroy(&x->x_mutex); +} + + +#ifdef PD + +void netsend_tilde_setup(void) +{ + netsend_tilde_class = class_new(gensym("netsend~"), (t_newmethod)netsend_tilde_new, (t_method)netsend_tilde_free, + sizeof(t_netsend_tilde), 0, A_DEFFLOAT, A_DEFFLOAT, A_NULL); + class_addmethod(netsend_tilde_class, nullfn, gensym("signal"), 0); + class_addmethod(netsend_tilde_class, (t_method)netsend_tilde_dsp, gensym("dsp"), 0); + class_addfloat(netsend_tilde_class, netsend_tilde_float); + class_addbang(netsend_tilde_class, netsend_tilde_bang); + class_addmethod(netsend_tilde_class, (t_method)netsend_tilde_connect, gensym("connect"), A_DEFSYM, A_DEFFLOAT, 0); + class_addmethod(netsend_tilde_class, (t_method)netsend_tilde_disconnect, gensym("disconnect"), 0); + class_addmethod(netsend_tilde_class, (t_method)netsend_tilde_channels, gensym("channels"), A_FLOAT, 0); + class_addmethod(netsend_tilde_class, (t_method)netsend_tilde_format, gensym("format"), A_SYMBOL, A_DEFFLOAT, 0); + class_addmethod(netsend_tilde_class, (t_method)netsend_tilde_host, gensym("host"), A_DEFSYM, 0); + class_sethelpsymbol(netsend_tilde_class, gensym("netsend~")); + post("netsend~ v%s, (c) 2004-2005 Olaf Matthes", VERSION); + + ps_nothing = gensym(""); + ps_localhost = gensym("localhost"); + ps_hostname = gensym("ipaddr"); + ps_format = gensym("format"); + ps_channels = gensym("channels"); + ps_framesize = gensym("framesize"); + ps_bitrate = gensym("bitrate"); + ps_sf_float = gensym("_float_"); + ps_sf_16bit = gensym("_16bit_"); + ps_sf_8bit = gensym("_8bit_"); + ps_sf_mp3 = gensym("_mp3_"); + ps_sf_aac = gensym("_aac_"); + ps_sf_unknown = gensym("_unknown_"); +} + +#else + +void netsend_tilde_assist(t_netsend_tilde *x, void *b, long m, long a, char *s) +{ + switch(m) + { + case 1: // inlet + switch(a) + { + case 0: + sprintf(s, "Control Messages & Audio Channel 1"); + break; + default: + sprintf(s, "Audio Channel %d", (int)a + 1); + break; + } + break; + case 2: // outlet + switch(a) + { + case 0: + sprintf(s, "(Int) State of Connection"); + break; + } + break; + } + +} + +void main() +{ +#ifdef _WINDOWS + short version = MAKEWORD(2, 0); + WSADATA nobby; +#endif /* _WINDOWS */ + + setup((t_messlist **)&netsend_tilde_class, (method)netsend_tilde_new, (method)netsend_tilde_free, + (short)sizeof(t_netsend_tilde), 0L, A_DEFLONG, A_DEFLONG, 0); + + addmess((method)netsend_tilde_dsp, "dsp", A_CANT, 0); + addmess((method)netsend_tilde_connect, "connect", A_DEFSYM, A_DEFLONG, 0); + addmess((method)netsend_tilde_disconnect, "disconnect", 0); + addmess((method)netsend_tilde_format, "format", A_SYM, A_DEFLONG, 0); + addmess((method)netsend_tilde_channels, "channels", A_LONG, 0); + addmess((method)netsend_tilde_host, "host", A_DEFSYM, 0); + addmess((method)netsend_tilde_assist, "assist", A_CANT, 0); + addbang((method)netsend_tilde_bang); + dsp_initclass(); + finder_addclass("System", "netsend~"); + post("netsend~ v%s, © 2004-2005 Olaf Matthes", VERSION); + ps_nothing = gensym(""); + ps_localhost = gensym("localhost"); + ps_hostname = gensym("ipaddr"); + ps_format = gensym("format"); + ps_channels = gensym("channels"); + ps_framesize = gensym("framesize"); + ps_bitrate = gensym("bitrate"); + ps_sf_float = gensym("_float_"); + ps_sf_16bit = gensym("_16bit_"); + ps_sf_8bit = gensym("_8bit_"); + ps_sf_mp3 = gensym("_mp3_"); + ps_sf_aac = gensym("_aac_"); + ps_sf_unknown = gensym("_unknown_"); + +#ifdef _WINDOWS + if (WSAStartup(version, &nobby)) error("netsend~: WSAstartup failed"); +#endif /* _WINDOWS */ +} + +#endif /* PD */ diff --git a/l2ork_addons/raspberry_pi/netsend_tilde/netsend~.h b/l2ork_addons/raspberry_pi/netsend_tilde/netsend~.h new file mode 100644 index 0000000000000000000000000000000000000000..874b63f25b48ea82ee4e7e6d38d9ff67db4e4425 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netsend_tilde/netsend~.h @@ -0,0 +1,154 @@ +/* ------------------------ netsend~ ------------------------------------------ */ +/* */ +/* Tilde object to send uncompressed audio data to netreceive~. */ +/* Written by Olaf Matthes <olaf.matthes@gmx.de>. */ +/* Based on streamout~ by Guenter Geiger. */ +/* Get source at http://www.akustische-kunst.org/ */ +/* */ +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ +/* */ +/* See file LICENSE for further informations on licensing terms. */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* This project was commissioned by the Society for Arts and Technology [SAT], */ +/* Montreal, Quebec, Canada, http://www.sat.qc.ca/. */ +/* */ +/* ---------------------------------------------------------------------------- */ + + +/* This file is based on and inspired by stream.h (C) Guenter Geiger 1999. */ +/* Some enhancements have been made with the goal of keeping compatibility */ +/* between the stream formats of streamout~/in~ and netsend~/receive~. */ + +#define VERSION "1.0alpha11" + +#define DEFAULT_AUDIO_CHANNELS 32 /* nax. number of audio channels we support */ +#define DEFAULT_AUDIO_BUFFER_SIZE 1024 /* number of samples in one audio block */ +#define DEFAULT_UDP_PACKT_SIZE 8192 /* number of bytes we send in one UDP datagram (OS X only) */ +#define DEFAULT_PORT 8000 /* default network port number */ + +#ifdef _WINDOWS +#ifndef HAVE_INT32_T +typedef int int32_t; +#define HAVE_INT32_T +#endif +#ifndef HAVE_INT16_T +typedef short int16_t; +#define HAVE_INT16_T +#endif +#ifndef HAVE_U_INT32_T +typedef unsigned int u_int32_t; +#define HAVE_U_INT32_T +#endif +#ifndef HAVE_U_INT16_T +typedef unsigned short u_int16_t; +#define HAVE_U_INT16_T +#endif +#endif + +#ifndef CLIP +#define CLIP(a, lo, hi) ( (a)>(lo)?( (a)<(hi)?(a):(hi) ):(lo) ) +#endif + + +/* swap 32bit t_float. Is there a better way to do that???? */ +#ifdef _WINDOWS +__inline static float netsend_float(float f) +#else +inline static float netsend_float(float f) +#endif +{ + union + { + float f; + unsigned char b[4]; + } dat1, dat2; + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +/* swap 32bit long int */ +#ifdef _WINDOWS +__inline static long netsend_long(long n) +#else +inline static long netsend_long(long n) +#endif +{ + return (((n & 0xff) << 24) | ((n & 0xff00) << 8) | + ((n & 0xff0000) >> 8) | ((n & 0xff000000) >> 24)); +} + +/* swap 16bit short int */ +#ifdef _WINDOWS +__inline static long netsend_short(long n) +#else +inline static short netsend_short(short n) +#endif +{ + return (((n & 0xff) << 8) | ((n & 0xff00) >> 8)); +} + + +/* format specific stuff */ + +#define SF_FLOAT 1 +#define SF_DOUBLE 2 /* not implemented */ +#define SF_8BIT 10 +#define SF_16BIT 11 +#define SF_32BIT 12 /* not implemented */ +#define SF_ALAW 20 /* not implemented */ +#define SF_MP3 30 /* not implemented */ +#define SF_AAC 31 /* AAC encoding using FAAC */ +#define SF_VORBIS 40 /* not implemented */ +#define SF_FLAC 50 /* not implemented */ + +#define SF_SIZEOF(a) (a == SF_FLOAT ? sizeof(t_float) : \ + a == SF_16BIT ? sizeof(short) : 1) + + +/* version / byte-endian specific stuff */ + +#define SF_BYTE_LE 1 /* little endian */ +#define SF_BYTE_BE 2 /* big endian */ + +#if defined(_WINDOWS) || defined(__linux__) || defined(IRIX) +#define SF_BYTE_NATIVE SF_BYTE_LE +#else /* must be __APPLE__ */ +#define SF_BYTE_NATIVE SF_BYTE_BE +#endif + + +typedef struct _tag { /* size (bytes) */ + char version; /* 1 */ + char format; /* 1 */ + long count; /* 4 */ + char channels; /* 1 */ + long framesize; /* 4 */ + char extension[5]; /* 5 */ +} t_tag; /*--------------*/ + /* 16 */ + + +typedef struct _frame { + t_tag tag; + char *data; +} t_frame; + diff --git a/l2ork_addons/raspberry_pi/netsend_tilde/test-netsend~.pd b/l2ork_addons/raspberry_pi/netsend_tilde/test-netsend~.pd new file mode 100644 index 0000000000000000000000000000000000000000..d3d9cbf5f99a069fe7bc787d5560a95ef58c8131 --- /dev/null +++ b/l2ork_addons/raspberry_pi/netsend_tilde/test-netsend~.pd @@ -0,0 +1,72 @@ +#N canvas 291 209 642 596 10; +#X obj 18 178 netreceive~ 8008 4; +#X obj 19 514 netsend~ 4; +#X obj 18 221 dac~ 1; +#X obj 68 221 dac~ 2; +#X obj 121 221 dac~ 3; +#X obj 175 222 dac~ 4; +#X obj 74 480 osc~ 440; +#X obj 145 481 osc~ 880; +#X obj 214 480 osc~ 990; +#X obj 281 481 osc~ 220; +#X text 245 515 sends 4 dsp-channels to localhost:8008; +#X msg 62 325 disconnect; +#X msg 76 350 format float; +#X msg 86 374 format 16bit; +#X msg 102 398 format 8bit; +#X text 189 358 format defines the resolution of the sent signal; +#X text 347 480 the signals to send; +#X floatatom 19 558 5 0 0 0 - - -; +#X text 63 559 status: 1 = connected 0 = disconnected; +#X msg 19 273 connect localhost 8008; +#X text 15 13 netreceive~/netsend~; +#X text 15 31 written by Olaf Matthes <olaf.matthes@gmx.de>; +#X text 161 181 receives 4 channels on port 8808; +#X text 186 274 connect to <hostname> <port>; +#X text 203 421 change number of channels; +#X text 200 376 float is the most expensive with the best resolution +(32bit) \, default is 16bit; +#X msg 109 422 channels 2; +#X msg 38 298 connect 255.255.255.255 8008; +#X text 14 50 commissioned by the Society for Arts and Technology [SAT] +\, Montreal \, Quebec \, Canada \, http://www.sat.qc.at/; +#X obj 269 222 print; +#X obj 18 96 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X msg 46 97 kick; +#X msg 51 122 buffer 0.25; +#X msg 62 149 print; +#X obj 87 539 print; +#X floatatom 224 224 5 0 0 0 - - -; +#X text 220 298 broadcast to everybody on your local subnet listening +on the specified port (in UDP mode only!); +#X obj 112 448 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X text 137 448 get info; +#X text 226 252 connect to <hostname> <port>; +#X msg 23 250 connect 192.168.1.10 8008; +#X connect 0 0 2 0; +#X connect 0 1 3 0; +#X connect 0 2 4 0; +#X connect 0 3 5 0; +#X connect 0 4 35 0; +#X connect 0 5 29 0; +#X connect 1 0 17 0; +#X connect 1 1 34 0; +#X connect 6 0 1 0; +#X connect 7 0 1 1; +#X connect 8 0 1 2; +#X connect 9 0 1 3; +#X connect 11 0 1 0; +#X connect 12 0 1 0; +#X connect 13 0 1 0; +#X connect 14 0 1 0; +#X connect 19 0 1 0; +#X connect 26 0 1 0; +#X connect 27 0 1 0; +#X connect 30 0 0 0; +#X connect 31 0 0 0; +#X connect 32 0 0 0; +#X connect 33 0 0 0; +#X connect 37 0 1 0; +#X connect 40 0 1 0;