From f29e5ba61aebdda221b201e35bb47a4a9c885735 Mon Sep 17 00:00:00 2001 From: Martin Peach Date: Wed, 16 Aug 2006 20:22:22 +0000 Subject: Added the net, osc and sqosc~ directories svn path=/trunk/externals/mrpeach/; revision=5629 --- net/tcpclient-help.pd | 72 ++++ net/tcpreceive-help.pd | 20 + net/tcpsend-help.pd | 16 + net/tcpserver-help.pd | 72 ++++ net/udpreceive-help.pd | 17 + net/udpsend-help.pd | 16 + net/x_net_tcp_client.c | 443 +++++++++++++++++++++ net/x_net_tcp_server.c | 600 +++++++++++++++++++++++++++++ net/x_net_tcpclient.c | 443 +++++++++++++++++++++ net/x_net_tcpreceive.c | 313 +++++++++++++++ net/x_net_tcpsend.c | 221 +++++++++++ net/x_net_tcpserver.c | 619 ++++++++++++++++++++++++++++++ net/x_net_udpreceive.c | 161 ++++++++ net/x_net_udpsend.c | 214 +++++++++++ osc/packOSC-help.pd | 30 ++ osc/packOSC.c | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++ osc/routeOSC-help.pd | 35 ++ osc/routeOSC.c | 510 ++++++++++++++++++++++++ osc/unpackOSC.c | 604 +++++++++++++++++++++++++++++ sqosc~/sqosc-help.pd | 116 ++++++ sqosc~/sqosc.c | 461 ++++++++++++++++++++++ 21 files changed, 5983 insertions(+) create mode 100755 net/tcpclient-help.pd create mode 100755 net/tcpreceive-help.pd create mode 100755 net/tcpsend-help.pd create mode 100755 net/tcpserver-help.pd create mode 100755 net/udpreceive-help.pd create mode 100755 net/udpsend-help.pd create mode 100755 net/x_net_tcp_client.c create mode 100755 net/x_net_tcp_server.c create mode 100755 net/x_net_tcpclient.c create mode 100755 net/x_net_tcpreceive.c create mode 100755 net/x_net_tcpsend.c create mode 100755 net/x_net_tcpserver.c create mode 100755 net/x_net_udpreceive.c create mode 100755 net/x_net_udpsend.c create mode 100755 osc/packOSC-help.pd create mode 100755 osc/packOSC.c create mode 100755 osc/routeOSC-help.pd create mode 100755 osc/routeOSC.c create mode 100755 osc/unpackOSC.c create mode 100755 sqosc~/sqosc-help.pd create mode 100755 sqosc~/sqosc.c diff --git a/net/tcpclient-help.pd b/net/tcpclient-help.pd new file mode 100755 index 0000000..952ea84 --- /dev/null +++ b/net/tcpclient-help.pd @@ -0,0 +1,72 @@ +#N canvas 89 21 1092 501 12; +#X msg 61 93 disconnect; +#X obj 78 222 unpack 0 0 0 0; +#X floatatom 78 245 3 0 0 0 - - -; +#X floatatom 113 245 3 0 0 0 - - -; +#X floatatom 149 245 3 0 0 0 - - -; +#X floatatom 185 245 3 0 0 0 - - -; +#X text 35 244 from; +#X msg 45 30 connect 132.205.142.12 80; +#X obj 45 170 tcpclient; +#X msg 55 53 send 71 69 84 32 104 116 116 112 58 47 47 47 105 110 100 +101 120 46 104 116 109 108 13 10; +#X text 566 53 GET http:///index.html CRLF; +#X obj 112 196 tgl 15 0 empty empty connected 18 7 0 8 -24198 -241291 +-1 0 1; +#X msg 160 115 dump \$1; +#X obj 160 96 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 1 +; +#X msg 231 139 receive; +#X msg 295 139 recv; +#X text 214 -21 connect with an IP address and port number; +#X msg 23 5 connect www.concordia.ca 80; +#X text 231 114 print received messages to main window in hexdump format +; +#X text 131 171 tcpclient opens a tcp socket to send and receive bytes +on; +#X text 276 263 See also:; +#X obj 354 263 netclient; +#X msg 10 -20 connect 127.0.0.1 9997; +#X obj 354 286 tcpreceive; +#X text 574 285 can receive messages from tcpclient; +#X text 442 263 is what tcpclient is based on; +#X text 21 325 Received messages are output as a list of bytes; +#X text 20 343 Attempting to print long messages output can hang pd! +; +#X text 343 132 get any received data (not useful unless you need it +faster than once per 20ms); +#X text 445 93 semicolon-terminated string for netserver or netreceive +; +#X msg 249 93 send 49 127 128 51 59; +#X obj -86 317 textfile; +#X msg -86 213 clear; +#X msg -67 285 write tcpclientreceived.txt; +#X obj -73 254 prepend add; +#X obj -86 186 loadbang; +#X text 575 342 2006/05/12 Martin Peach; +#X obj 482 286 tcpserver; +#X text 448 285 and; +#X text -71 -82 tcpclient can connect to a server and send and receive +messages as lists of bytes. Any integer value between 0 and 255 can +be transmitted or received.; +#X connect 0 0 8 0; +#X connect 1 0 2 0; +#X connect 1 1 3 0; +#X connect 1 2 4 0; +#X connect 1 3 5 0; +#X connect 7 0 8 0; +#X connect 8 0 34 0; +#X connect 8 1 1 0; +#X connect 8 2 11 0; +#X connect 9 0 8 0; +#X connect 12 0 8 0; +#X connect 13 0 12 0; +#X connect 14 0 8 0; +#X connect 15 0 8 0; +#X connect 17 0 8 0; +#X connect 22 0 8 0; +#X connect 30 0 8 0; +#X connect 32 0 31 0; +#X connect 33 0 31 0; +#X connect 34 0 31 0; +#X connect 35 0 32 0; diff --git a/net/tcpreceive-help.pd b/net/tcpreceive-help.pd new file mode 100755 index 0000000..b2d781d --- /dev/null +++ b/net/tcpreceive-help.pd @@ -0,0 +1,20 @@ +#N canvas 713 11 478 294 12; +#X obj 212 117 unpack 0 0 0 0; +#X floatatom 212 140 3 0 0 0 - - -; +#X floatatom 247 140 3 0 0 0 - - -; +#X floatatom 283 140 3 0 0 0 - - -; +#X floatatom 319 140 3 0 0 0 - - -; +#X text 169 139 from; +#X obj 155 185 print message; +#X obj 155 57 tcpreceive 9997; +#X floatatom 270 96 5 0 0 0 - - -; +#X text 316 94 connections; +#X text 32 16 tcpreceive receives bytes over a tcp connection.; +#X text 265 235 Martin Peach 2006/04/21; +#X connect 0 0 1 0; +#X connect 0 1 2 0; +#X connect 0 2 3 0; +#X connect 0 3 4 0; +#X connect 7 0 6 0; +#X connect 7 1 0 0; +#X connect 7 2 8 0; diff --git a/net/tcpsend-help.pd b/net/tcpsend-help.pd new file mode 100755 index 0000000..e61a322 --- /dev/null +++ b/net/tcpsend-help.pd @@ -0,0 +1,16 @@ +#N canvas 0 0 555 316 12; +#X msg 177 158 disconnect; +#X msg 171 96 connect 127.0.0.1 9997; +#X obj 171 207 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 171 184 tcpsend; +#X text 375 97 <--first; +#X msg 67 129 send 0 1 2 3; +#X text 10 38 tcpsend sends bytes over a tcp connection.; +#X text 10 61 Used in conjunction with packOSC will send OSC over tcp +; +#X text 236 259 Martin Peach 2006/04/21; +#X connect 0 0 3 0; +#X connect 1 0 3 0; +#X connect 3 0 2 0; +#X connect 5 0 3 0; diff --git a/net/tcpserver-help.pd b/net/tcpserver-help.pd new file mode 100755 index 0000000..33e4b13 --- /dev/null +++ b/net/tcpserver-help.pd @@ -0,0 +1,72 @@ +#N canvas 13 208 1115 644 12; +#X msg 18 -6 print; +#X obj 136 109 tcpserver 9997; +#X floatatom 171 237 5 0 0 0 - - -; +#X floatatom 286 191 5 0 0 0 - - -; +#X obj 324 138 unpack 0 0 0 0; +#X floatatom 324 161 3 0 0 0 - - -; +#X floatatom 359 161 3 0 0 0 - - -; +#X floatatom 395 161 3 0 0 0 - - -; +#X floatatom 431 161 3 0 0 0 - - -; +#X text 281 160 from; +#X text 224 236 connections; +#X obj 43 161 print received; +#X msg 118 45 client 1 1 2 3; +#X msg 86 20 broadcast 1 2 3; +#X text 69 -6 list of connections; +#X text 226 19 send to all clients; +#X text 249 45 send to client 1; +#X text 347 254 2006/05/11 Martin Peach; +#X text 199 191 on socket; +#X msg 140 71 send 504 1 2 3; +#X text 273 71 send to client on socket 504; +#X msg 564 -365 client 1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 +18 19; +#X msg 563 -340 client 1 20 21 22 23 24 25 26 27 28 29 30 31 32 33 +34 35 36 37 38 39; +#X msg 563 -297 client 1 40 41 42 43 44 45 46 47 48 49 50 51 52 53 +54 55 56 57 58 59; +#X msg 563 -255 client 1 60 61 62 63 64 65 66 67 68 69 70 71 72 73 +74 75 76 77 78 79; +#X msg 561 -213 client 1 80 81 82 83 84 85 86 87 88 89 90 91 92 93 +94 95 96 97 98 99; +#X msg 560 -170 client 1 100 101 102 103 104 105 106 107 108 109 110 +111 112 113 114 115 116 117 118 119; +#X msg 559 -127 client 1 120 121 122 123 124 125 126 127 138 139 140 +141 142 143 144 145 146 147 148 149; +#X msg 559 -84 client 1 150 151 152 153 154 155 156 157 158 159 160 +161 162 163 164 165 166 167 168 169; +#X msg 559 -39 client 1 170 171 172 173 174 175 176 177 178 179 180 +181 182 183 184 185 186 187 188 189; +#X msg 558 5 client 1 190 191 192 193 194 195 196 197 198 199 200 201 +202 203 204 205 206 207 208 209; +#X msg 558 47 client 1 210 211 212 213 214 215 216 217 218 219 220 +221 222 223 224 225 226 227 228 229; +#X msg 558 89 client 1 230 231 232 233 234 235 236 237 238 239 240 +241 242 243 244 245 246 247 248 249; +#X msg 558 134 client 1 250 251 252 253 254 255; +#X connect 0 0 1 0; +#X connect 1 0 11 0; +#X connect 1 1 2 0; +#X connect 1 2 3 0; +#X connect 1 3 4 0; +#X connect 4 0 5 0; +#X connect 4 1 6 0; +#X connect 4 2 7 0; +#X connect 4 3 8 0; +#X connect 12 0 1 0; +#X connect 13 0 1 0; +#X connect 19 0 1 0; +#X connect 21 0 1 0; +#X connect 22 0 1 0; +#X connect 23 0 1 0; +#X connect 24 0 1 0; +#X connect 25 0 1 0; +#X connect 26 0 1 0; +#X connect 27 0 1 0; +#X connect 28 0 1 0; +#X connect 29 0 1 0; +#X connect 30 0 1 0; +#X connect 31 0 1 0; +#X connect 32 0 1 0; +#X connect 33 0 1 0; diff --git a/net/udpreceive-help.pd b/net/udpreceive-help.pd new file mode 100755 index 0000000..b86e734 --- /dev/null +++ b/net/udpreceive-help.pd @@ -0,0 +1,17 @@ +#N canvas 658 564 478 294 12; +#X obj 222 119 unpack 0 0 0 0; +#X floatatom 222 142 3 0 0 0 - - -; +#X floatatom 257 142 3 0 0 0 - - -; +#X floatatom 293 142 3 0 0 0 - - -; +#X floatatom 329 142 3 0 0 0 - - -; +#X text 179 141 from; +#X obj 107 194 print message; +#X obj 107 91 udpreceive 9997; +#X text 32 16 udpreceive receives bytes over a udp connection.; +#X text 265 235 Martin Peach 2006/04/21; +#X connect 0 0 1 0; +#X connect 0 1 2 0; +#X connect 0 2 3 0; +#X connect 0 3 4 0; +#X connect 7 0 6 0; +#X connect 7 1 0 0; diff --git a/net/udpsend-help.pd b/net/udpsend-help.pd new file mode 100755 index 0000000..629eace --- /dev/null +++ b/net/udpsend-help.pd @@ -0,0 +1,16 @@ +#N canvas 58 443 538 316 12; +#X msg 177 158 disconnect; +#X msg 171 96 connect 127.0.0.1 9997; +#X obj 171 207 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X text 375 97 <--first; +#X msg 67 129 send 0 1 2 3; +#X text 10 38 udpsend sends bytes over a udp connection.; +#X text 10 61 Used in conjunction with packOSC will send OSC over udp +; +#X obj 171 184 udpsend; +#X text 236 259 Martin Peach 2006/04/21; +#X connect 0 0 7 0; +#X connect 1 0 7 0; +#X connect 4 0 7 0; +#X connect 7 0 2 0; diff --git a/net/x_net_tcp_client.c b/net/x_net_tcp_client.c new file mode 100755 index 0000000..366d421 --- /dev/null +++ b/net/x_net_tcp_client.c @@ -0,0 +1,443 @@ +/* x_net_tcp_client.c Martin Peach 20060508, working version 20060512 */ +/* linux version 20060515 */ +/* x_net_tcp_client.c is based on netclient: */ +/* -------------------------- netclient ------------------------------------- */ +/* */ +/* Extended 'netsend', connects to 'netserver'. */ +/* Uses child thread to connect to server. Thus needs pd0.35-test17 or later. */ +/* Written by Olaf Matthes (olaf.matthes@gmx.de) */ +/* Get source at http://www.akustische-kunst.org/puredata/maxlib/ */ +/* */ +/* 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. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* ---------------------------------------------------------------------------- */ +//define DEBUG + +#include "m_pd.h" +#include "s_stuff.h" + +#include +#include +#include +#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#else +#include +#endif + +#define DEFPOLLTIME 20 /* check for input every 20 ms */ + +static t_class *tcpclient_class; +static char objName[] = "tcpclient"; +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet + +typedef struct _tcpclient +{ + t_object x_obj; + t_clock *x_clock; + t_clock *x_poll; + t_outlet *x_msgout; + t_outlet *x_addrout; + t_outlet *x_outconnect; + int x_dump; // 1 = hexdump received bytes + int x_fd; // the socket + char *x_hostname; // address we want to connect to as text + int x_connectstate; // 0 = not connected, 1 = connected + int x_port; // port we're connected to + long x_addr; // address we're connected to as 32bit int + t_atom x_addrbytes[4]; // address we're connected to as 4 bytes + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; // received data as float atoms + unsigned char x_msginbuf[MAX_UDP_RECEIVE]; // received data as bytes + /* multithread stuff */ + pthread_t x_threadid; /* id of child thread */ + pthread_attr_t x_threadattr; /* attributes of child thread */ +} t_tcpclient; + +static void tcpclient_dump(t_tcpclient *x, t_float dump); +static void tcp_client_hexdump(unsigned char *buf, long len); +static void tcpclient_tick(t_tcpclient *x); +static void *tcpclient_child_connect(void *w); +static void tcpclient_connect(t_tcpclient *x, t_symbol *hostname, t_floatarg fportno); +static void tcpclient_disconnect(t_tcpclient *x); +static void tcpclient_send(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv); +static void tcpclient_rcv(t_tcpclient *x); +static void tcpclient_poll(t_tcpclient *x); +static void *tcpclient_new(t_floatarg udpflag); +static void tcpclient_free(t_tcpclient *x); +#ifdef MSW +__declspec(dllexport) +#endif +void tcpclient_setup(void); + +static void tcpclient_dump(t_tcpclient *x, t_float dump) +{ + x->x_dump = (dump == 0)?0:1; +} + +static void tcp_client_hexdump(unsigned char *buf, long len) +{ +#define BYTES_PER_LINE 16 + char hexStr[(3*BYTES_PER_LINE)+1]; + char ascStr[BYTES_PER_LINE+1]; + long i, j, k = 0L; +#ifdef DEBUG + post("tcp_client_hexdump %d", len); +#endif + while (k < len) + { + for (i = j = 0; i < BYTES_PER_LINE; ++i, ++k, j+=3) + { + if (k < len) + { +#ifdef MSW + sprintf_s(&hexStr[j], 4, "%02X ", buf[k]); + sprintf_s(&ascStr[i], 2, "%c", ((buf[k] >= 32) && (buf[k] <= 126))? buf[k]: '.'); +#else + snprintf(&hexStr[j], 4, "%02X ", buf[k]); + snprintf(&ascStr[i], 2, "%c", ((buf[k] >= 32) && (buf[k] <= 126))? buf[k]: '.'); +#endif + } + else + { // the last line +#ifdef MSW + sprintf_s(&hexStr[j], 4, " "); + sprintf_s(&ascStr[i], 2, " "); +#else + snprintf(&hexStr[j], 4, " "); + snprintf(&ascStr[i], 2, " "); +#endif + } + } + post ("%s%s", hexStr, ascStr); + } +} + +static void tcpclient_tick(t_tcpclient *x) +{ + outlet_float(x->x_outconnect, 1); +} + +static void *tcpclient_child_connect(void *w) +{ + t_tcpclient *x = (t_tcpclient*) w; + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + + if (x->x_fd >= 0) + { + error("%s_connect: already connected", objName); + return (x); + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("%s: send socket %d\n", objName, sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpclient: socket"); + return (x); + } + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(x->x_hostname); + if (hp == 0) + { + sys_sockerror("tcpclient: bad host?\n"); + return (x); + } + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((u_short)x->x_port); + + post("%s: connecting socket %d to port %d", objName, sockfd, x->x_port); + /* try to connect */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + sys_sockerror("tcpclient: connecting stream socket"); + sys_closesocket(sockfd); + return (x); + } + x->x_fd = sockfd; + x->x_addr = ntohl(*(long *)hp->h_addr); + /* outlet_float is not threadsafe ! */ + // outlet_float(x->x_obj.ob_outlet, 1); + x->x_connectstate = 1; + /* use callback instead to set outlet */ + clock_delay(x->x_clock, 0); + return (x); +} + +static void tcpclient_connect(t_tcpclient *x, t_symbol *hostname, t_floatarg fportno) +{ + /* we get hostname and port and pass them on + to the child thread that establishes the connection */ + x->x_hostname = hostname->s_name; + x->x_port = fportno; + x->x_connectstate = 0; + /* start child thread */ + if(pthread_create(&x->x_threadid, &x->x_threadattr, tcpclient_child_connect, x) < 0) + post("%s: could not create new thread", objName); +} + +static void tcpclient_disconnect(t_tcpclient *x) +{ + if (x->x_fd >= 0) + { + sys_closesocket(x->x_fd); + x->x_fd = -1; + x->x_connectstate = 0; + outlet_float(x->x_outconnect, 0); + post("%s: disconnected", objName); + } + else post("%s: not connected", objName); +} + +static void tcpclient_send(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv) +{ + static char byte_buf[65536];// arbitrary maximum similar to max IP packet size + int i, d; + unsigned char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; +#ifdef DEBUG + post("%s: argv[%d]: float:%f int:%d delta:%f", objName, i, f, d, e); +#endif + if (e != 0) + { + error("%s_send: item %d (%f) is not an integer", objName, i, f); + return; + } + if ((d < 0) || (d > 255)) + { + error("%s: item %d (%f) is not between 0 and 255", objName, i, f); + return; + } + c = (unsigned char)d; +#ifdef DEBUG + post("%s_send: argv[%d]: %d", objName, i, c); +#endif + byte_buf[i] = c; + } + else + { + error("%s_send: item %d is not a float", objName, i); + return; + } + } + + length = i; + if ((x->x_fd >= 0) && (length > 0)) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(x->x_fd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("%s_send blocked %d msec", objName, + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("tcpclient_send"); + tcpclient_disconnect(x); + break; + } + else + { + sent += result; + bp += result; + } + } + } + else error("%s: not connected", objName); +} + +static void tcpclient_rcv(t_tcpclient *x) +{ + int sockfd = x->x_fd; + int ret; + int i; + fd_set readset; + fd_set exceptset; + struct timeval ztout; + + if(x->x_connectstate) + { + /* check if we can read/write from/to the socket */ + FD_ZERO(&readset); + FD_ZERO(&exceptset); + FD_SET(x->x_fd, &readset ); + FD_SET(x->x_fd, &exceptset ); + + ztout.tv_sec = 0; + ztout.tv_usec = 0; + + ret = select(sockfd+1, &readset, NULL, &exceptset, &ztout); + if(ret < 0) + { + error("%s: unable to read from socket", objName); + sys_closesocket(sockfd); + return; + } + if(FD_ISSET(sockfd, &readset) || FD_ISSET(sockfd, &exceptset)) + { + /* read from server */ + ret = recv(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0); + if(ret > 0) + { +#ifdef DEBUG + x->x_msginbuf[ret] = 0; + post("%s: received %d bytes ", objName, ret); +#endif + if (x->x_dump)tcp_client_hexdump(x->x_msginbuf, ret); + for (i = 0; i < ret; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)x->x_msginbuf[i]; + } + /* find sender's ip address and output it */ + x->x_addrbytes[0].a_w.w_float = (x->x_addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_addr & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + /* send the list out the outlet */ + if (ret > 1) outlet_list(x->x_msgout, &s_list, ret, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } + else + { + if (ret < 0) + { + sys_sockerror("tcpclient: recv"); + tcpclient_disconnect(x); + } + else + { + post("%s: connection closed for socket %d\n", objName, sockfd); + tcpclient_disconnect(x); + } + } + } + } + else post("%s: not connected", objName); +} + +static void tcpclient_poll(t_tcpclient *x) +{ + if(x->x_connectstate) + tcpclient_rcv(x); /* try to read in case we're connected */ + clock_delay(x->x_poll, DEFPOLLTIME); /* see you later */ +} + +static void *tcpclient_new(t_floatarg udpflag) +{ + int i; + + t_tcpclient *x = (t_tcpclient *)pd_new(tcpclient_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); /* received data */ + x->x_addrout = outlet_new(&x->x_obj, &s_list); + x->x_outconnect = outlet_new(&x->x_obj, &s_float); /* connection state */ + x->x_clock = clock_new(x, (t_method)tcpclient_tick); + x->x_poll = clock_new(x, (t_method)tcpclient_poll); + x->x_fd = -1; + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + x->x_addr = 0L; + /* prepare child thread */ + if(pthread_attr_init(&x->x_threadattr) < 0) + post("%s: warning: could not prepare child thread", objName); + if(pthread_attr_setdetachstate(&x->x_threadattr, PTHREAD_CREATE_DETACHED) < 0) + post("%s: warning: could not prepare child thread", objName); + clock_delay(x->x_poll, 0); /* start polling the input */ + return (x); +} + +static void tcpclient_free(t_tcpclient *x) +{ + tcpclient_disconnect(x); + clock_free(x->x_poll); + clock_free(x->x_clock); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpclient_setup(void) +{ + tcpclient_class = class_new(gensym(objName), (t_newmethod)tcpclient_new, + (t_method)tcpclient_free, + sizeof(t_tcpclient), 0, A_DEFFLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_connect, gensym("connect") + , A_SYMBOL, A_FLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_disconnect, gensym("disconnect"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_send, gensym("send"), A_GIMME, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_rcv, gensym("receive"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_rcv, gensym("rcv"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_dump, gensym("dump"), A_FLOAT, 0); +} + +/* end of x_net_tcp.c */ diff --git a/net/x_net_tcp_server.c b/net/x_net_tcp_server.c new file mode 100755 index 0000000..4438215 --- /dev/null +++ b/net/x_net_tcp_server.c @@ -0,0 +1,600 @@ +/* x_net_tcpserver.c Martin Peach 20060511 working version 20060512 */ +/* x_net_tcpserver.c is based on netserver: */ +/* -------------------------- netserver ------------------------------------- */ +/* */ +/* A server for bidirectional communication from within Pd. */ +/* Allows to send back data to specific clients connected to the server. */ +/* Written by Olaf Matthes */ +/* Get source at http://www.akustische-kunst.org/puredata/maxlib */ +/* */ +/* 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. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* ---------------------------------------------------------------------------- */ +//define DEBUG + +#include "m_pd.h" +#include "m_imp.h" +#include "s_stuff.h" + +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#else +//#include +//#include +#include +#endif + +#define MAX_CONNECT 32 /* maximum number of connections */ +#define INBUFSIZE 65536L /* was 4096: size of receiving data buffer */ +#define MAX_UDP_RECEIVE 65536L /* longer than data in maximum UDP packet */ + +/* ----------------------------- tcpserver ------------------------- */ + +static t_class *tcpserver_class; +static t_binbuf *inbinbuf; +static char objName[] = "tcpserver"; + +typedef void (*t_tcpserver_socketnotifier)(void *x); +typedef void (*t_tcpserver_socketreceivefn)(void *x, t_binbuf *b); + +typedef struct _tcpserver +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_connectout; + t_outlet *x_sockout; + t_outlet *x_addrout; + t_symbol *x_host[MAX_CONNECT]; + t_int x_fd[MAX_CONNECT]; + u_long x_addr[MAX_CONNECT]; + t_atom x_addrbytes[4]; + t_int x_sock_fd; + t_int x_connectsocket; + t_int x_nconnections; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_tcpserver; + +typedef struct _tcpserver_socketreceiver +{ + unsigned char *sr_inbuf; + int sr_inhead; + int sr_intail; + void *sr_owner; + t_tcpserver_socketnotifier sr_notifier; + t_tcpserver_socketreceivefn sr_socketreceivefn; +} t_tcpserver_socketreceiver; + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn); +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x); +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd); +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x); +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcp_server_send_bytes(int sockfd, t_tcpserver *x, int argc, t_atom *argv); +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_notify(t_tcpserver *x); +static void tcpserver_connectpoll(t_tcpserver *x); +static void tcpserver_print(t_tcpserver *x); +static void *tcpserver_new(t_floatarg fportno); +static void tcpserver_free(t_tcpserver *x); +#ifdef MSW +__declspec(dllexport) +#endif +void tcpserver_setup(void); + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn) +{ + t_tcpserver_socketreceiver *x = (t_tcpserver_socketreceiver *)getbytes(sizeof(*x)); + if (!x) + { + error("%s_socketreceiver: unable to allocate %d bytes", objName, sizeof(*x)); + } + else + { + x->sr_inhead = x->sr_intail = 0; + x->sr_owner = owner; + x->sr_notifier = notifier; + x->sr_socketreceivefn = socketreceivefn; + if (!(x->sr_inbuf = malloc(INBUFSIZE))) + { + freebytes(x, sizeof(*x)); + x = NULL; + error("%s_socketreceiver: unable to allocate %d bytes", objName, INBUFSIZE); + } + } + return (x); +} + +/* this is in a separately called subroutine so that the buffer isn't + sitting on the stack while the messages are getting passed. */ +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x) +{ + char messbuf[INBUFSIZE]; + char *bp = messbuf; + int indx, i; + int inhead = x->sr_inhead; + int intail = x->sr_intail; + unsigned char c; + t_tcpserver *y = x->sr_owner; + unsigned char *inbuf = x->sr_inbuf; + + if (intail == inhead) return (0); +#ifdef DEBUG + post ("%s_socketreceiver_doread: intail=%d inhead=%d", objName, intail, inhead); +#endif + + for (indx = intail, i = 0; indx != inhead; indx = (indx+1)&(INBUFSIZE-1), ++i) + { + c = *bp++ = inbuf[indx]; + y->x_msgoutbuf[i].a_w.w_float = (float)c; + } + if (i > 1) outlet_list(y->x_msgout, &s_list, i, y->x_msgoutbuf); + else outlet_float(y->x_msgout, y->x_msgoutbuf[0].a_w.w_float); + + // intail = (indx+1)&(INBUFSIZE-1); + x->sr_inhead = inhead; + x->sr_intail = indx;//intail; + return (1); +} + +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd) +{ + char *semi; + int readto = (x->sr_inhead >= x->sr_intail ? INBUFSIZE : x->sr_intail-1); + int ret, i; + t_tcpserver *y = x->sr_owner; + + y->x_sock_fd = fd; + /* the input buffer might be full. If so, drop the whole thing */ + if (readto == x->sr_inhead) + { + post("%s: dropped message", objName); + x->sr_inhead = x->sr_intail = 0; + readto = INBUFSIZE; + } + else + { + ret = recv(fd, x->sr_inbuf + x->sr_inhead, + readto - x->sr_inhead, 0); + if (ret < 0) + { + sys_sockerror("tcpserver: recv"); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else if (ret == 0) + { + post("%s: connection closed on socket %d", objName, fd); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else + { +#ifdef DEBUG + post ("%s_socketreceiver_read: ret = %d", objName, ret); +#endif + x->sr_inhead += ret; + if (x->sr_inhead >= INBUFSIZE) x->sr_inhead = 0; + /* output client's IP and socket no. */ + for(i = 0; i < y->x_nconnections; i++) /* search for corresponding IP */ + { + if(y->x_fd[i] == y->x_sock_fd) + { +// outlet_symbol(x->x_connectionip, x->x_host[i]); + /* find sender's ip address and output it */ + y->x_addrbytes[0].a_w.w_float = (y->x_addr[i] & 0xFF000000)>>24; + y->x_addrbytes[1].a_w.w_float = (y->x_addr[i] & 0x0FF0000)>>16; + y->x_addrbytes[2].a_w.w_float = (y->x_addr[i] & 0x0FF00)>>8; + y->x_addrbytes[3].a_w.w_float = (y->x_addr[i] & 0x0FF); + outlet_list(y->x_addrout, &s_list, 4L, y->x_addrbytes); + break; + } + } + outlet_float(y->x_sockout, y->x_sock_fd); /* the socket number */ + tcpserver_socketreceiver_doread(x); + } + } +} + +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x) +{ + free(x->sr_inbuf); + freebytes(x, sizeof(*x)); +} + +/* ---------------- main tcpserver (send) stuff --------------------- */ + +static void tcp_server_send_bytes(int client, t_tcpserver *x, int argc, t_atom *argv) +{ + static char byte_buf[MAX_UDP_RECEIVE];// arbitrary maximum similar to max IP packet size + int i, d; + unsigned char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + int sockfd = x->x_fd[client]; + + /* process & send data */ + if(sockfd >= 0) + { + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; +#ifdef DEBUG + post("%s: argv[%d]: float:%f int:%d delta:%f", objName, i, f, d, e); +#endif + if (e != 0) + { + error("%s: item %d (%f) is not an integer", objName, i, f); + return; + } + if ((d < 0) || (d > 255)) + { + error("%s: item %d (%f) is not between 0 and 255", objName, i, f); + return; + } + c = (unsigned char)d; /* make sure it doesn't become negative; this only matters for post() */ +#ifdef DEBUG + post("%s: argv[%d]: %d", objName, i, c); +#endif + byte_buf[i] = c; + } + else + { + error("%s: item %d is not a float", objName, i); + return; + } + } + length = i; + if (length > 0) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(sockfd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("%s: send blocked %d msec", objName, + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("tcpserver: send"); + post("%s: could not send data to client %d", objName, client); + break; + } + else + { + sent += result; + bp += result; + } + } + } + } + else post("%s: not a valid socket number (%d)", objName, sockfd); +} + +/* send message to client using socket number */ +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int i, sockfd; + int client = -1; + + if(x->x_nconnections < 0) + { + post("%s: no clients connected", objName); + return; + } + if(argc < 2) + { + post("%s: nothing to send", objName); + return; + } + /* get socket number of connection (first element in list) */ + if(argv[0].a_type == A_FLOAT) + { + sockfd = atom_getfloatarg(0, argc, argv); + for(i = 0; i < x->x_nconnections; i++) /* check if connection exists */ + { + if(x->x_fd[i] == sockfd) + { + client = i; /* the client we're sending to */ + break; + } + } + if(client == -1) + { + post("%s: no connection on socket %d", objName, sockfd); + return; + } + } + else + { + post("%s: no socket specified", objName); + return; + } + tcp_server_send_bytes(client, x, argc-1, &argv[1]); +} + +/* send message to client using client number + note that the client numbers might change in case a client disconnects! */ +/* clients start at 1 but our index starts at 0 */ +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int sockfd, client; + + if(x->x_nconnections < 0) + { + post("%s: no clients connected", objName); + return; + } + if(argc < 2) + { + post("%s: nothing to send", objName); + return; + } + /* get number of client (first element in list) */ + if(argv[0].a_type == A_FLOAT) + client = atom_getfloatarg(0, argc, argv); + else + { + post("%s: no client specified", objName); + return; + } + if (!((client > 0) && (client < MAX_CONNECT))) + { + post("%s: client %d out of range [1..%d]", objName, client, MAX_CONNECT); + return; + } + --client;/* zero based index*/ + tcp_server_send_bytes(client, x, argc-1, &argv[1]); +} + +/* broadcasts a message to all connected clients */ +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int client; + + /* enumerate through the clients and send each the message */ + for(client = 0; client < x->x_nconnections; client++) /* check if connection exists */ + { + if(x->x_fd[client] >= 0) + { /* socket exists for this client */ + tcp_server_send_bytes(client, x, argc, argv); + break; + } + } +} + +/* ---------------- main tcpserver (receive) stuff --------------------- */ + +static void tcpserver_notify(t_tcpserver *x) +{ + int i, k; + + /* remove connection from list */ + for(i = 0; i < x->x_nconnections; i++) + { + if(x->x_fd[i] == x->x_sock_fd) + { + x->x_nconnections--; + post("%s: \"%s\" removed from list of clients", objName, x->x_host[i]->s_name); + x->x_host[i] = NULL; /* delete entry */ + x->x_fd[i] = -1; + /* rearrange list now: move entries to close the gap */ + for(k = i; k < x->x_nconnections; k++) + { + x->x_host[k] = x->x_host[k + 1]; + x->x_fd[k] = x->x_fd[k + 1]; + } + } + } + outlet_float(x->x_connectout, x->x_nconnections); +} + +static void tcpserver_connectpoll(t_tcpserver *x) +{ + struct sockaddr_in incomer_address; + int sockaddrl = (int) sizeof( struct sockaddr ); + int fd = accept(x->x_connectsocket, (struct sockaddr*)&incomer_address, &sockaddrl); + int i; + + if (fd < 0) post("%s: accept failed", objName); + else + { + t_tcpserver_socketreceiver *y = tcpserver_socketreceiver_new((void *)x, + (t_tcpserver_socketnotifier)tcpserver_notify, NULL);/* MP tcpserver_doit isn't used I think...*/ + if (!y) + { +#ifdef MSW + closesocket(fd); +#else + close(fd); +#endif + return; + } + sys_addpollfn(fd, (t_fdpollfn)tcpserver_socketreceiver_read, y); + x->x_nconnections++; + i = x->x_nconnections - 1; + x->x_host[i] = gensym(inet_ntoa(incomer_address.sin_addr)); + x->x_fd[i] = fd; + post("%s: accepted connection from %s on socket %d", + objName, x->x_host[i]->s_name, x->x_fd[i]); + outlet_float(x->x_connectout, x->x_nconnections); + outlet_float(x->x_sockout, x->x_fd[i]); /* the socket number */ + x->x_addr[i] = ntohl(incomer_address.sin_addr.s_addr); + x->x_addrbytes[0].a_w.w_float = (x->x_addr[i] & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_addr[i] & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_addr[i] & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_addr[i] & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + } +} + +static void tcpserver_print(t_tcpserver *x) +{ + int i; + + if(x->x_nconnections > 0) + { + post("%s: %d open connections:", objName, x->x_nconnections); + for(i = 0; i < x->x_nconnections; i++) + { + post(" \"%s\" on socket %d", + x->x_host[i]->s_name, x->x_fd[i]); + } + } + else post("%s: no open connections", objName); +} + +static void *tcpserver_new(t_floatarg fportno) +{ + t_tcpserver *x; + int i; + struct sockaddr_in server; + int sockfd, portno = fportno; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("%s: receive socket %d", objName, sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpserver: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; +#ifdef IRIX + /* this seems to work only in IRIX but is unnecessary in + Linux. Not sure what NT needs in place of this. */ + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) + post("setsockopt failed\n"); +#endif + /* assign server port number */ + server.sin_port = htons((u_short)portno); + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("tcpserver: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_tcpserver *)pd_new(tcpserver_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); /* 1st outlet for received data */ + /* streaming protocol */ + if (listen(sockfd, 5) < 0) + { + sys_sockerror("tcpserver: listen"); + sys_closesocket(sockfd); + sockfd = -1; + } + else + { + sys_addpollfn(sockfd, (t_fdpollfn)tcpserver_connectpoll, x); + x->x_connectout = outlet_new(&x->x_obj, &s_float); /* 2nd outlet for number of connected clients */ + x->x_sockout = outlet_new(&x->x_obj, &s_float); /* 3rd outlet for socket number of current client */ + x->x_addrout = outlet_new(&x->x_obj, &s_list); /* 4th outlet for ip address of current client */ + inbinbuf = binbuf_new(); + } + x->x_connectsocket = sockfd; + x->x_nconnections = 0; + for(i = 0; i < MAX_CONNECT; i++) x->x_fd[i] = -1; + /* prepare to convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + + return (x); +} + +static void tcpserver_free(t_tcpserver *x) +{ + int i; + + for(i = 0; i < x->x_nconnections; i++) + { + sys_rmpollfn(x->x_fd[i]); + sys_closesocket(x->x_fd[i]); + } + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } + binbuf_free(inbinbuf); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpserver_setup(void) +{ + tcpserver_class = class_new(gensym(objName),(t_newmethod)tcpserver_new, (t_method)tcpserver_free, + sizeof(t_tcpserver), 0, A_DEFFLOAT, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_print, gensym("print"), 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_send, gensym("send"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_client_send, gensym("client"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_broadcast, gensym("broadcast"), A_GIMME, 0); +} diff --git a/net/x_net_tcpclient.c b/net/x_net_tcpclient.c new file mode 100755 index 0000000..366d421 --- /dev/null +++ b/net/x_net_tcpclient.c @@ -0,0 +1,443 @@ +/* x_net_tcp_client.c Martin Peach 20060508, working version 20060512 */ +/* linux version 20060515 */ +/* x_net_tcp_client.c is based on netclient: */ +/* -------------------------- netclient ------------------------------------- */ +/* */ +/* Extended 'netsend', connects to 'netserver'. */ +/* Uses child thread to connect to server. Thus needs pd0.35-test17 or later. */ +/* Written by Olaf Matthes (olaf.matthes@gmx.de) */ +/* Get source at http://www.akustische-kunst.org/puredata/maxlib/ */ +/* */ +/* 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. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* ---------------------------------------------------------------------------- */ +//define DEBUG + +#include "m_pd.h" +#include "s_stuff.h" + +#include +#include +#include +#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#else +#include +#endif + +#define DEFPOLLTIME 20 /* check for input every 20 ms */ + +static t_class *tcpclient_class; +static char objName[] = "tcpclient"; +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet + +typedef struct _tcpclient +{ + t_object x_obj; + t_clock *x_clock; + t_clock *x_poll; + t_outlet *x_msgout; + t_outlet *x_addrout; + t_outlet *x_outconnect; + int x_dump; // 1 = hexdump received bytes + int x_fd; // the socket + char *x_hostname; // address we want to connect to as text + int x_connectstate; // 0 = not connected, 1 = connected + int x_port; // port we're connected to + long x_addr; // address we're connected to as 32bit int + t_atom x_addrbytes[4]; // address we're connected to as 4 bytes + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; // received data as float atoms + unsigned char x_msginbuf[MAX_UDP_RECEIVE]; // received data as bytes + /* multithread stuff */ + pthread_t x_threadid; /* id of child thread */ + pthread_attr_t x_threadattr; /* attributes of child thread */ +} t_tcpclient; + +static void tcpclient_dump(t_tcpclient *x, t_float dump); +static void tcp_client_hexdump(unsigned char *buf, long len); +static void tcpclient_tick(t_tcpclient *x); +static void *tcpclient_child_connect(void *w); +static void tcpclient_connect(t_tcpclient *x, t_symbol *hostname, t_floatarg fportno); +static void tcpclient_disconnect(t_tcpclient *x); +static void tcpclient_send(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv); +static void tcpclient_rcv(t_tcpclient *x); +static void tcpclient_poll(t_tcpclient *x); +static void *tcpclient_new(t_floatarg udpflag); +static void tcpclient_free(t_tcpclient *x); +#ifdef MSW +__declspec(dllexport) +#endif +void tcpclient_setup(void); + +static void tcpclient_dump(t_tcpclient *x, t_float dump) +{ + x->x_dump = (dump == 0)?0:1; +} + +static void tcp_client_hexdump(unsigned char *buf, long len) +{ +#define BYTES_PER_LINE 16 + char hexStr[(3*BYTES_PER_LINE)+1]; + char ascStr[BYTES_PER_LINE+1]; + long i, j, k = 0L; +#ifdef DEBUG + post("tcp_client_hexdump %d", len); +#endif + while (k < len) + { + for (i = j = 0; i < BYTES_PER_LINE; ++i, ++k, j+=3) + { + if (k < len) + { +#ifdef MSW + sprintf_s(&hexStr[j], 4, "%02X ", buf[k]); + sprintf_s(&ascStr[i], 2, "%c", ((buf[k] >= 32) && (buf[k] <= 126))? buf[k]: '.'); +#else + snprintf(&hexStr[j], 4, "%02X ", buf[k]); + snprintf(&ascStr[i], 2, "%c", ((buf[k] >= 32) && (buf[k] <= 126))? buf[k]: '.'); +#endif + } + else + { // the last line +#ifdef MSW + sprintf_s(&hexStr[j], 4, " "); + sprintf_s(&ascStr[i], 2, " "); +#else + snprintf(&hexStr[j], 4, " "); + snprintf(&ascStr[i], 2, " "); +#endif + } + } + post ("%s%s", hexStr, ascStr); + } +} + +static void tcpclient_tick(t_tcpclient *x) +{ + outlet_float(x->x_outconnect, 1); +} + +static void *tcpclient_child_connect(void *w) +{ + t_tcpclient *x = (t_tcpclient*) w; + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + + if (x->x_fd >= 0) + { + error("%s_connect: already connected", objName); + return (x); + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("%s: send socket %d\n", objName, sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpclient: socket"); + return (x); + } + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(x->x_hostname); + if (hp == 0) + { + sys_sockerror("tcpclient: bad host?\n"); + return (x); + } + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((u_short)x->x_port); + + post("%s: connecting socket %d to port %d", objName, sockfd, x->x_port); + /* try to connect */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + sys_sockerror("tcpclient: connecting stream socket"); + sys_closesocket(sockfd); + return (x); + } + x->x_fd = sockfd; + x->x_addr = ntohl(*(long *)hp->h_addr); + /* outlet_float is not threadsafe ! */ + // outlet_float(x->x_obj.ob_outlet, 1); + x->x_connectstate = 1; + /* use callback instead to set outlet */ + clock_delay(x->x_clock, 0); + return (x); +} + +static void tcpclient_connect(t_tcpclient *x, t_symbol *hostname, t_floatarg fportno) +{ + /* we get hostname and port and pass them on + to the child thread that establishes the connection */ + x->x_hostname = hostname->s_name; + x->x_port = fportno; + x->x_connectstate = 0; + /* start child thread */ + if(pthread_create(&x->x_threadid, &x->x_threadattr, tcpclient_child_connect, x) < 0) + post("%s: could not create new thread", objName); +} + +static void tcpclient_disconnect(t_tcpclient *x) +{ + if (x->x_fd >= 0) + { + sys_closesocket(x->x_fd); + x->x_fd = -1; + x->x_connectstate = 0; + outlet_float(x->x_outconnect, 0); + post("%s: disconnected", objName); + } + else post("%s: not connected", objName); +} + +static void tcpclient_send(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv) +{ + static char byte_buf[65536];// arbitrary maximum similar to max IP packet size + int i, d; + unsigned char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; +#ifdef DEBUG + post("%s: argv[%d]: float:%f int:%d delta:%f", objName, i, f, d, e); +#endif + if (e != 0) + { + error("%s_send: item %d (%f) is not an integer", objName, i, f); + return; + } + if ((d < 0) || (d > 255)) + { + error("%s: item %d (%f) is not between 0 and 255", objName, i, f); + return; + } + c = (unsigned char)d; +#ifdef DEBUG + post("%s_send: argv[%d]: %d", objName, i, c); +#endif + byte_buf[i] = c; + } + else + { + error("%s_send: item %d is not a float", objName, i); + return; + } + } + + length = i; + if ((x->x_fd >= 0) && (length > 0)) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(x->x_fd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("%s_send blocked %d msec", objName, + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("tcpclient_send"); + tcpclient_disconnect(x); + break; + } + else + { + sent += result; + bp += result; + } + } + } + else error("%s: not connected", objName); +} + +static void tcpclient_rcv(t_tcpclient *x) +{ + int sockfd = x->x_fd; + int ret; + int i; + fd_set readset; + fd_set exceptset; + struct timeval ztout; + + if(x->x_connectstate) + { + /* check if we can read/write from/to the socket */ + FD_ZERO(&readset); + FD_ZERO(&exceptset); + FD_SET(x->x_fd, &readset ); + FD_SET(x->x_fd, &exceptset ); + + ztout.tv_sec = 0; + ztout.tv_usec = 0; + + ret = select(sockfd+1, &readset, NULL, &exceptset, &ztout); + if(ret < 0) + { + error("%s: unable to read from socket", objName); + sys_closesocket(sockfd); + return; + } + if(FD_ISSET(sockfd, &readset) || FD_ISSET(sockfd, &exceptset)) + { + /* read from server */ + ret = recv(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0); + if(ret > 0) + { +#ifdef DEBUG + x->x_msginbuf[ret] = 0; + post("%s: received %d bytes ", objName, ret); +#endif + if (x->x_dump)tcp_client_hexdump(x->x_msginbuf, ret); + for (i = 0; i < ret; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)x->x_msginbuf[i]; + } + /* find sender's ip address and output it */ + x->x_addrbytes[0].a_w.w_float = (x->x_addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_addr & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + /* send the list out the outlet */ + if (ret > 1) outlet_list(x->x_msgout, &s_list, ret, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } + else + { + if (ret < 0) + { + sys_sockerror("tcpclient: recv"); + tcpclient_disconnect(x); + } + else + { + post("%s: connection closed for socket %d\n", objName, sockfd); + tcpclient_disconnect(x); + } + } + } + } + else post("%s: not connected", objName); +} + +static void tcpclient_poll(t_tcpclient *x) +{ + if(x->x_connectstate) + tcpclient_rcv(x); /* try to read in case we're connected */ + clock_delay(x->x_poll, DEFPOLLTIME); /* see you later */ +} + +static void *tcpclient_new(t_floatarg udpflag) +{ + int i; + + t_tcpclient *x = (t_tcpclient *)pd_new(tcpclient_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); /* received data */ + x->x_addrout = outlet_new(&x->x_obj, &s_list); + x->x_outconnect = outlet_new(&x->x_obj, &s_float); /* connection state */ + x->x_clock = clock_new(x, (t_method)tcpclient_tick); + x->x_poll = clock_new(x, (t_method)tcpclient_poll); + x->x_fd = -1; + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + x->x_addr = 0L; + /* prepare child thread */ + if(pthread_attr_init(&x->x_threadattr) < 0) + post("%s: warning: could not prepare child thread", objName); + if(pthread_attr_setdetachstate(&x->x_threadattr, PTHREAD_CREATE_DETACHED) < 0) + post("%s: warning: could not prepare child thread", objName); + clock_delay(x->x_poll, 0); /* start polling the input */ + return (x); +} + +static void tcpclient_free(t_tcpclient *x) +{ + tcpclient_disconnect(x); + clock_free(x->x_poll); + clock_free(x->x_clock); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpclient_setup(void) +{ + tcpclient_class = class_new(gensym(objName), (t_newmethod)tcpclient_new, + (t_method)tcpclient_free, + sizeof(t_tcpclient), 0, A_DEFFLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_connect, gensym("connect") + , A_SYMBOL, A_FLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_disconnect, gensym("disconnect"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_send, gensym("send"), A_GIMME, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_rcv, gensym("receive"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_rcv, gensym("rcv"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_dump, gensym("dump"), A_FLOAT, 0); +} + +/* end of x_net_tcp.c */ diff --git a/net/x_net_tcpreceive.c b/net/x_net_tcpreceive.c new file mode 100755 index 0000000..d5884e1 --- /dev/null +++ b/net/x_net_tcpreceive.c @@ -0,0 +1,313 @@ +/* x_net_tcpreceive.c 20060424. Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "m_pd.h" +#include "s_stuff.h" + +#ifdef MSW +#include +#include /* for socklen_t */ +#else +#include +#include +#include +#include +#include +#endif + + +/* ----------------------------- tcpreceive ------------------------- */ + +static t_class *tcpreceive_class; + +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet +#define MAX_CONNECTIONS 128 // this is going to cause trouble down the line...:( + +typedef struct _tcpconnection +{ + long addr; + int socket; +} t_tcpconnection; + +typedef struct _tcpreceive +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_addrout; + t_outlet *x_connectout; + int x_connectsocket; + int x_nconnections; + t_tcpconnection x_connection[MAX_CONNECTIONS]; + t_atom x_addrbytes[4]; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_tcpreceive; + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpreceive_setup(void); +static void tcpreceive_free(t_tcpreceive *x); +static void *tcpreceive_new(t_floatarg fportno); +static void tcpreceive_read(t_tcpreceive *x, int sockfd); +static void tcpreceive_connectpoll(t_tcpreceive *x); +static int tcpreceive_addconnection(t_tcpreceive * x, int fd, long addr); +static int tcpreceive_removeconnection(t_tcpreceive * x, int fd); +static void tcpreceive_closeall(t_tcpreceive *x); +static long tcpreceive_getconnection(t_tcpreceive * x, int fd); + +static void tcpreceive_read(t_tcpreceive *x, int sockfd) +{ + int i, read = 0; + long addr; + +// read = recvfrom(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0, (struct sockaddr *)&from, &fromlen); + read = recv(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0); +#ifdef DEBUG + post("tcpreceive_read: read %lu x->x_connectsocket = %d", + read, x->x_connectsocket); +#endif + if (read < 0) + { + sys_sockerror("tcpreceive_read: recv"); + sys_rmpollfn(sockfd); + sys_closesocket(sockfd); + tcpreceive_removeconnection(x, sockfd); + outlet_float(x->x_connectout, --x->x_nconnections); + } + else if (read == 0) + { + post("tcpreceive: EOF on socket %d\n", sockfd); + sys_rmpollfn(sockfd); + sys_closesocket(sockfd); + tcpreceive_removeconnection(x, sockfd); + outlet_float(x->x_connectout, --x->x_nconnections); + } + else if (read > 0) + { + for (i = 0; i < read; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)x->x_msginbuf[i]; + } + /* find sender's ip address and output it */ + addr = tcpreceive_getconnection(x, sockfd); + x->x_addrbytes[0].a_w.w_float = (addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (addr & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + /* send the list out the outlet */ + if (read > 1) outlet_list(x->x_msgout, &s_list, read, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } +} + +static void *tcpreceive_new(t_floatarg fportno) +{ + t_tcpreceive *x; + struct sockaddr_in server; + int sockfd, portno = fportno; + int intarg, i; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("tcpreceive_new: socket %d port %d", sockfd, portno); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpreceive: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* ask OS to allow another Pd to repoen this port after we close it. */ + intarg = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (char *)&intarg, sizeof(intarg)) < 0) + post("tcpreceive: setsockopt (SO_REUSEADDR) failed"); + /* Stream (TCP) sockets are set NODELAY */ + intarg = 1; + if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, + (char *)&intarg, sizeof(intarg)) < 0) + post("setsockopt (TCP_NODELAY) failed\n"); + + /* assign server port number */ + server.sin_port = htons((u_short)portno); + + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("tcpreceive: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_tcpreceive *)pd_new(tcpreceive_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); + x->x_addrout = outlet_new(&x->x_obj, &s_list); + x->x_connectout = outlet_new(&x->x_obj, &s_float); + /* clear the connection list */ + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + x->x_connection[i].socket = -1; + x->x_connection[i].addr = 0L; + } + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + + /* streaming protocol */ + if (listen(sockfd, 5) < 0) + { + sys_sockerror("tcpreceive: listen"); + sys_closesocket(sockfd); + sockfd = -1; + } + else + { + sys_addpollfn(sockfd, (t_fdpollfn)tcpreceive_connectpoll, x); + } + x->x_connectsocket = sockfd; + x->x_nconnections = 0; + +//udp version... sys_addpollfn(x->x_connectsocket, (t_fdpollfn)tcpreceive_read, x); + return (x); +} + +/* tcpreceive_connectpoll checks for incoming connection requests on the original socket */ +/* a new socket is assigned */ +static void tcpreceive_connectpoll(t_tcpreceive *x) +{ + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + long addr; + int fd; + + fd = accept(x->x_connectsocket, (struct sockaddr *)&from, &fromlen); + if (fd < 0) post("tcpreceive: accept failed"); + else + { + // t_socketreceiver *y = socketreceiver_new((void *)x, + // (t_socketnotifier)tcpreceive_notify, + // 0, 0); + + /* get the sender's ip */ + addr = ntohl(from.sin_addr.s_addr); + if (tcpreceive_addconnection(x, fd, addr)) + { + sys_addpollfn(fd, (t_fdpollfn)tcpreceive_read, x); + outlet_float(x->x_connectout, ++x->x_nconnections); + x->x_addrbytes[0].a_w.w_float = (addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (addr & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + } + else + { + error ("tcpreceive: Too many connections"); + sys_closesocket(fd); + } + } +} + +/* tcpreceive_addconnection tries to add the socket fd to the list */ +/* returns 1 on success, else 0 */ +static int tcpreceive_addconnection(t_tcpreceive *x, int fd, long addr) +{ + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + if (x->x_connection[i].socket == -1) + { + x->x_connection[i].socket = fd; + x->x_connection[i].addr = addr; + return 1; + } + } + return 0; +} + +/* tcpreceive_closeall closes all open sockets and deletes them from the list */ +static void tcpreceive_closeall(t_tcpreceive *x) +{ + int i; + + for (i = 0; ((i < MAX_CONNECTIONS) && (x->x_nconnections > 0)); ++i) + { + if (x->x_connection[i].socket != -1) + { + post ("tcpreceive: closing socket %d", x->x_connection[i].socket); + sys_rmpollfn(x->x_connection[i].socket); + sys_closesocket(x->x_connection[i].socket); + x->x_connection[i].socket = -1; + x->x_connection[i].addr = 0L; + outlet_float(x->x_connectout, --x->x_nconnections); + } + } +} + +/* tcpreceive_removeconnection tries to delete the socket fd from the list */ +/* returns 1 on success, else 0 */ +static int tcpreceive_removeconnection(t_tcpreceive *x, int fd) +{ + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + if (x->x_connection[i].socket == fd) + { + x->x_connection[i].socket = -1; + x->x_connection[i].addr = 0L; + return 1; + } + } + return 0; +} + +/* tcpreceive_getconnection tries to find the socket fd in the list */ +/* returns addr on success, else 0 */ +static long tcpreceive_getconnection(t_tcpreceive *x, int fd) +{ + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + if (x->x_connection[i].socket == fd) + return x->x_connection[i].addr; + } + return 0; +} + +static void tcpreceive_free(t_tcpreceive *x) +{ /* is this ever called? */ + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } + tcpreceive_closeall(x); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpreceive_setup(void) +{ + tcpreceive_class = class_new(gensym("tcpreceive"), + (t_newmethod)tcpreceive_new, (t_method)tcpreceive_free, + sizeof(t_tcpreceive), CLASS_NOINLET, A_DEFFLOAT, 0); +} + +/* end x_net_tcpreceive.c */ + diff --git a/net/x_net_tcpsend.c b/net/x_net_tcpsend.c new file mode 100755 index 0000000..1a9937b --- /dev/null +++ b/net/x_net_tcpsend.c @@ -0,0 +1,221 @@ +/* x_net_tcpsend.c 20060424 Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* network */ + +#include "m_pd.h" +#include "s_stuff.h" + +#ifdef MSW +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +static t_class *tcpsend_class; + +typedef struct _tcpsend +{ + t_object x_obj; + int x_fd; +} t_tcpsend; + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpsend_setup(void); +static void tcpsend_free(t_tcpsend *x); +static void tcpsend_send(t_tcpsend *x, t_symbol *s, int argc, t_atom *argv); +static void tcpsend_disconnect(t_tcpsend *x); +static void tcpsend_connect(t_tcpsend *x, t_symbol *hostname, t_floatarg fportno); +static void *tcpsend_new(void); + +static void *tcpsend_new(void) +{ + t_tcpsend *x = (t_tcpsend *)pd_new(tcpsend_class); + outlet_new(&x->x_obj, &s_float); + x->x_fd = -1; + return (x); +} + +static void tcpsend_connect(t_tcpsend *x, t_symbol *hostname, + t_floatarg fportno) +{ + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + int portno = fportno; + int intarg; + + if (x->x_fd >= 0) + { + error("tcpsend: already connected"); + return; + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + fprintf(stderr, "tcpsend_connect: send socket %d\n", sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpsend: socket"); + return; + } + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(hostname->s_name); + if (hp == 0) + { + post("tcpsend: bad host?\n"); + return; + } + /* for stream (TCP) sockets, specify "nodelay" */ + intarg = 1; + if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, + (char *)&intarg, sizeof(intarg)) < 0) + post("tcpsend: setsockopt (TCP_NODELAY) failed\n"); + + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((u_short)portno); + + post("tcpsend: connecting to port %d", portno); + /* try to connect. */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + sys_sockerror("tcpsend: connecting stream socket"); + sys_closesocket(sockfd); + return; + } + x->x_fd = sockfd; + outlet_float(x->x_obj.ob_outlet, 1); +} + +static void tcpsend_disconnect(t_tcpsend *x) +{ + if (x->x_fd >= 0) + { + sys_closesocket(x->x_fd); + x->x_fd = -1; + outlet_float(x->x_obj.ob_outlet, 0); + post("tcpsend: disconnected"); + } +} + +static void tcpsend_send(t_tcpsend *x, t_symbol *s, int argc, t_atom *argv) +{ + static char byte_buf[65536];// arbitrary maximum similar to max IP packet size + int i, d; + char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; + if (e != 0) + { + error("tcpsend_send: item %d (%f) is not an integer", i, f); + return; + } + c = (unsigned char)d; + if (c != d) + { + error("tcpsend_send: item %d (%f) is not between 0 and 255", i, f); + return; + } +#ifdef DEBUG + post("tcpsend_send: argv[%d]: %d", i, c); +#endif + byte_buf[i] = c; + } + else + { + error("tcpsend_send: item %d is not a float", i); + return; + } + } + + length = i; + if ((x->x_fd >= 0) && (length > 0)) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(x->x_fd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("tcpsend blocked %d msec", + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("tcpsend"); + tcpsend_disconnect(x); + break; + } + else + { + sent += result; + bp += result; + } + } + } + else error("tcpsend: not connected"); +} + +static void tcpsend_free(t_tcpsend *x) +{ + tcpsend_disconnect(x); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpsend_setup(void) +{ + tcpsend_class = class_new(gensym("tcpsend"), (t_newmethod)tcpsend_new, + (t_method)tcpsend_free, + sizeof(t_tcpsend), 0, 0); + class_addmethod(tcpsend_class, (t_method)tcpsend_connect, + gensym("connect"), A_SYMBOL, A_FLOAT, 0); + class_addmethod(tcpsend_class, (t_method)tcpsend_disconnect, + gensym("disconnect"), 0); + class_addmethod(tcpsend_class, (t_method)tcpsend_send, gensym("send"), + A_GIMME, 0); +} + +/* end x_net_tcpsend.c*/ diff --git a/net/x_net_tcpserver.c b/net/x_net_tcpserver.c new file mode 100755 index 0000000..f6526c0 --- /dev/null +++ b/net/x_net_tcpserver.c @@ -0,0 +1,619 @@ +/* x_net_tcpserver.c Martin Peach 20060511 working version 20060512 */ +/* 20060515 works on linux too... */ +/* x_net_tcpserver.c is based on netserver: */ +/* -------------------------- netserver ------------------------------------- */ +/* */ +/* A server for bidirectional communication from within Pd. */ +/* Allows to send back data to specific clients connected to the server. */ +/* Written by Olaf Matthes */ +/* Get source at http://www.akustische-kunst.org/puredata/maxlib */ +/* */ +/* 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. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* ---------------------------------------------------------------------------- */ +//define DEBUG + +#include "m_pd.h" +#include "m_imp.h" +#include "s_stuff.h" + +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#else +//#include +//#include +#include +#endif + +#define MAX_CONNECT 32 /* maximum number of connections */ +#define INBUFSIZE 65536L /* was 4096: size of receiving data buffer */ +#define MAX_UDP_RECEIVE 65536L /* longer than data in maximum UDP packet */ + +/* ----------------------------- tcpserver ------------------------- */ + +static t_class *tcpserver_class; +static t_binbuf *inbinbuf; +static char objName[] = "tcpserver"; + +typedef void (*t_tcpserver_socketnotifier)(void *x); +typedef void (*t_tcpserver_socketreceivefn)(void *x, t_binbuf *b); + +typedef struct _tcpserver_socketreceiver +{ + unsigned char *sr_inbuf; + int sr_inhead; + int sr_intail; + void *sr_owner; + t_tcpserver_socketnotifier sr_notifier; + t_tcpserver_socketreceivefn sr_socketreceivefn; +} t_tcpserver_socketreceiver; + +typedef struct _tcpserver +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_connectout; + t_outlet *x_sockout; + t_outlet *x_addrout; + t_symbol *x_host[MAX_CONNECT]; + t_int x_fd[MAX_CONNECT]; + u_long x_addr[MAX_CONNECT]; + t_tcpserver_socketreceiver *x_sr[MAX_CONNECT]; + t_atom x_addrbytes[4]; + t_int x_sock_fd; + t_int x_connectsocket; + t_int x_nconnections; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_tcpserver; + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn); +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x); +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd); +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x); +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcp_server_send_bytes(int sockfd, t_tcpserver *x, int argc, t_atom *argv); +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_notify(t_tcpserver *x); +static void tcpserver_connectpoll(t_tcpserver *x); +static void tcpserver_print(t_tcpserver *x); +static void *tcpserver_new(t_floatarg fportno); +static void tcpserver_free(t_tcpserver *x); +#ifdef MSW +__declspec(dllexport) +#endif +void tcpserver_setup(void); + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn) +{ + t_tcpserver_socketreceiver *x = (t_tcpserver_socketreceiver *)getbytes(sizeof(*x)); + if (!x) + { + error("%s_socketreceiver: unable to allocate %d bytes", objName, sizeof(*x)); + } + else + { + x->sr_inhead = x->sr_intail = 0; + x->sr_owner = owner; + x->sr_notifier = notifier; + x->sr_socketreceivefn = socketreceivefn; + if (!(x->sr_inbuf = malloc(INBUFSIZE))) + { + freebytes(x, sizeof(*x)); + x = NULL; + error("%s_socketreceiver: unable to allocate %d bytes", objName, INBUFSIZE); + } + } + return (x); +} + +/* this is in a separately called subroutine so that the buffer isn't + sitting on the stack while the messages are getting passed. */ +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x) +{ + char messbuf[INBUFSIZE]; + char *bp = messbuf; + int indx, i; + int inhead = x->sr_inhead; + int intail = x->sr_intail; + unsigned char c; + t_tcpserver *y = x->sr_owner; + unsigned char *inbuf = x->sr_inbuf; + + if (intail == inhead) return (0); +#ifdef DEBUG + post ("%s_socketreceiver_doread: intail=%d inhead=%d", objName, intail, inhead); +#endif + + for (indx = intail, i = 0; indx != inhead; indx = (indx+1)&(INBUFSIZE-1), ++i) + { + c = *bp++ = inbuf[indx]; + y->x_msgoutbuf[i].a_w.w_float = (float)c; + } + if (i > 1) outlet_list(y->x_msgout, &s_list, i, y->x_msgoutbuf); + else outlet_float(y->x_msgout, y->x_msgoutbuf[0].a_w.w_float); + + // intail = (indx+1)&(INBUFSIZE-1); + x->sr_inhead = inhead; + x->sr_intail = indx;//intail; + return (1); +} + +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd) +{ + int readto = (x->sr_inhead >= x->sr_intail ? INBUFSIZE : x->sr_intail-1); + int ret, i; + t_tcpserver *y = x->sr_owner; + + y->x_sock_fd = fd; + /* the input buffer might be full. If so, drop the whole thing */ + if (readto == x->sr_inhead) + { + post("%s: dropped message", objName); + x->sr_inhead = x->sr_intail = 0; + readto = INBUFSIZE; + } + else + { + ret = recv(fd, x->sr_inbuf + x->sr_inhead, + readto - x->sr_inhead, 0); + if (ret < 0) + { + sys_sockerror("tcpserver: recv"); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else if (ret == 0) + { + post("%s: connection closed on socket %d", objName, fd); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else + { +#ifdef DEBUG + post ("%s_socketreceiver_read: ret = %d", objName, ret); +#endif + x->sr_inhead += ret; + if (x->sr_inhead >= INBUFSIZE) x->sr_inhead = 0; + /* output client's IP and socket no. */ + for(i = 0; i < y->x_nconnections; i++) /* search for corresponding IP */ + { + if(y->x_fd[i] == y->x_sock_fd) + { +// outlet_symbol(x->x_connectionip, x->x_host[i]); + /* find sender's ip address and output it */ + y->x_addrbytes[0].a_w.w_float = (y->x_addr[i] & 0xFF000000)>>24; + y->x_addrbytes[1].a_w.w_float = (y->x_addr[i] & 0x0FF0000)>>16; + y->x_addrbytes[2].a_w.w_float = (y->x_addr[i] & 0x0FF00)>>8; + y->x_addrbytes[3].a_w.w_float = (y->x_addr[i] & 0x0FF); + outlet_list(y->x_addrout, &s_list, 4L, y->x_addrbytes); + break; + } + } + outlet_float(y->x_sockout, y->x_sock_fd); /* the socket number */ + tcpserver_socketreceiver_doread(x); + } + } +} + +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x) +{ + if (x != NULL) + { + free(x->sr_inbuf); + freebytes(x, sizeof(*x)); + } +} + +/* ---------------- main tcpserver (send) stuff --------------------- */ + +static void tcp_server_send_bytes(int client, t_tcpserver *x, int argc, t_atom *argv) +{ + static char byte_buf[MAX_UDP_RECEIVE];// arbitrary maximum similar to max IP packet size + int i, d; + unsigned char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + int sockfd = x->x_fd[client]; + + /* process & send data */ + if(sockfd >= 0) + { + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; +#ifdef DEBUG + post("%s: argv[%d]: float:%f int:%d delta:%f", objName, i, f, d, e); +#endif + if (e != 0) + { + error("%s: item %d (%f) is not an integer", objName, i, f); + return; + } + if ((d < 0) || (d > 255)) + { + error("%s: item %d (%f) is not between 0 and 255", objName, i, f); + return; + } + c = (unsigned char)d; /* make sure it doesn't become negative; this only matters for post() */ +#ifdef DEBUG + post("%s: argv[%d]: %d", objName, i, c); +#endif + byte_buf[i] = c; + } + else + { + error("%s: item %d is not a float", objName, i); + return; + } + } + length = i; + if (length > 0) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(sockfd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("%s: send blocked %d msec", objName, + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("tcpserver: send"); + post("%s: could not send data to client %d", objName, client); + break; + } + else + { + sent += result; + bp += result; + } + } + } + } + else post("%s: not a valid socket number (%d)", objName, sockfd); +} + +/* send message to client using socket number */ +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int i, sockfd; + int client = -1; + + if(x->x_nconnections < 0) + { + post("%s: no clients connected", objName); + return; + } + if(argc < 2) + { + post("%s: nothing to send", objName); + return; + } + /* get socket number of connection (first element in list) */ + if(argv[0].a_type == A_FLOAT) + { + sockfd = atom_getfloatarg(0, argc, argv); + for(i = 0; i < x->x_nconnections; i++) /* check if connection exists */ + { + if(x->x_fd[i] == sockfd) + { + client = i; /* the client we're sending to */ + break; + } + } + if(client == -1) + { + post("%s: no connection on socket %d", objName, sockfd); + return; + } + } + else + { + post("%s: no socket specified", objName); + return; + } + tcp_server_send_bytes(client, x, argc-1, &argv[1]); +} + +/* send message to client using client number + note that the client numbers might change in case a client disconnects! */ +/* clients start at 1 but our index starts at 0 */ +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int client; + + if(x->x_nconnections < 0) + { + post("%s: no clients connected", objName); + return; + } + if(argc < 2) + { + post("%s: nothing to send", objName); + return; + } + /* get number of client (first element in list) */ + if(argv[0].a_type == A_FLOAT) + client = atom_getfloatarg(0, argc, argv); + else + { + post("%s: no client specified", objName); + return; + } + if (!((client > 0) && (client < MAX_CONNECT))) + { + post("%s: client %d out of range [1..%d]", objName, client, MAX_CONNECT); + return; + } + --client;/* zero based index*/ + tcp_server_send_bytes(client, x, argc-1, &argv[1]); +} + +/* broadcasts a message to all connected clients */ +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int client; + + /* enumerate through the clients and send each the message */ + for(client = 0; client < x->x_nconnections; client++) /* check if connection exists */ + { + if(x->x_fd[client] >= 0) + { /* socket exists for this client */ + tcp_server_send_bytes(client, x, argc, argv); + break; + } + } +} + +/* ---------------- main tcpserver (receive) stuff --------------------- */ + +static void tcpserver_notify(t_tcpserver *x) +{ + int i, k; + + /* remove connection from list */ + for(i = 0; i < x->x_nconnections; i++) + { + if(x->x_fd[i] == x->x_sock_fd) + { + x->x_nconnections--; + post("%s: \"%s\" removed from list of clients", objName, x->x_host[i]->s_name); + x->x_host[i] = NULL; /* delete entry */ + x->x_fd[i] = -1; + tcpserver_socketreceiver_free(x->x_sr[i]); + x->x_sr[i] = NULL; + + /* rearrange list now: move entries to close the gap */ + for(k = i; k < x->x_nconnections; k++) + { + x->x_host[k] = x->x_host[k + 1]; + x->x_fd[k] = x->x_fd[k + 1]; + } + } + } + outlet_float(x->x_connectout, x->x_nconnections); +} + +static void tcpserver_connectpoll(t_tcpserver *x) +{ + struct sockaddr_in incomer_address; + int sockaddrl = (int) sizeof( struct sockaddr ); + int fd = accept(x->x_connectsocket, (struct sockaddr*)&incomer_address, &sockaddrl); + int i; + + if (fd < 0) post("%s: accept failed", objName); + else + { + t_tcpserver_socketreceiver *y = tcpserver_socketreceiver_new((void *)x, + (t_tcpserver_socketnotifier)tcpserver_notify, NULL);/* MP tcpserver_doit isn't used I think...*/ + if (!y) + { +#ifdef MSW + closesocket(fd); +#else + close(fd); +#endif + return; + } + sys_addpollfn(fd, (t_fdpollfn)tcpserver_socketreceiver_read, y); + x->x_nconnections++; + i = x->x_nconnections - 1; + x->x_host[i] = gensym(inet_ntoa(incomer_address.sin_addr)); + x->x_fd[i] = fd; + x->x_sr[i] = y; + post("%s: accepted connection from %s on socket %d", + objName, x->x_host[i]->s_name, x->x_fd[i]); + outlet_float(x->x_connectout, x->x_nconnections); + outlet_float(x->x_sockout, x->x_fd[i]); /* the socket number */ + x->x_addr[i] = ntohl(incomer_address.sin_addr.s_addr); + x->x_addrbytes[0].a_w.w_float = (x->x_addr[i] & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_addr[i] & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_addr[i] & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_addr[i] & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + } +} + +static void tcpserver_print(t_tcpserver *x) +{ + int i; + + if(x->x_nconnections > 0) + { + post("%s: %d open connections:", objName, x->x_nconnections); + for(i = 0; i < x->x_nconnections; i++) + { + post(" \"%s\" on socket %d", + x->x_host[i]->s_name, x->x_fd[i]); + } + } + else post("%s: no open connections", objName); +} + +static void *tcpserver_new(t_floatarg fportno) +{ + t_tcpserver *x; + int i; + struct sockaddr_in server; + int sockfd, portno = fportno; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("%s: receive socket %d", objName, sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpserver: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; +#ifdef IRIX + /* this seems to work only in IRIX but is unnecessary in + Linux. Not sure what NT needs in place of this. */ + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) + post("setsockopt failed\n"); +#endif + /* assign server port number */ + server.sin_port = htons((u_short)portno); + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("tcpserver: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_tcpserver *)pd_new(tcpserver_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); /* 1st outlet for received data */ + /* streaming protocol */ + if (listen(sockfd, 5) < 0) + { + sys_sockerror("tcpserver: listen"); + sys_closesocket(sockfd); + sockfd = -1; + } + else + { + sys_addpollfn(sockfd, (t_fdpollfn)tcpserver_connectpoll, x); + x->x_connectout = outlet_new(&x->x_obj, &s_float); /* 2nd outlet for number of connected clients */ + x->x_sockout = outlet_new(&x->x_obj, &s_float); /* 3rd outlet for socket number of current client */ + x->x_addrout = outlet_new(&x->x_obj, &s_list); /* 4th outlet for ip address of current client */ + inbinbuf = binbuf_new(); + } + x->x_connectsocket = sockfd; + x->x_nconnections = 0; + for(i = 0; i < MAX_CONNECT; i++) + { + x->x_fd[i] = -1; + x->x_sr[i] = NULL; + } + /* prepare to convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + + return (x); +} + +static void tcpserver_free(t_tcpserver *x) +{ + int i; + + for(i = 0; i < MAX_CONNECT; i++) + { + if (x->x_fd[i] >= 0) + { + sys_rmpollfn(x->x_fd[i]); + sys_closesocket(x->x_fd[i]); + } + if (x->x_sr[i] != NULL) tcpserver_socketreceiver_free(x->x_sr[i]); + } + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } + + binbuf_free(inbinbuf); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpserver_setup(void) +{ + tcpserver_class = class_new(gensym(objName),(t_newmethod)tcpserver_new, (t_method)tcpserver_free, + sizeof(t_tcpserver), 0, A_DEFFLOAT, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_print, gensym("print"), 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_send, gensym("send"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_client_send, gensym("client"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_broadcast, gensym("broadcast"), A_GIMME, 0); +} + +/* end of x_net_tcpserver.c */ diff --git a/net/x_net_udpreceive.c b/net/x_net_udpreceive.c new file mode 100755 index 0000000..1aa9058 --- /dev/null +++ b/net/x_net_udpreceive.c @@ -0,0 +1,161 @@ +/* x_net_udpreceive.c 20060424. Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "m_pd.h" +#include "s_stuff.h" + +#ifdef MSW +#include +#include +#else +#include +#include +#include +#include +#include +#endif + + +/* ----------------------------- udpreceive ------------------------- */ + +static t_class *udpreceive_class; + +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet + +typedef struct _udpreceive +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_addrout; + int x_connectsocket; + t_atom x_addrbytes[4]; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_udpreceive; + +#ifdef MSW +__declspec(dllexport) +#endif +void udpreceive_setup(void); +static void udpreceive_free(t_udpreceive *x); +static void *udpreceive_new(t_floatarg fportno); +static void udpreceive_read(t_udpreceive *x, int sockfd); + +static void udpreceive_read(t_udpreceive *x, int sockfd) +{ + int i, read = 0; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + long addr; + + read = recvfrom(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0, (struct sockaddr *)&from, &fromlen); +#ifdef DEBUG + post("udpreceive_read: read %lu x->x_connectsocket = %d", + read, x->x_connectsocket); +#endif + /* get the sender's ip */ + addr = ntohl(from.sin_addr.s_addr); + x->x_addrbytes[0].a_w.w_float = (addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (addr & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + + if (read < 0) + { + sys_sockerror("udpreceive_read"); + sys_closesocket(x->x_connectsocket); + return; + } + if (read > 0) + { + for (i = 0; i < read; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)x->x_msginbuf[i]; + } + /* send the list out the outlet */ + if (read > 1) outlet_list(x->x_msgout, &s_list, read, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } +} + +static void *udpreceive_new(t_floatarg fportno) +{ + t_udpreceive *x; + struct sockaddr_in server; + int sockfd, portno = fportno; + int intarg, i; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); +#ifdef DEBUG + post("udpreceive_new: socket %d port %d", sockfd, portno); +#endif + if (sockfd < 0) + { + sys_sockerror("udpreceive: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* ask OS to allow another Pd to repoen this port after we close it. */ + intarg = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (char *)&intarg, sizeof(intarg)) < 0) + post("udpreceive: setsockopt (SO_REUSEADDR) failed"); + + /* assign server port number */ + server.sin_port = htons((u_short)portno); + + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("udpreceive: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_udpreceive *)pd_new(udpreceive_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); + x->x_addrout = outlet_new(&x->x_obj, &s_list); + x->x_connectsocket = sockfd; + + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + sys_addpollfn(x->x_connectsocket, (t_fdpollfn)udpreceive_read, x); + return (x); +} + +static void udpreceive_free(t_udpreceive *x) +{ + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } +} + +#ifdef MSW +__declspec(dllexport) +#endif +void udpreceive_setup(void) +{ + udpreceive_class = class_new(gensym("udpreceive"), + (t_newmethod)udpreceive_new, (t_method)udpreceive_free, + sizeof(t_udpreceive), CLASS_NOINLET, A_DEFFLOAT, 0); +} + +/* end x_net_udpreceive.c */ + diff --git a/net/x_net_udpsend.c b/net/x_net_udpsend.c new file mode 100755 index 0000000..03c1f18 --- /dev/null +++ b/net/x_net_udpsend.c @@ -0,0 +1,214 @@ +/* x_net_udpsend.c 20060424. Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* network */ + +#include "m_pd.h" +#include "s_stuff.h" + +#include +#include +#ifdef MSW +#include +#else +#include +#include +#include +#include +#endif + +static t_class *udpsend_class; + +typedef struct _udpsend +{ + t_object x_obj; + int x_fd; +} t_udpsend; + +#ifdef MSW +__declspec(dllexport) +#endif +void udpsend_setup(void); +static void udpsend_free(t_udpsend *x); +static void udpsend_send(t_udpsend *x, t_symbol *s, int argc, t_atom *argv); +static void udpsend_disconnect(t_udpsend *x); +static void udpsend_connect(t_udpsend *x, t_symbol *hostname, t_floatarg fportno); +static void *udpsend_new(void); + +static void *udpsend_new(void) +{ + t_udpsend *x = (t_udpsend *)pd_new(udpsend_class); + outlet_new(&x->x_obj, &s_float); + x->x_fd = -1; + return (x); +} + +static void udpsend_connect(t_udpsend *x, t_symbol *hostname, + t_floatarg fportno) +{ + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + int portno = fportno; + + if (x->x_fd >= 0) + { + error("udpsend: already connected"); + return; + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); +#ifdef DEBUG + fprintf(stderr, "udpsend_connect: send socket %d\n", sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("udpsend: socket"); + return; + } + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(hostname->s_name); + if (hp == 0) + { + post("udpsend: bad host?\n"); + return; + } + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((u_short)portno); + + post("udpsend: connecting to port %d", portno); + /* try to connect. */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + sys_sockerror("udpsend: connecting stream socket"); + sys_closesocket(sockfd); + return; + } + x->x_fd = sockfd; + outlet_float(x->x_obj.ob_outlet, 1); +} + +static void udpsend_disconnect(t_udpsend *x) +{ + if (x->x_fd >= 0) + { + sys_closesocket(x->x_fd); + x->x_fd = -1; + outlet_float(x->x_obj.ob_outlet, 0); + } +} + +static void udpsend_send(t_udpsend *x, t_symbol *s, int argc, t_atom *argv) +{ + static char byte_buf[65536];// arbitrary maximum similar to max IP packet size + int i, d; + char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; + if (e != 0) + { + error("udpsend_send: item %d (%f) is not an integer", i, f); + return; + } + c = (unsigned char)d; + if (c != d) + { + error("udpsend_send: item %d (%f) is not between 0 and 255", i, f); + return; + } +#ifdef DEBUG + post("udpsend_send: argv[%d]: %d", i, c); +#endif + byte_buf[i] = c; + } + else + { + error("udpsend_send: item %d is not a float", i); + return; + } + } + + length = i; + if ((x->x_fd >= 0) && (length > 0)) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(x->x_fd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("udpsend blocked %d msec", + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("udpsend"); + udpsend_disconnect(x); + break; + } + else + { + sent += result; + bp += result; + } + } + } + else error("udpsend: not connected"); +} + +static void udpsend_free(t_udpsend *x) +{ + udpsend_disconnect(x); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void udpsend_setup(void) +{ + udpsend_class = class_new(gensym("udpsend"), (t_newmethod)udpsend_new, + (t_method)udpsend_free, + sizeof(t_udpsend), 0, 0); + class_addmethod(udpsend_class, (t_method)udpsend_connect, + gensym("connect"), A_SYMBOL, A_FLOAT, 0); + class_addmethod(udpsend_class, (t_method)udpsend_disconnect, + gensym("disconnect"), 0); + class_addmethod(udpsend_class, (t_method)udpsend_send, gensym("send"), + A_GIMME, 0); +} + +/* end x_net_udpsend.c*/ + diff --git a/osc/packOSC-help.pd b/osc/packOSC-help.pd new file mode 100755 index 0000000..8518642 --- /dev/null +++ b/osc/packOSC-help.pd @@ -0,0 +1,30 @@ +#N canvas 611 6 488 317 12; +#X obj 171 184 udpsend; +#X msg 177 158 disconnect; +#X msg 171 96 connect 127.0.0.1 9997; +#X obj 71 96 packOSC; +#X obj 71 128 prepend send; +#X obj 171 207 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 1 +1; +#X msg 14 18 send /test/one/two/three zz 88 T; +#X msg 71 65 send /test 1 2 3; +#X msg 90 43 send /west 35; +#X msg 201 41 send /*/left 22; +#X msg 296 24 send /?est/ 1; +#X msg 246 66 send /left one two; +#X msg 339 43 send /right 88; +#X text 4 226 packOSC is like sendOSC except that it outputs a list +of floats instead of directly connecting to the network; +#X text 158 276 2006/04/25 Martin Peach; +#X connect 0 0 5 0; +#X connect 1 0 0 0; +#X connect 2 0 0 0; +#X connect 3 0 4 0; +#X connect 4 0 0 0; +#X connect 6 0 3 0; +#X connect 7 0 3 0; +#X connect 8 0 3 0; +#X connect 9 0 3 0; +#X connect 10 0 3 0; +#X connect 11 0 3 0; +#X connect 12 0 3 0; diff --git a/osc/packOSC.c b/osc/packOSC.c new file mode 100755 index 0000000..eed5f0a --- /dev/null +++ b/osc/packOSC.c @@ -0,0 +1,1000 @@ +/* packOSC is like sendOSC but outputs a list of floats which are the bytes making up the OSC packet. */ +/* This allows for the separation of the protocol and its transport. */ +/* Started by Martin Peach 20060403 */ +/* 20060425 version independent of libOSC */ +/* packOSC.c makes extensive use of code from OSC-client.c and sendOSC.c */ +/* as well as some from OSC-timetag.c. These files have the following header: */ +/* +Written by Matt Wright, The Center for New Music and Audio Technologies, +University of California, Berkeley. Copyright (c) 1996,97,98,99,2000,01,02,03 +The Regents of the University of California (Regents). + +Permission to use, copy, modify, distribute, and distribute modified versions +of this software and its documentation without fee and without a signed +licensing agreement, is hereby granted, provided that the above copyright +notice, this paragraph and the following two paragraphs appear in all copies, +modifications, and distributions. + +IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING +OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED +HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE +MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + + +The OSC webpage is http://cnmat.cnmat.berkeley.edu/OpenSoundControl +*/ + +#define MAX_ARGS 2000 +#define SC_BUFFER_SIZE 64000 + +#include "m_pd.h" +#include +#include +#include + +#ifdef MSW +#include +#else +#include +#endif + +#ifdef unix +#include +#endif +/* This is from OSC-client.h :*/ +/* + + OSC-client.h: library for constructing OpenSoundControl messages. + Derived from SynthControl.h + Author: Matt Wright + Version 0.1: 6/13/97 + Version 0.2: 7/21/2000: Support for type-tagged messages + + + General notes: + + This library abstracts away the data format for the OpenSoundControl + protocol. Users of this library can construct OpenSoundControl packets + with a function call interface instead of knowing how to lay out the bits. + + All issues of memory allocation are deferred to the user of this library. + There are two data structures that the user must allocate. The first + is the actual buffer that the message will be written into. This buffer + can be any size, but if it's too small there's a possibility that it + will become overfull. The other data structure is called an OSCbuf, + and it holds all the state used by the library as it's constructing + a buffer. + + All procedures that have the possibility of an error condition return int, + with 0 indicating no error and nonzero indicating an error. The variable + OSC_errorMessage will be set to point to a string containing an error + message explaining what the problem is. + +*/ +/* + OSC_timeTag.h: library for manipulating OSC time tags + Matt Wright, 5/29/97 + + Time tags in OSC have the same format as in NTP: 64 bit fixed point, with the + top 32 bits giving number of seconds sinve midnight 1/1/1900 and the bottom + 32 bits giving fractional parts of a second. We represent this by an 8-byte + unsigned long if possible, or else a struct. + + NB: On many architectures with 8-byte ints, it's illegal (like maybe a bus error) + to dereference a pointer to an 8 byte int that's not 8-byte aligned. +*/ + +/* You may have to redefine this typedef if ints on your system + aren't 4 bytes. */ +typedef unsigned int uint4; +typedef struct +{ + uint4 seconds; + uint4 fraction; +} OSCTimeTag; + +/* Return the time tag 0x0000000000000001, indicating to the receiving device + that it should process the message immediately. */ +static OSCTimeTag OSCTT_Immediately(void); + +/* The int4byte type has to be a 4-byte integer. You may have to + change this to long or something else on your system. */ +typedef int int4byte; + +/* The maximum depth of bundles within bundles within bundles within... + This is the size of a static array. If you exceed this limit you'll + get an error message. */ +#define MAX_BUNDLE_NESTING 32 + +/* Don't ever manipulate the data in the OSCbuf struct directly. (It's + declared here in the header file only so your program will be able to + declare variables of type OSCbuf and have the right amount of memory + be allocated.) */ + +typedef struct OSCbuf_struct +{ + char *buffer; /* The buffer to hold the OSC packet */ + int size; /* Size of the buffer */ + char *bufptr; /* Current position as we fill the buffer */ + int state; /* State of partially-constructed message */ + int4byte *thisMsgSize; /* Pointer to count field before */ + /* currently-being-written message */ + int4byte *prevCounts[MAX_BUNDLE_NESTING]; /* Pointers to count */ + /* field before each currently open bundle */ + int bundleDepth; /* How many sub-sub-bundles are we in now? */ + char *typeStringPtr; /* This pointer advances through the type */ + /* tag string as you add arguments. */ + int gettingFirstUntypedArg; /* nonzero if this message doesn't have */ + /* a type tag and we're waiting for the 1st arg */ +} OSCbuf; + +/* Here are the possible values of the state field: */ + +#define EMPTY 0 /* Nothing written to packet yet */ +#define ONE_MSG_ARGS 1 /* Packet has a single message; gathering arguments */ +#define NEED_COUNT 2 /* Just opened a bundle; must write message name or */ + /* open another bundle */ +#define GET_ARGS 3 /* Getting arguments to a message. If we see a message */ + /* name or a bundle open/close then the current message */ + /* will end. */ +#define DONE 4 /* All open bundles have been closed, so can't write */ + /* anything else */ + + +static int OSC_strlen(char *s); +static int OSC_padString(char *dest, char *str); +static int OSC_padStringWithAnExtraStupidComma(char *dest, char *str); +static int OSC_WritePadding(char *dest, int i); +static int CheckTypeTag(OSCbuf *buf, char expectedType); + +/* Initialize the given OSCbuf. The user of this module must pass in the + block of memory that this OSCbuf will use for a buffer, and the number of + bytes in that block. (It's the user's job to allocate the memory because + you do it differently in different systems.) */ +static void OSC_initBuffer(OSCbuf *buf, int size, char *byteArray); + + +/* Reset the given OSCbuf. Do this after you send out the contents of + the buffer and want to start writing new data into it. */ +static void OSC_resetBuffer(OSCbuf *buf); + + +/* Is the buffer empty? (I.e., would it be stupid to send the buffer + contents to the synth?) */ +static int OSC_isBufferEmpty(OSCbuf *buf); + + +/* How much space is left in the buffer? */ +static int OSC_freeSpaceInBuffer(OSCbuf *buf); + +/* Does the buffer contain a valid OSC packet? (Returns nonzero if yes.) */ +static int OSC_isBufferDone(OSCbuf *buf); + +/* When you're ready to send out the buffer (i.e., when OSC_isBufferDone() + returns true), call these two procedures to get the OSC packet that's been + assembled and its size in bytes. (And then call OSC_resetBuffer() if you + want to re-use this OSCbuf for the next packet.) */ +static char *OSC_getPacket(OSCbuf *buf); +static int OSC_packetSize(OSCbuf *buf); +static int OSC_CheckOverflow(OSCbuf *buf, int bytesNeeded); + +/* Here's the basic model for building up OSC messages in an OSCbuf: + - Make sure the OSCbuf has been initialized with OSC_initBuffer(). + - To open a bundle, call OSC_openBundle(). You can then write + messages or open new bundles within the bundle you opened. + Call OSC_closeBundle() to close the bundle. Note that a packet + does not have to have a bundle; it can instead consist of just a + single message. + - For each message you want to send: + - Call OSC_writeAddress() with the name of your message. (In + addition to writing your message name into the buffer, this + procedure will also leave space for the size count of this message.) + - Alternately, call OSC_writeAddressAndTypes() with the name of + your message and with a type string listing the types of all the + arguments you will be putting in this message. + - Now write each of the arguments into the buffer, by calling one of: + OSC_writeFloatArg() + OSC_writeIntArg() + OSC_writeStringArg() + - Now your message is complete; you can send out the buffer or you can + add another message to it. +*/ + +static int OSC_openBundle(OSCbuf *buf, OSCTimeTag tt); +static int OSC_closeBundle(OSCbuf *buf); +static int OSC_writeAddress(OSCbuf *buf, char *name); +static int OSC_writeAddressAndTypes(OSCbuf *buf, char *name, char *types); +static int OSC_writeFloatArg(OSCbuf *buf, float arg); +static int OSC_writeIntArg(OSCbuf *buf, int4byte arg); +static int OSC_writeStringArg(OSCbuf *buf, char *arg); + +/* How many bytes will be needed in the OSC format to hold the given + string? The length of the string, plus the null char, plus any padding + needed for 4-byte alignment. */ +static int OSC_effectiveStringLength(char *string); + +typedef struct +{ + enum {INT_osc, FLOAT_osc, STRING_osc, NOTYPE_osc} type; + union + { + int i; + float f; + char *s; + } datum; +} typedArg; + +static int useTypeTags = 0; +static char bufferForOSCbuf[SC_BUFFER_SIZE]; +static t_atom bufferForOSClist[SC_BUFFER_SIZE]; + +static t_class *packOSC_class; + +typedef struct _packOSC +{ + t_object x_obj; + t_int x_typetags; // typetag flag + int x_bundle; // bundle open flag + OSCbuf x_oscbuf[1]; // OSCbuffer + t_atom *x_osclist; + t_outlet *x_bdpthout; // bundle-depth floatoutlet + t_outlet *x_listout; // OSC packet list ouput +} t_packOSC; + +static void *packOSC_new(t_floatarg udpflag); +static void packOSC_openbundle(t_packOSC *x); +static void packOSC_closebundle(t_packOSC *x); +static void packOSC_settypetags(t_packOSC *x, t_float *f); +static void packOSC_sendtyped(t_packOSC *x, t_symbol *s, int argc, t_atom *argv); +static void packOSC_send(t_packOSC *x, t_symbol *s, int argc, t_atom *argv); +static void packOSC_free(t_packOSC *x); +#ifdef MSW +__declspec(dllexport) +#endif +void packOSC_setup(void); +static typedArg packOSC_parseatom(t_atom *a); +static int packOSC_writemessage(OSCbuf *buf, char *messageName, int numArgs, typedArg *args); +static void packOSC_sendbuffer(t_packOSC *x); + +static void *packOSC_new(t_floatarg udpflag) +{ + t_packOSC *x = (t_packOSC *)pd_new(packOSC_class); + // set typetags to 1 by default + x->x_typetags = 1; + // bundle is closed + x->x_bundle = 0; + OSC_initBuffer(x->x_oscbuf, SC_BUFFER_SIZE, bufferForOSCbuf); + x->x_osclist = bufferForOSClist; + x->x_listout = outlet_new(&x->x_obj, &s_list); + x->x_bdpthout = outlet_new(&x->x_obj, &s_float); + return (x); +} + + +static void packOSC_openbundle(t_packOSC *x) +{ + if (x->x_oscbuf->bundleDepth + 1 >= MAX_BUNDLE_NESTING || + OSC_openBundle(x->x_oscbuf, OSCTT_Immediately())) + { + post("packOSC: Problem opening bundle."); + return; + } + x->x_bundle = 1; + outlet_float(x->x_bdpthout, (float)x->x_oscbuf->bundleDepth); +} + +static void packOSC_closebundle(t_packOSC *x) +{ + if (OSC_closeBundle(x->x_oscbuf)) + { + post("packOSC: Problem closing bundle."); + return; + } + outlet_float(x->x_bdpthout, (float)x->x_oscbuf->bundleDepth); + // in bundle mode we send when bundle is closed? + if(!OSC_isBufferEmpty(x->x_oscbuf) > 0 && OSC_isBufferDone(x->x_oscbuf)) + { + packOSC_sendbuffer(x); + OSC_initBuffer(x->x_oscbuf, SC_BUFFER_SIZE, bufferForOSCbuf); + x->x_bundle = 0; + return; + } +} + +static void packOSC_settypetags(t_packOSC *x, t_float *f) +{ + x->x_typetags = (f != 0)?1:0; + post("packOSC: setting typetags %d",x->x_typetags); +} + + +////////////////////////////////////////////////////////////////////// +// this is the real and only sending routine now, for both typed and +// undtyped mode. + +static void packOSC_sendtyped(t_packOSC *x, t_symbol *s, int argc, t_atom *argv) +{ + char messageName[MAXPDSTRING]; + typedArg args[MAX_ARGS]; + int i; + + messageName[0] = '\0'; // empty + + if(argc>MAX_ARGS) + { + post ("packOSC: too many arguments! (max: %d)", MAX_ARGS); + return; + } + +#ifdef DEBUG + post ("packOSC: type tags? %d", useTypeTags); +#endif + + atom_string(&argv[0], messageName, MAXPDSTRING); + for (i = 0; i < argc-1; i++) + { + args[i] = packOSC_parseatom(&argv[i+1]); +#ifdef DEBUG + switch (args[i].type) + { + case INT_osc: + post("packOSC: cell-cont: %d\n", args[i].datum.i); + break; + case FLOAT_osc: + post("packOSC: cell-cont: %f\n", args[i].datum.f); + break; + case STRING_osc: + post("packOSC: cell-cont: %s\n", args[i].datum.s); + break; + case NOTYPE_osc: + post("packOSC: unknown type\n"); + break; + } + post("packOSC: type-id: %d\n", args[i].type); +#endif + } + + if(packOSC_writemessage(x->x_oscbuf, messageName, i, args)) + { + post("packOSC: usage error, write-msg failed."); + return; + } + + if(!x->x_bundle) + { + packOSC_sendbuffer(x); + OSC_initBuffer(x->x_oscbuf, SC_BUFFER_SIZE, bufferForOSCbuf); + } +} + +static void packOSC_send(t_packOSC *x, t_symbol *s, int argc, t_atom *argv) +{ + if(!argc) + { + post("packOSC: not sending empty message."); + return; + } + if(x->x_typetags) + { + useTypeTags = 1; + packOSC_sendtyped(x, s, argc, argv); + useTypeTags = 0; + } + else + { + packOSC_sendtyped(x, s, argc, argv); + } +} + +static void packOSC_free(t_packOSC *x) +{ +} + +#ifdef MSW +__declspec(dllexport) +#endif +void packOSC_setup(void) +{ + packOSC_class = class_new(gensym("packOSC"), (t_newmethod)packOSC_new, + (t_method)packOSC_free, + sizeof(t_packOSC), 0, A_DEFFLOAT, 0); + class_addmethod(packOSC_class, (t_method)packOSC_settypetags, + gensym("typetags"), A_FLOAT, 0); + class_addmethod(packOSC_class, (t_method)packOSC_send, + gensym("send"), A_GIMME, 0); + class_addmethod(packOSC_class, (t_method)packOSC_send, + gensym("senduntyped"), A_GIMME, 0); + class_addmethod(packOSC_class, (t_method)packOSC_send, + gensym("sendtyped"), A_GIMME, 0); + class_addmethod(packOSC_class, (t_method)packOSC_openbundle, + gensym("["), 0, 0); + class_addmethod(packOSC_class, (t_method)packOSC_closebundle, + gensym("]"), 0, 0); +} + +static typedArg packOSC_parseatom(t_atom *a) +{ + typedArg returnVal; + t_float f; + t_int i; + t_symbol s; + char buf[MAXPDSTRING]; + + atom_string(a, buf, MAXPDSTRING); +#ifdef DEBUG + post("packOSC: atom type %d (%s)", a->a_type, buf); +#endif + /* It might be an int, a float, or a string */ + switch (a->a_type) + { + case A_FLOAT: + f = atom_getfloat(a); + i = atom_getint(a); + if (f == (t_float)i) + { // assume that if the int and float are the same, it's an int + returnVal.type = INT_osc; + returnVal.datum.i = i; + } + else + { + returnVal.type = FLOAT_osc; + returnVal.datum.f = f; + } + return returnVal; + case A_SYMBOL: + s = *atom_getsymbol(a); + returnVal.type = STRING_osc; + returnVal.datum.s = s.s_name; + return returnVal; + default: + atom_string(a, buf, MAXPDSTRING); + error("packOSC: atom type %d not implemented (%s)", a->a_type, buf); + returnVal.type = NOTYPE_osc; + returnVal.datum.s = NULL; + return returnVal; + } +} + +static int packOSC_writemessage(OSCbuf *buf, char *messageName, int numArgs, typedArg *args) +{ + int j, returnVal; + + returnVal = 0; +#ifdef DEBUG + post("packOSC: packOSC_writemessage: %s ", messageName); + + for (j = 0; j < numArgs; j++) + { + switch (args[j].type) + { + case INT_osc: + post("packOSC: %d ", args[j].datum.i); + break; + case FLOAT_osc: + post("packOSC: %f ", args[j].datum.f); + break; + case STRING_osc: + post("packOSC: %s ", args[j].datum.s); + break; + default: + post("packOSC: Unrecognized arg type %d", args[j].type); + break; + } + } +#endif + + if (!useTypeTags) + { + returnVal = OSC_writeAddress(buf, messageName); + if (returnVal) + { + post("packOSC: Problem writing address."); + } + } + else + { + /* First figure out the type tags */ + char typeTags[MAX_ARGS+2]; + + typeTags[0] = ','; + + for (j = 0; j < numArgs; ++j) + { + switch (args[j].type) + { + case INT_osc: + typeTags[j+1] = 'i'; + break; + case FLOAT_osc: + typeTags[j+1] = 'f'; + break; + case STRING_osc: + typeTags[j+1] = 's'; + break; + default: + post("packOSC: arg %d type is unrecognized(%d)", j, args[j].type); + break; + } + } + typeTags[j+1] = '\0'; + returnVal = OSC_writeAddressAndTypes(buf, messageName, typeTags); + if (returnVal) + { + post("packOSC: Problem writing address."); + } + } + + for (j = 0; j < numArgs; j++) + { + switch (args[j].type) + { + case INT_osc: + if ((returnVal = OSC_writeIntArg(buf, args[j].datum.i)) != 0) + { + return returnVal; + } + break; + case FLOAT_osc: + if ((returnVal = OSC_writeFloatArg(buf, args[j].datum.f)) != 0) + { + return returnVal; + } + break; + case STRING_osc: + if ((returnVal = OSC_writeStringArg(buf, args[j].datum.s)) != 0) + { + return returnVal; + } + break; + default: + break; // just skip bad types (which we won't get anyway unless this code is buggy) + } + } + return returnVal; +} + +static void packOSC_sendbuffer(t_packOSC *x) +{ + int i; + int length; + char *buf; +#ifdef DEBUG + post("packOSC_sendbuffer: Sending buffer...\n"); +#endif + if (OSC_isBufferEmpty(x->x_oscbuf)) + { + post("packOSC_sendbuffer() called but buffer empty"); + return; + } + if (!OSC_isBufferDone(x->x_oscbuf)) + { + post("packOSC_sendbuffer() called but buffer not ready!, not exiting"); + return; //{{raf}} + } + length = OSC_packetSize(x->x_oscbuf); + buf = OSC_getPacket(x->x_oscbuf); +#ifdef DEBUG + post ("packOSC_sendbuffer: length: %lu", length); +#endif + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < length; ++i) SETFLOAT(&x->x_osclist[i], buf[i]); + /* send the list out the outlet */ + outlet_list(x->x_listout, &s_list, length, x->x_osclist); +} + +/* The next part is copied and morphed from OSC-client.c. */ +/* + Author: Matt Wright + Version 2.2: Calls htonl in the right places 20000620 + Version 2.3: Gets typed messages right. + */ + +/* + pd + ------------- + + raf@interaccess.com: + rev. for Win32 build (verified under Win-2ooo) 11-April-2002 + + -- changed licence part (20040820) jdl + -- Version 2.4 changes not in here (20040820) jdl + +*/ + + +static void OSC_initBuffer(OSCbuf *buf, int size, char *byteArray) +{ + buf->buffer = byteArray; + buf->size = size; + OSC_resetBuffer(buf); +} + +static void OSC_resetBuffer(OSCbuf *buf) +{ + buf->bufptr = buf->buffer; + buf->state = EMPTY; + buf->bundleDepth = 0; + buf->prevCounts[0] = 0; + buf->gettingFirstUntypedArg = 0; + buf->typeStringPtr = 0; +} + +static int OSC_isBufferEmpty(OSCbuf *buf) +{ + return buf->bufptr == buf->buffer; +} + +static int OSC_freeSpaceInBuffer(OSCbuf *buf) +{ + return buf->size - (buf->bufptr - buf->buffer); +} + +static int OSC_isBufferDone(OSCbuf *buf) +{ + return (buf->state == DONE || buf->state == ONE_MSG_ARGS); +} + +static char *OSC_getPacket(OSCbuf *buf) +{ + return buf->buffer; +} + +static int OSC_packetSize(OSCbuf *buf) +{ + return (buf->bufptr - buf->buffer); +} + +static int OSC_CheckOverflow(OSCbuf *buf, int bytesNeeded) +{ + if ((bytesNeeded) > OSC_freeSpaceInBuffer(buf)) + { + post("packOSC: buffer overflow"); + return 1; + } + return 0; +} + +static void PatchMessageSize(OSCbuf *buf) +{ + int4byte size = buf->bufptr - ((char *) buf->thisMsgSize) - 4; + *(buf->thisMsgSize) = htonl(size); +} + +static int OSC_openBundle(OSCbuf *buf, OSCTimeTag tt) +{ + if (buf->state == ONE_MSG_ARGS) + { + post("packOSC: Can't open a bundle in a one-message packet"); + return 3; + } + + if (buf->state == DONE) + { + post("packOSC: This packet is finished; can't open a new bundle"); + return 4; + } + + if (++(buf->bundleDepth) >= MAX_BUNDLE_NESTING) + { + post("packOSC: Bundles nested too deeply; change MAX_BUNDLE_NESTING"); + return 2; + } + + if (CheckTypeTag(buf, '\0')) return 9; + + if (buf->state == GET_ARGS) + { + PatchMessageSize(buf); + } + + if (buf->state == EMPTY) + { + /* Need 16 bytes for "#bundle" and time tag */ + if(OSC_CheckOverflow(buf, 16)) return 1; + } + else + { + /* This bundle is inside another bundle, so we need to leave + a blank size count for the size of this current bundle. */ + if(OSC_CheckOverflow(buf, 20))return 1; + *((int4byte *)buf->bufptr) = 0xaaaaaaaa; + buf->prevCounts[buf->bundleDepth] = (int4byte *)buf->bufptr; + + buf->bufptr += 4; + } + + buf->bufptr += OSC_padString(buf->bufptr, "#bundle"); + + + *((OSCTimeTag *) buf->bufptr) = tt; + + if (htonl(1) != 1) + { + /* Byte swap the 8-byte integer time tag */ + int4byte *intp = (int4byte *)buf->bufptr; + intp[0] = htonl(intp[0]); + intp[1] = htonl(intp[1]); + + /* tt is a struct of two 32-bit words, and even though + each word was wrong-endian, they were in the right order + in the struct.) */ + } + + buf->bufptr += sizeof(OSCTimeTag); + + buf->state = NEED_COUNT; + + buf->gettingFirstUntypedArg = 0; + buf->typeStringPtr = 0; + return 0; +} + + +static int OSC_closeBundle(OSCbuf *buf) +{ + if (buf->bundleDepth == 0) + { + /* This handles EMPTY, ONE_MSG, ARGS, and DONE */ + post("packOSC: Can't close bundle; no bundle is open!"); + return 5; + } + + if (CheckTypeTag(buf, '\0')) return 9; + + if (buf->state == GET_ARGS) + { + PatchMessageSize(buf); + } + + if (buf->bundleDepth == 1) + { + /* Closing the last bundle: No bundle size to patch */ + buf->state = DONE; + } + else + { + /* Closing a sub-bundle: patch bundle size */ + int size = buf->bufptr - ((char *) buf->prevCounts[buf->bundleDepth]) - 4; + *(buf->prevCounts[buf->bundleDepth]) = htonl(size); + buf->state = NEED_COUNT; + } + + --buf->bundleDepth; + buf->gettingFirstUntypedArg = 0; + buf->typeStringPtr = 0; + return 0; +} + +static int OSC_writeAddress(OSCbuf *buf, char *name) +{ + int4byte paddedLength; + + if (buf->state == ONE_MSG_ARGS) + { + post("packOSC: This packet is not a bundle, so you can't write another address"); + return 7; + } + + if (buf->state == DONE) + { + post("packOSC: This packet is finished; can't write another address"); + return 8; + } + + if (CheckTypeTag(buf, '\0')) return 9; + + paddedLength = OSC_effectiveStringLength(name); + + if (buf->state == EMPTY) + { + /* This will be a one-message packet, so no sizes to worry about */ + if(OSC_CheckOverflow(buf, paddedLength))return 1; + buf->state = ONE_MSG_ARGS; + } + else + { + /* GET_ARGS or NEED_COUNT */ + if(OSC_CheckOverflow(buf, 4+paddedLength))return 1; + if (buf->state == GET_ARGS) + { + /* Close the old message */ + PatchMessageSize(buf); + } + buf->thisMsgSize = (int4byte *)buf->bufptr; + *(buf->thisMsgSize) = 0xbbbbbbbb; + buf->bufptr += 4; + buf->state = GET_ARGS; + } + + /* Now write the name */ + buf->bufptr += OSC_padString(buf->bufptr, name); + buf->typeStringPtr = 0; + buf->gettingFirstUntypedArg = 1; + + return 0; +} + +static int OSC_writeAddressAndTypes(OSCbuf *buf, char *name, char *types) +{ + int result; + int4byte paddedLength; + + if (CheckTypeTag(buf, '\0')) return 9; + + result = OSC_writeAddress(buf, name); + + if (result) return result; + + paddedLength = OSC_effectiveStringLength(types); + + if(OSC_CheckOverflow(buf, paddedLength))return 1; + + buf->typeStringPtr = buf->bufptr + 1; /* skip comma */ + buf->bufptr += OSC_padString(buf->bufptr, types); + + buf->gettingFirstUntypedArg = 0; + return 0; +} + +static int CheckTypeTag(OSCbuf *buf, char expectedType) +{ + if (buf->typeStringPtr) + { + if (*(buf->typeStringPtr) != expectedType) + { + if (expectedType == '\0') + { + post("packOSC: According to the type tag I expected more arguments."); + } + else if (*(buf->typeStringPtr) == '\0') + { + post("packOSC: According to the type tag I didn't expect any more arguments."); + } + else + { + post("packOSC: According to the type tag I expected an argument of a different type."); + post("* Expected %c, string now %s\n", expectedType, buf->typeStringPtr); + } + return 9; + } + ++(buf->typeStringPtr); + } + return 0; +} + +static int OSC_writeFloatArg(OSCbuf *buf, float arg) +{ + int4byte *intp; + + if(OSC_CheckOverflow(buf, 4))return 1; + + if (CheckTypeTag(buf, 'f')) return 9; + + /* Pretend arg is a long int so we can use htonl() */ + intp = ((int4byte *) &arg); + *((int4byte *) buf->bufptr) = htonl(*intp); + + buf->bufptr += 4; + + buf->gettingFirstUntypedArg = 0; + return 0; +} + +static int OSC_writeIntArg(OSCbuf *buf, int4byte arg) +{ + if(OSC_CheckOverflow(buf, 4))return 1; + if (CheckTypeTag(buf, 'i')) return 9; + + *((int4byte *) buf->bufptr) = htonl(arg); + buf->bufptr += 4; + + buf->gettingFirstUntypedArg = 0; + return 0; +} + +static int OSC_writeStringArg(OSCbuf *buf, char *arg) +{ + int len; + + if (CheckTypeTag(buf, 's')) return 9; + + len = OSC_effectiveStringLength(arg); + + if (buf->gettingFirstUntypedArg && arg[0] == ',') + { + /* This un-type-tagged message starts with a string + that starts with a comma, so we have to escape it + (with a double comma) so it won't look like a type + tag string. */ + + if(OSC_CheckOverflow(buf, len+4))return 1; /* Too conservative */ + buf->bufptr += + OSC_padStringWithAnExtraStupidComma(buf->bufptr, arg); + + } + else + { + if(OSC_CheckOverflow(buf, len))return 1; + buf->bufptr += OSC_padString(buf->bufptr, arg); + } + + buf->gettingFirstUntypedArg = 0; + return 0; + +} + +/* String utilities */ + +static int OSC_strlen(char *s) +{ + int i; + for (i = 0; s[i] != '\0'; i++) /* Do nothing */ ; + return i; +} + +#define STRING_ALIGN_PAD 4 +static int OSC_effectiveStringLength(char *string) +{ + int len = OSC_strlen(string) + 1; /* We need space for the null char. */ + + /* Round up len to next multiple of STRING_ALIGN_PAD to account for alignment padding */ + if ((len % STRING_ALIGN_PAD) != 0) + { + len += STRING_ALIGN_PAD - (len % STRING_ALIGN_PAD); + } + return len; +} + +static int OSC_padString(char *dest, char *str) +{ + int i; + + for (i = 0; str[i] != '\0'; i++) dest[i] = str[i]; + + return OSC_WritePadding(dest, i); +} + +static int OSC_padStringWithAnExtraStupidComma(char *dest, char *str) +{ + int i; + + dest[0] = ','; + for (i = 0; str[i] != '\0'; i++) dest[i+1] = str[i]; + + return OSC_WritePadding(dest, i+1); +} + +static int OSC_WritePadding(char *dest, int i) +{ + dest[i] = '\0'; + i++; + + for (; (i % STRING_ALIGN_PAD) != 0; i++) dest[i] = '\0'; + + return i; +} + +/* The next bit is modified from OSC-timetag.c. */ +/* + + OSC_timeTag.c: library for manipulating OSC time tags + Matt Wright, 5/29/97 + + Version 0.2 (9/11/98): cleaned up so no explicit type names in the .c file. + +*/ + +static OSCTimeTag OSCTT_Immediately(void) +{ + OSCTimeTag tt; + tt.fraction = 1; + tt.seconds = 0; + return tt; +} +/* end packOSC.c*/ diff --git a/osc/routeOSC-help.pd b/osc/routeOSC-help.pd new file mode 100755 index 0000000..080ce50 --- /dev/null +++ b/osc/routeOSC-help.pd @@ -0,0 +1,35 @@ +#N canvas 0 0 574 322 12; +#X obj 58 82 udpreceive 9997; +#X obj 173 106 unpack 0 0 0 0; +#X floatatom 173 129 3 0 0 0 - - -; +#X floatatom 208 129 3 0 0 0 - - -; +#X floatatom 244 129 3 0 0 0 - - -; +#X floatatom 280 129 3 0 0 0 - - -; +#X text 137 128 from; +#X obj 58 114 unpackOSC; +#X obj 56 158 print; +#X obj 70 206 routeOSC /test /west; +#X obj 70 241 print a; +#X obj 147 241 print b; +#X obj 225 241 print c; +#X msg 203 171 set /left; +#X msg 294 171 set /left /right; +#X text 10 7 routeOSC; +#X text 10 25 accepts lists of floats that are interpreted as OSC packets +; +#X text 10 43 set message reassigns outputs; +#X text 244 206 arguments are OSC addresses to route; +#X text 296 284 2006/04/25 Martin Peach; +#X connect 0 0 7 0; +#X connect 0 1 1 0; +#X connect 1 0 2 0; +#X connect 1 1 3 0; +#X connect 1 2 4 0; +#X connect 1 3 5 0; +#X connect 7 0 8 0; +#X connect 7 0 9 0; +#X connect 9 0 10 0; +#X connect 9 1 11 0; +#X connect 9 2 12 0; +#X connect 13 0 9 0; +#X connect 14 0 9 0; diff --git a/osc/routeOSC.c b/osc/routeOSC.c new file mode 100755 index 0000000..50c7872 --- /dev/null +++ b/osc/routeOSC.c @@ -0,0 +1,510 @@ +/* routeOSC.c 20060424 by Martin Peach, based on OSCroute and OSC-pattern-match.c. */ +/* 20060425 cleaned up some more */ +/* OSCroute.c header follows: */ +/* +Written by Adrian Freed, The Center for New Music and Audio Technologies, +University of California, Berkeley. Copyright (c) 1992,93,94,95,96,97,98,99,2000,01,02,03,04 +The Regents of the University of California (Regents). + +Permission to use, copy, modify, distribute, and distribute modified versions +of this software and its documentation without fee and without a signed +licensing agreement, is hereby granted, provided that the above copyright +notice, this paragraph and the following two paragraphs appear in all copies, +modifications, and distributions. + +IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING +OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED +HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE +MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + + +The OSC webpage is http://cnmat.cnmat.berkeley.edu/OpenSoundControl +*/ + +/* OSC-route.c + Max object for OSC-style dispatching + + To-do: + + Match a pattern against a pattern? + [Done: Only Slash-Star is allowed, see MyPatternMatch.] + Declare outlet types / distinguish leaf nodes from other children + More sophisticated (2-pass?) allmessages scheme + set message? + + + pd + ------------- + -- tweaks for Win32 www.zeggz.com/raf 13-April-2002 + +*/ +/* OSC-pattern-match.c header follows: */ +/* +Copyright © 1998. The Regents of the University of California (Regents). +All Rights Reserved. + +Written by Matt Wright, The Center for New Music and Audio Technologies, +University of California, Berkeley. + +Permission to use, copy, modify, distribute, and distribute modified versions +of this software and its documentation without fee and without a signed +licensing agreement, is hereby granted, provided that the above copyright +notice, this paragraph and the following two paragraphs appear in all copies, +modifications, and distributions. + +IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING +OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED +HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE +MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +The OpenSound Control WWW page is + http://www.cnmat.berkeley.edu/OpenSoundControl +*/ + + + +/* + OSC-pattern-match.c + Matt Wright, 3/16/98 + Adapted from oscpattern.c, by Matt Wright and Amar Chaudhury +*/ + +#if HAVE_CONFIG_H +#include +#endif + +/* the required include files */ +#include "m_pd.h" + +#define MAX_NUM 128 // maximum number of paths (prefixes) we can route + +typedef struct _routeOSC +{ + t_object x_obj; // required header + t_int x_num; // Number of prefixes we store + char *x_prefixes[MAX_NUM]; + void *x_outlets[MAX_NUM+1]; // one for each prefix plus one for everything else +} t_routeOSC; + +/* prototypes */ + +#ifdef MSW +__declspec(dllexport) +#endif +void routeOSC_setup(void); +static int MyPatternMatch (const char *pattern, const char *test); +static void routeOSC_doanything(t_routeOSC *x, t_symbol *s, int argc, t_atom *argv); +static void routeOSC_list(t_routeOSC *x, t_symbol *s, int argc, t_atom *argv); +static void *routeOSC_new(t_symbol *s, int argc, t_atom *argv); +static void routeOSC_set(t_routeOSC *x, t_symbol *s, int argc, t_atom *argv); +static char *NextSlashOrNull(char *p); +static void StrCopyUntilSlash(char *target, const char *source); + +/* from + OSC-pattern-match.c +*/ +static const char *theWholePattern; /* Just for warning messages */ +static int MatchBrackets (const char *pattern, const char *test); +static int MatchList (const char *pattern, const char *test); +static int PatternMatch (const char * pattern, const char * test); + +static t_class *routeOSC_class; +t_symbol *ps_list, *ps_complain, *ps_emptySymbol; + +static int MyPatternMatch (const char *pattern, const char *test) +{ + // This allows the special case of "routeOSC /* " to be an outlet that + // matches anything; i.e., it always outputs the input with the first level + // of the address stripped off. + + if (test[0] == '*' && test[1] == '\0') return 1; + else return PatternMatch(pattern, test); +} + +static void routeOSC_free(t_routeOSC *x) +{ +} + +/* initialization routine */ +// setup +#ifdef MSW +__declspec(dllexport) +#endif +void routeOSC_setup(void) +{ + routeOSC_class = class_new(gensym("routeOSC"), (t_newmethod)routeOSC_new, + (t_method)routeOSC_free,sizeof(t_routeOSC), 0, A_GIMME, 0); + class_addlist(routeOSC_class, routeOSC_list); + class_addanything(routeOSC_class, routeOSC_doanything); + class_addmethod(routeOSC_class, (t_method)routeOSC_set, gensym("set"), A_GIMME, 0); + class_sethelpsymbol(routeOSC_class, gensym("routeOSC-help.pd")); + + ps_emptySymbol = gensym(""); + + post("routeOSC object version 1.0 by Martin Peach, based on OSCroute by Matt Wright. pd: jdl Win32 raf."); + post("OSCroute Copyright © 1999 Regents of the Univ. of California. All Rights Reserved."); +} + +/* instance creation routine */ +static void *routeOSC_new(t_symbol *s, int argc, t_atom *argv) +{ + + t_routeOSC *x = (t_routeOSC *)pd_new(routeOSC_class); // get memory for a new object & initialize + int i; + + if (argc > MAX_NUM) + { + post("* routeOSC: too many arguments: %ld (max %ld)", argc, MAX_NUM); + return 0; + } + x->x_num = 0; + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_SYMBOL) + { + if (argv[i].a_w.w_symbol->s_name[0] == '/') + { /* Now that's a nice prefix */ + x->x_prefixes[i] = argv[i].a_w.w_symbol->s_name; + ++(x->x_num); + } +#if 0 +/* this doesn't make sense to me MP20060425 */ + else if (argv[i].a_w.w_symbol->s_name[0] == '#' && + argv[i].a_w.w_symbol->s_name[1] >= '1' && + argv[i].a_w.w_symbol->s_name[1] <= '9') + { + /* The Max programmer is trying to make a patch that will be + a subpatch with arguments. We have to make an outlet for this + argument. */ + x->x_prefixes[i] = "dummy"; + ++(x->x_num); + } +#endif /* 0 */ + } + else if (argv[i].a_type == A_FLOAT) + { + post("* routeOSC: float arguments are not OK."); + return 0; + } + else + { + post("* routeOSC: unrecognized argument type!"); + return 0; + } + } + /* Have to create the outlets in reverse order */ + /* well, not in pd ? */ + for (i = 0; i <= x->x_num; i++) + { + x->x_outlets[i] = outlet_new(&x->x_obj, &s_list); + } + return (x); +} + +static void routeOSC_set(t_routeOSC *x, t_symbol *s, int argc, t_atom *argv) +{ + int i; + + if (argc > x->x_num) + { + post ("routeOSC: too many paths"); + return; + } + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type != A_SYMBOL) + { + post ("routeOSC: path %d not a symbol", i); + return; + } + if (argv[i].a_w.w_symbol->s_name[0] != '/') + { + post ("routeOSC: path %d doesn't start with /", i); + return; + } + } + for (i = 0; i < argc; ++i) + { + if (argv[i].a_w.w_symbol->s_name[0] == '/') + { /* Now that's a nice prefix */ + x->x_prefixes[i] = argv[i].a_w.w_symbol->s_name; + } + } +} + +static void routeOSC_list(t_routeOSC *x, t_symbol *s, int argc, t_atom *argv) +{ + if (argc > 0 && argv[0].a_type == A_SYMBOL) + { + /* Ignore the fact that this is a "list" */ + routeOSC_doanything(x, argv[0].a_w.w_symbol, argc-1, argv+1); + } + else + { + // post("* OSC-route: invalid list beginning with a number"); + // output on unmatched outlet jdl 20020908 + if (argv[0].a_type == A_FLOAT) + { + outlet_float(x->x_outlets[x->x_num], argv[0].a_w.w_float); + } + else + { + post("* routeOSC: unrecognized atom type!"); + } + } +} + +static void routeOSC_doanything(t_routeOSC *x, t_symbol *s, int argc, t_atom *argv) +{ + char *pattern, *nextSlash; + int i; + int matchedAnything; + // post("*** routeOSC_anything(s %s, argc %ld)", s->s_name, (long) argc); + + pattern = s->s_name; + if (pattern[0] != '/') + { + post("* routeOSC: invalid message pattern %s does not begin with /", s->s_name); + outlet_anything(x->x_outlets[x->x_num], s, argc, argv); + return; + } + matchedAnything = 0; + + nextSlash = NextSlashOrNull(pattern+1); + if (*nextSlash == '\0') + { + /* last level of the address, so we'll output the argument list */ + + for (i = 0; i < x->x_num; ++i) + { + if (MyPatternMatch(pattern+1, x->x_prefixes[i]+1)) + { + ++matchedAnything; + // I hate stupid Max lists with a special first element + if (argc == 0) + { + outlet_bang(x->x_outlets[i]); + } + else if (argv[0].a_type == A_SYMBOL) + { + // Promote the symbol that was argv[0] to the special symbol + outlet_anything(x->x_outlets[i], argv[0].a_w.w_symbol, argc-1, argv+1); + } + else if (argc > 1) + { + // Multiple arguments starting with a number, so naturally we have + // to use a special function to output this "list", since it's what + // Max originally meant by "list". + outlet_list(x->x_outlets[i], 0L, argc, argv); + } + else + { + // There was only one argument, and it was a number, so we output it + // not as a list + if (argv[0].a_type == A_FLOAT) + { + outlet_float(x->x_outlets[i], argv[0].a_w.w_float); + } + else + { + post("* routeOSC: unrecognized atom type!"); + } + } + } + } + } + else + { + /* There's more address after this part, so our output list will begin with + the next slash. */ + t_symbol *restOfPattern = 0; /* avoid the gensym unless we have to output */ + char patternBegin[1000]; + + /* Get the first level of the incoming pattern to match against all our prefixes */ + StrCopyUntilSlash(patternBegin, pattern+1); + + for (i = 0; i < x->x_num; ++i) + { + if (MyPatternMatch(patternBegin, x->x_prefixes[i]+1)) + { + ++matchedAnything; + if (restOfPattern == 0) restOfPattern = gensym(nextSlash); + outlet_anything(x->x_outlets[i], restOfPattern, argc, argv); + } + } + } + + if (!matchedAnything) + { + // output unmatched data on rightmost outlet a la normal 'route' object, jdl 20020908 + outlet_anything(x->x_outlets[x->x_num], s, argc, argv); + } +} + +static char *NextSlashOrNull(char *p) +{ + while (*p != '/' && *p != '\0') p++; + return p; +} + +static void StrCopyUntilSlash(char *target, const char *source) +{ + while (*source != '/' && *source != '\0') + { + *target = *source; + ++target; + ++source; + } + *target = 0; +} + +/* from + OSC-pattern-match.c + Matt Wright, 3/16/98 + Adapted from oscpattern.c, by Matt Wright and Amar Chaudhury +*/ + +static int PatternMatch (const char * pattern, const char * test) +{ + theWholePattern = pattern; + + if (pattern == 0 || pattern[0] == 0) return test[0] == 0; + + if (test[0] == 0) + { + if (pattern[0] == '*') return PatternMatch (pattern+1, test); + return 0; + } + + switch (pattern[0]) + { + case 0: + return test[0] == 0; + case '?': + return PatternMatch (pattern+1, test+1); + case '*': + if (PatternMatch (pattern+1, test)) return 1; + return PatternMatch (pattern, test+1); + case ']': + case '}': + post("routeOSC: Spurious %c in pattern \".../%s/...\"",pattern[0], theWholePattern); + return 0; + case '[': + return MatchBrackets (pattern,test); + case '{': + return MatchList (pattern,test); + case '\\': + if (pattern[1] == 0) return test[0] == 0; + if (pattern[1] == test[0]) return PatternMatch (pattern+2,test+1); + return 0; + default: + if (pattern[0] == test[0]) return PatternMatch (pattern+1,test+1); + return 0; + } +} + +/* we know that pattern[0] == '[' and test[0] != 0 */ + +static int MatchBrackets (const char *pattern, const char *test) +{ + int result; + int negated = 0; + const char *p = pattern; + + if (pattern[1] == 0) + { + post("routeOSC: Unterminated [ in pattern \".../%s/...\"", theWholePattern); + return 0; + } + if (pattern[1] == '!') + { + negated = 1; + p++; + } + while (*p != ']') + { + if (*p == 0) + { + post("Unterminated [ in pattern \".../%s/...\"", theWholePattern); + return 0; + } + if (p[1] == '-' && p[2] != 0) + { + if (test[0] >= p[0] && test[0] <= p[2]) + { + result = !negated; + goto advance; + } + } + if (p[0] == test[0]) + { + result = !negated; + goto advance; + } + p++; + } + result = negated; +advance: + if (!result) return 0; + while (*p != ']') + { + if (*p == 0) + { + post("Unterminated [ in pattern \".../%s/...\"", theWholePattern); + return 0; + } + p++; + } + return PatternMatch (p+1,test+1); +} + +static int MatchList (const char *pattern, const char *test) +{ + const char *restOfPattern, *tp = test; + + for(restOfPattern = pattern; *restOfPattern != '}'; restOfPattern++) + { + if (*restOfPattern == 0) + { + post("Unterminated { in pattern \".../%s/...\"", theWholePattern); + return 0; + } + } + restOfPattern++; /* skip close curly brace */ + pattern++; /* skip open curly brace */ + while (1) + { + if (*pattern == ',') + { + if (PatternMatch (restOfPattern, tp)) return 1; + tp = test; + ++pattern; + } + else if (*pattern == '}') return PatternMatch (restOfPattern, tp); + else if (*pattern == *tp) + { + ++pattern; + ++tp; + } + else + { + tp = test; + while (*pattern != ',' && *pattern != '}') pattern++; + if (*pattern == ',') pattern++; + } + } +} + +/* end of routeOSC.c */ diff --git a/osc/unpackOSC.c b/osc/unpackOSC.c new file mode 100755 index 0000000..acdec07 --- /dev/null +++ b/osc/unpackOSC.c @@ -0,0 +1,604 @@ +/* unpackOSC is like dumpOSC but outputs two lists: a list of symbols for the path */ +/* and a list of floats and/or symbols for the data */ +/* This allows for the separation of the protocol and its transport. */ +/* Started by Martin Peach 20060420 */ +/* This version tries to be standalone from LIBOSC MP 20060425 */ +/* MP 20060505 fixed a bug (line 209) where bytes are wrongly interpreted as negative */ +/* dumpOSC.c header follows: */ +/* +Written by Matt Wright and Adrian Freed, The Center for New Music and +Audio Technologies, University of California, Berkeley. Copyright (c) +1992,93,94,95,96,97,98,99,2000,01,02,03,04 The Regents of the University of +California (Regents). + +Permission to use, copy, modify, distribute, and distribute modified versions +of this software and its documentation without fee and without a signed +licensing agreement, is hereby granted, provided that the above copyright +notice, this paragraph and the following two paragraphs appear in all copies, +modifications, and distributions. + +IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING +OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED +HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE +MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + + +The OSC webpage is http://cnmat.cnmat.berkeley.edu/OpenSoundControl +*/ + + + /* + + dumpOSC.c + server that displays OpenSoundControl messages sent to it + for debugging client udp and UNIX protocol + + by Matt Wright, 6/3/97 + modified from dumpSC.c, by Matt Wright and Adrian Freed + + version 0.2: Added "-silent" option a.k.a. "-quiet" + + version 0.3: Incorporated patches from Nicola Bernardini to make + things Linux-friendly. Also added ntohl() in the right places + to support little-endian architectures. + + + + compile: + cc -o dumpOSC dumpOSC.c + + to-do: + + More robustness in saying exactly what's wrong with ill-formed + messages. (If they don't make sense, show exactly what was + received.) + + Time-based features: print time-received for each packet + + Clean up to separate OSC parsing code from socket/select stuff + + pd: branched from http://www.cnmat.berkeley.edu/OpenSoundControl/src/dumpOSC/dumpOSC.c + ------------- + -- added pd functions + -- socket is made differently than original via pd mechanisms + -- tweaks for Win32 www.zeggz.com/raf 13-April-2002 + -- the OSX changes from cnmat didnt make it here yet but this compiles + on OSX anyway. + +*/ + +#if HAVE_CONFIG_H +#include +#endif + +#include "m_pd.h" + +/* declarations */ + + +#ifdef _WIN32 + #ifdef _MSC_VER +// #include "OSC-common.h" + #endif /* _MSC_VER */ + #include + #include + #include + #include +#else + #include + #include + #include +// #include +// #include + #include +// #include + #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + #include +// #include +// #include +// #include +// #include +// #include +// #include + //#include + +// #ifdef NEED_SCHEDCTL_AND_LOCK +// #include +// #include +// #endif +#endif /* _WIN32 */ + +/* Declarations */ +#ifdef WIN32 + typedef unsigned __int64 osc_time_t; +#else + typedef unsigned long long osc_time_t; +#endif + +#define MAX_MESG 65536 // was 32768 MP: make same as MAX_UDP_PACKET + +/* ----------------------------- was dumpOSC ------------------------- */ + +#define MAX_PATH_AT 50 // maximum nuber of elements in OSC path + +static t_class *unpackOSC_class; + +typedef struct _unpackOSC +{ + t_object x_obj; + t_outlet *x_data_out; + t_atom x_data_at[MAX_MESG];// symbols making up the path + payload + int x_data_atc;// number of symbols to be output + char x_raw[MAX_MESG];// bytes making up the entire OSC message + int x_raw_c;// number of bytes in OSC message +} t_unpackOSC; + +#ifdef MSW +__declspec(dllexport) +#endif +void unpackOSC_setup(void); +static void *unpackOSC_new(void); +static void unpackOSC_free(t_unpackOSC *x); +void unpackOSC_setup(void); +static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv); +static int unpackOSC_path(t_unpackOSC *x, char *path); +static void unpackOSC_Smessage(t_unpackOSC *x, void *v, int n); +static void unpackOSC_PrintTypeTaggedArgs(t_unpackOSC *x, void *v, int n); +static void unpackOSC_PrintHeuristicallyTypeGuessedArgs(t_unpackOSC *x, void *v, int n, int skipComma); +static char *unpackOSC_DataAfterAlignedString(char *string, char *boundary); +static int unpackOSC_IsNiceString(char *string, char *boundary); + +static void *unpackOSC_new(void) +{ + t_unpackOSC *x; + + x = (t_unpackOSC *)pd_new(unpackOSC_class); + x->x_data_out = outlet_new(&x->x_obj, &s_list); + x->x_raw_c = x->x_data_atc = 0; + return (x); +} + +static void unpackOSC_free(t_unpackOSC *x) +{ +} + +#ifdef MSW +__declspec(dllexport) +#endif +void unpackOSC_setup(void) +{ + unpackOSC_class = class_new(gensym("unpackOSC"), + (t_newmethod)unpackOSC_new, (t_method)unpackOSC_free, + sizeof(t_unpackOSC), 0, 0); + class_addlist(unpackOSC_class, (t_method)unpackOSC_list); +} + +/* unpackOSC_list expects an OSC packet in the form of a list of floats on [0..255] */ +static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv) +{ + int size, messageLen, i, j; + char *messageName, *args, *buf; + + if ((argc%4) != 0) + { + post("unpackOSC: packet size (%d) not a multiple of 4 bytes: dropping packet", argc); + return; + } + /* copy the list to a byte buffer, checking for bytes only */ + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + j = (int)argv[i].a_w.w_float; +// if ((j == argv[i].a_w.w_float) && (j >= 0) && (j <= 255)) +// this can miss bytes between 128 and 255 because they are interpreted somewhere as negative +// , so change to this: + if ((j == argv[i].a_w.w_float) && (j >= -128) && (j <= 255)) + { + x->x_raw[i] = (char)j; + } + else + { + post("unpackOSC: data out of range (%d), dropping packet", argv[i].a_w.w_float); + return; + } + } + else + { + post("unpackOSC: data not float, dropping packet"); + return; + } + } + x->x_raw_c = argc; + buf = x->x_raw; + + if ((argc >= 8) && (strncmp(buf, "#bundle", 8) == 0)) + { /* This is a bundle message. */ +#ifdef DEBUG + post("unpackOSC: bundle msg: bundles not yet supported\n"); +#endif + + if (argc < 16) + { + post("unpackOSC: Bundle message too small (%d bytes) for time tag", argc); + return; + } + + /* Print the time tag */ +#ifdef DEBUG + printf("unpackOSC: [ %lx%08lx\n", ntohl(*((unsigned long *)(buf+8))), + ntohl(*((unsigned long *)(buf+12)))); +#endif + + /* Note: if we wanted to actually use the time tag as a little-endian + 64-bit int, we'd have to word-swap the two 32-bit halves of it */ + + i = 16; /* Skip "#group\0" and time tag */ + + while(i < argc) + { + size = ntohl(*((int *) (buf + i))); + if ((size % 4) != 0) + { + post("unpackOSC: Bad size count %d in bundle (not a multiple of 4)", size); + return; + } + if ((size + i + 4) > argc) + { + post("unpackOSC: Bad size count %d in bundle (only %d bytes left in entire bundle)", + size, argc-i-4); + return; + } + + /* Recursively handle element of bundle */ + unpackOSC_list(x, s, size, &argv[i+4]); + i += 4 + size; + } + + if (i != argc) + { + post("unpackOSC: This can't happen"); + } +#ifdef DEBUG + printf("]\n"); +#endif + + } + else if ((argc == 24) && (strcmp(buf, "#time") == 0)) + { + post("unpackOSC: Time message: %s\n :).\n", buf); + return; + } + else + { /* This is not a bundle message or a time message */ + + messageName = buf; + args = unpackOSC_DataAfterAlignedString(messageName, buf+x->x_raw_c); + if (args == 0) + { + post("unpackOSC: Bad message name string: (%s) Dropping entire message.", + messageName); + return; + } +#ifdef DEBUG + post("unpackOSC: message name string: %s", messageName); +#endif + messageLen = args-messageName; + /* put the OSC path into a single symbol */ + x->x_data_atc = unpackOSC_path(x, messageName); + if (x->x_data_atc == 1) unpackOSC_Smessage(x, (void *)args, x->x_raw_c-messageLen); + } + if (x->x_data_atc >= 1) outlet_list(x->x_data_out, &s_list, x->x_data_atc, x->x_data_at); + x->x_data_atc = 0; +} + +static int unpackOSC_path(t_unpackOSC *x, char *path) +{ + int i; + + if (path[0] != '/') + { + post("unpackOSC: bad path (%s)", path); + return 0; + } + for (i = 1; i < MAX_MESG; ++i) + { + if (path[i] == '\0') + { /* the end of the path: turn path into a symbol */ + SETSYMBOL(&x->x_data_at[0],gensym(path)); + return 1; + } + } + post("unpackOSC: path too long"); + return 0; +} +#define SMALLEST_POSITIVE_FLOAT 0.000001f + +static void unpackOSC_Smessage(t_unpackOSC *x, void *v, int n) +{ + char *chars = v; + + if (n != 0) + { + if (chars[0] == ',') + { + if (chars[1] != ',') + { + /* This message begins with a type-tag string */ + unpackOSC_PrintTypeTaggedArgs(x, v, n); + } + else + { + /* Double comma means an escaped real comma, not a type string */ + unpackOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 1); + } + } + else + { + unpackOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 0); + } + } +} + +static void unpackOSC_PrintTypeTaggedArgs(t_unpackOSC *x, void *v, int n) +{ + char *typeTags, *thisType, *p; + int myargc = x->x_data_atc; + t_atom *mya = x->x_data_at; + + typeTags = v; + + if (!unpackOSC_IsNiceString(typeTags, typeTags+n)) + { + /* No null-termination, so maybe it wasn't a type tag + string after all */ + unpackOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 0); + return; + } + + p = unpackOSC_DataAfterAlignedString(typeTags, typeTags+n); + + for (thisType = typeTags + 1; *thisType != 0; ++thisType) + { + switch (*thisType) + { + case 'i': case 'r': case 'm': case 'c': +#ifdef DEBUG + post("integer: %d", ntohl(*((int *) p))); +#endif + SETFLOAT(mya+myargc,(signed)ntohl(*((int *) p))); + myargc++; + p += 4; + break; + case 'f': + { + int i = ntohl(*((int *) p)); + float *floatp = ((float *) (&i)); +#ifdef DEBUG + post("float: %f", *floatp); +#endif + SETFLOAT(mya+myargc,*floatp); + myargc++; + p += 4; + break; + } + case 'h': case 't': +#ifdef DEBUG + printf("[A 64-bit int] "); +#endif + post("[A 64-bit int] not implemented"); + p += 8; + break; + case 'd': +#ifdef DEBUG + printf("[A 64-bit float] "); +#endif + post("[A 64-bit float] not implemented"); + p += 8; + break; + case 's': case 'S': + if (!unpackOSC_IsNiceString(p, typeTags+n)) + { + post("Type tag said this arg is a string but it's not!\n"); + return; + } + else + { +#ifdef DEBUG + post("string: \"%s\"", p); +#endif + SETSYMBOL(mya+myargc,gensym(p)); + myargc++; + p = unpackOSC_DataAfterAlignedString(p, typeTags+n); + + } + break; + case 'T': +#ifdef DEBUG + printf("[True] "); +#endif + SETFLOAT(mya+myargc,1.); + myargc++; + break; + case 'F': +#ifdef DEBUG + printf("[False] "); +#endif + SETFLOAT(mya+myargc,0.); + myargc++; + break; + case 'N': +#ifdef DEBUG + printf("[Nil]"); +#endif + post("sendOSC: [Nil] not implemented"); + break; + case 'I': +#ifdef DEBUG + printf("[Infinitum]"); +#endif + post("sendOSC: [Infinitum] not implemented"); + break; + default: + post("sendOSC: [Unrecognized type tag %c]", *thisType); + } + } + x->x_data_atc = myargc; +} + +static void unpackOSC_PrintHeuristicallyTypeGuessedArgs(t_unpackOSC *x, void *v, int n, int skipComma) +{ + int i, thisi; + int *ints; + float thisf; + char *chars, *string, *nextString; + int myargc= x->x_data_atc; + t_atom* mya = x->x_data_at; + + + /* Go through the arguments 32 bits at a time */ + ints = v; + chars = v; + + for (i = 0; i < n/4; ) + { + string = &chars[i*4]; + thisi = ntohl(ints[i]); + /* Reinterpret the (potentially byte-reversed) thisi as a float */ + thisf = *(((float *) (&thisi))); + + if (thisi >= -1000 && thisi <= 1000000) + { +#ifdef DEBUG + printf("%d ", thisi); +#endif + SETFLOAT(mya+myargc,(t_float) (thisi)); + myargc++; + i++; + } + else if (thisf >= -1000.f && thisf <= 1000000.f && + (thisf <=0.0f || thisf >= SMALLEST_POSITIVE_FLOAT)) + { +#ifdef DEBUG + printf("%f ", thisf); +#endif + SETFLOAT(mya+myargc,thisf); + myargc++; + i++; + } + else if (unpackOSC_IsNiceString(string, chars+n)) + { + nextString = unpackOSC_DataAfterAlignedString(string, chars+n); +#ifdef DEBUG + printf("\"%s\" ", (i == 0 && skipComma) ? string +1 : string); +#endif + SETSYMBOL(mya+myargc,gensym(string)); + myargc++; + i += (nextString-string) / 4; + } + else + { + // unhandled .. ;) +#ifdef DEBUG + post("unpackOSC: indeterminate type: 0x%x xx", ints[i]); +#endif + i++; + } + x->x_data_atc = myargc; + } +} + +#define STRING_ALIGN_PAD 4 + +static char *unpackOSC_DataAfterAlignedString(char *string, char *boundary) +{ + /* The argument is a block of data beginning with a string. The + string has (presumably) been padded with extra null characters + so that the overall length is a multiple of STRING_ALIGN_PAD + bytes. Return a pointer to the next byte after the null + byte(s). The boundary argument points to the character after + the last valid character in the buffer---if the string hasn't + ended by there, something's wrong. + + If the data looks wrong, return 0, and set htm_error_string */ + + int i; + + if ((boundary - string) %4 != 0) + { + post("unpackOSC: DataAfterAlignedString: bad boundary"); + return 0; + } + + for (i = 0; string[i] != '\0'; i++) + { + if (string + i >= boundary) + { + post("unpackOSC: DataAfterAlignedString: Unreasonably long string"); + return 0; + } + } + + /* Now string[i] is the first null character */ + i++; + + for (; (i % STRING_ALIGN_PAD) != 0; i++) + { + if (string + i >= boundary) + { + post("unpackOSC: DataAfterAlignedString: Unreasonably long string"); + return 0; + } + if (string[i] != '\0') + { + post("unpackOSC:DataAfterAlignedString: Incorrectly padded string"); + return 0; + } + } + + return string+i; +} + +static int unpackOSC_IsNiceString(char *string, char *boundary) +{ + /* Arguments same as DataAfterAlignedString(). Is the given "string" + really a string? I.e., is it a sequence of isprint() characters + terminated with 1-4 null characters to align on a 4-byte boundary? + Returns 1 if true, else 0. */ + + int i; + + if ((boundary - string) %4 != 0) + { + fprintf(stderr, "Internal error: IsNiceString: bad boundary\n"); + return 0; + } + + for (i = 0; string[i] != '\0'; i++) + if ((!isprint(string[i])) || (string + i >= boundary)) return 0; + + /* If we made it this far, it's a null-terminated sequence of printing characters + in the given boundary. Now we just make sure it's null padded... */ + + /* Now string[i] is the first null character */ + i++; + for (; (i % STRING_ALIGN_PAD) != 0; i++) + if (string[i] != '\0') return 0; + + return 1; +} + +/* end of unpackOSC.c */ diff --git a/sqosc~/sqosc-help.pd b/sqosc~/sqosc-help.pd new file mode 100755 index 0000000..1a5cf6d --- /dev/null +++ b/sqosc~/sqosc-help.pd @@ -0,0 +1,116 @@ +#N canvas 166 14 572 824 12; +#X obj 149 229 dac~; +#X obj 257 179 tabwrite~ wave; +#X obj 15 319 table wave 512; +#X msg 257 149 bang; +#X text 297 148 graph; +#X floatatom 230 32 5 0 1 0 - - -; +#X msg 225 -2 0.001; +#X obj 7 48 osc~ 1; +#X obj 46 160 *~ 100; +#X floatatom 7 25 5 0 0 0 - - -; +#X floatatom 184 53 5 0 0 0 - - -; +#X floatatom 78 79 5 0 0 0 - - -; +#X obj 77 47 line; +#X msg 93 22 0 10000; +#X msg 81 -13 20000 10000; +#X obj 7 79 +~ 1; +#X msg 313 24 0.1; +#X floatatom 275 67 5 0 0 0 - - -; +#X obj 359 46 line; +#X msg 346 -8 0.01 10000; +#X msg 382 17 0.99 10000; +#X msg 296 -8 0.5; +#X obj 379 127 metro 100; +#X obj 379 96 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 1 1 +; +#X obj 184 30 mtof; +#X obj 184 -33 counter 127; +#X obj 184 -76 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 184 -57 metro 100; +#X floatatom 251 -78 5 0 0 0 - - -; +#X msg 250 -102 10000; +#X floatatom 364 -67 5 0 0 0 - - -; +#X obj 362 -149 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 361 -90 counter 2; +#X obj 403 -32 sel 1 2; +#X obj 362 -118 metro 10000; +#X text 18 255 creation arguments: frequency pulsewidth bandwidth; +#X text 17 274 inlets: frequency phase pulsewidth; +#X text 22 295 outlet: pulse; +#X obj 24 -91 random 1000; +#X obj 24 -65 * 0.001; +#X obj 24 -115 metro 100; +#X obj 24 -134 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X floatatom 91 -136 5 0 0 0 - - -; +#X msg 91 -161 100; +#X obj 163 96 sqosc~ 440 0.5 2000; +#X obj 146 -171 osc~ 1; +#X obj 147 -148 +~ 1; +#X obj 146 -125 *~ 100; +#X floatatom 211 -152 5 0 0 1 fm - -; +#X floatatom 146 -192 5 0 0 0 - - -; +#X obj 379 151 spigot; +#X obj 422 96 tgl 15 0 empty empty sync_graph 20 7 0 8 -262144 -1 -1 +0 1; +#X msg 379 176 0; +#X obj 342 71 loadbang; +#X obj 164 72 +~; +#X floatatom 431 -162 5 0 0 0 - - -; +#X connect 3 0 1 0; +#X connect 5 0 44 1; +#X connect 6 0 5 0; +#X connect 7 0 15 0; +#X connect 9 0 7 0; +#X connect 10 0 54 1; +#X connect 11 0 8 1; +#X connect 12 0 11 0; +#X connect 13 0 12 0; +#X connect 14 0 12 0; +#X connect 15 0 8 0; +#X connect 16 0 17 0; +#X connect 17 0 44 2; +#X connect 18 0 17 0; +#X connect 19 0 18 0; +#X connect 20 0 18 0; +#X connect 21 0 17 0; +#X connect 22 0 1 0; +#X connect 22 0 50 0; +#X connect 23 0 22 0; +#X connect 24 0 10 0; +#X connect 25 0 24 0; +#X connect 26 0 27 0; +#X connect 27 0 25 0; +#X connect 28 0 27 1; +#X connect 29 0 28 0; +#X connect 30 0 33 0; +#X connect 31 0 34 0; +#X connect 32 0 30 0; +#X connect 33 0 21 0; +#X connect 33 1 16 0; +#X connect 34 0 32 0; +#X connect 38 0 39 0; +#X connect 39 0 17 0; +#X connect 40 0 38 0; +#X connect 41 0 40 0; +#X connect 42 0 40 1; +#X connect 43 0 42 0; +#X connect 44 0 1 0; +#X connect 44 0 0 1; +#X connect 44 0 0 0; +#X connect 45 0 46 0; +#X connect 46 0 47 0; +#X connect 47 0 54 0; +#X connect 48 0 47 1; +#X connect 49 0 45 0; +#X connect 50 0 52 0; +#X connect 51 0 50 1; +#X connect 52 0 5 0; +#X connect 53 0 23 0; +#X connect 53 0 3 0; +#X connect 53 0 48 0; +#X connect 54 0 44 0; +#X connect 55 0 34 1; diff --git a/sqosc~/sqosc.c b/sqosc~/sqosc.c new file mode 100755 index 0000000..f832633 --- /dev/null +++ b/sqosc~/sqosc.c @@ -0,0 +1,461 @@ +/* sqosc.c Martin Peach 20060613 based on d_osc.c */ +/* 20060707 using x-x^3/3 to smooth the ramp */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* sinusoidal oscillator and table lookup; see also tabosc4~ in d_array.c. +*/ + +#include "m_pd.h" +#include "math.h" +#include /* for file io */ + +#define UNITBIT32 1572864. /* 3*2^19; bit 32 has place value 1 */ + + /* machine-dependent definitions. These ifdefs really + should have been by CPU type and not by operating system! */ +#ifdef IRIX + /* big-endian. Most significant byte is at low address in memory */ +#define HIOFFSET 0 /* word offset to find MSB */ +#define LOWOFFSET 1 /* word offset to find LSB */ +#define int32 long /* a data type that has 32 bits */ +#endif /* IRIX */ + +#ifdef MSW + /* little-endian; most significant byte is at highest address */ +#define HIOFFSET 1 +#define LOWOFFSET 0 +#define int32 long +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +#include +#endif + +#ifdef __APPLE__ +#define __BYTE_ORDER BYTE_ORDER +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#endif + +#ifdef __linux__ +#include +#endif + +#if defined(__unix__) || defined(__APPLE__) +#if !defined(__BYTE_ORDER) || !defined(__LITTLE_ENDIAN) +#error No byte order defined +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define HIOFFSET 1 +#define LOWOFFSET 0 +#else +#define HIOFFSET 0 /* word offset to find MSB */ +#define LOWOFFSET 1 /* word offset to find LSB */ +#endif /* __BYTE_ORDER */ +#include +#define int32 int32_t +#endif /* __unix__ or __APPLE__*/ + +union tabfudge +{ + double tf_d; + int32 tf_i[2]; +}; + +static t_class *sqosc_class, *scalarsqosc_class; + +static float *sqosc_table; +/* COSTABSIZE is 512 in m_pd.h, we start with that... */ +#define LOGSQOSCTABSIZE 9 +#define SQOSCTABSIZE 512 +#define HALFSQOSCTABSIZE 256 + +typedef struct _sqosc +{ + t_object x_obj; + double x_phase; + float x_conv; + float x_f; /* frequency if scalar */ + float x_pw; /* pulse width 0-1, default 0.5 */ + float x_bw; /* bandwidth */ + float x_slew; /* slew time in samples */ + double x_dpw; /* pulse width in this pulse */ + int x_pulse_ended; /* nonzero if pulse has finished */ +// FILE *x_logfp; +// int x_logcount; +} t_sqosc; + +static void sqosc_maketable(void); +void sqosc_tilde_setup(void); +static void sqosc_ft1(t_sqosc *x, t_float f); +static void sqosc_pw(t_sqosc *x, t_float pw); +static void sqosc_dsp(t_sqosc *x, t_signal **sp); +static t_int *sqosc_perform(t_int *w); +static void *sqosc_new(t_floatarg f, t_floatarg pw, t_float bw); + +static void sqosc_maketable(void) +{ + int i; + float *fp, phase, phsinc = (2. * 3.14159) / SQOSCTABSIZE; + union tabfudge tf; + //FILE *cosfp; + + if (sqosc_table) return; + //cosfp = fopen("sqosctable.txt", "wb"); + sqosc_table = (float *)getbytes(sizeof(float) * (SQOSCTABSIZE+1)); + for (i = SQOSCTABSIZE + 1, fp = sqosc_table, phase = 0; i--; + fp++, phase += phsinc) + { + *fp = cos(phase); + //fprintf(cosfp, "%f: %f\n", phase, *fp); + } + //fclose(cosfp); + /* here we check at startup whether the byte alignment + is as we declared it. If not, the code has to be + recompiled the other way. */ + tf.tf_d = UNITBIT32 + 0.5; + if ((unsigned)tf.tf_i[LOWOFFSET] != 0x80000000) + bug("cos~: unexpected machine alignment"); +} + +static void *sqosc_new(t_floatarg f, t_floatarg pw, t_floatarg bw) +{ + t_sqosc *x = (t_sqosc *)pd_new(sqosc_class); + + x->x_f = f; /* the initial frequency in Hz */ + outlet_new(&x->x_obj, gensym("signal")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("ft1")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("pw")); + post("sqosc_new frequency %f, pulsewidth %f bandwidth %f", f, pw, bw); + x->x_phase = 0; + x->x_conv = 0; + if ((pw <= 0)||(pw >= 1)) + { + post("sqosc: second argument (pulse width) must be greater than 0 and less than 1, using 0.5"); + x->x_pw = 0.5 * SQOSCTABSIZE; + } + else x->x_pw = pw * SQOSCTABSIZE; + if (bw < 0) + { + post("sqosc: third argument (bandwidth) must be greater than 0, using 10000"); + x->x_bw = 10000; + } + else x->x_bw = bw; + x->x_slew = HALFSQOSCTABSIZE/x->x_bw;/* slew = time of half bandwidth cycle */ + x->x_dpw = x->x_pw; /* pulse width in this pulse */ + x->x_pulse_ended = 1; /* nonzero if pulse has finished */ +// x->x_logfp = fopen("sqosclog.txt", "wb"); +// x->x_logcount = 0; + return (x); +} + +/* +This is corrected from Chun Lee's explanation in an email at: +http://music.columbia.edu/pipermail/music-dsp/2004-November/028814.html + +1-> a double variable UNITBIT32 is assigned to 1572864. This is what it +looks like in memory in a little endian machine: +byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0 +00000000 00000000 00000000 00000000 00000000 00000000 0001 1100 1000001 0 +|<------fraction------------------>|<----1572864--------->|exponent |sign| + +The reason for 1572864 (1.5 X 2^20) is that its representation in IEEE754 format (double) +has 51 zeros to the right of a placeholding 1 bit, which can then be used as +a wide fixed-point register with the binary point at bit 32, so the lower 32 bits +can be used as a fractional accumulator with 32-bit precision. +Adding 1 to INITBIT32 adds a 1 at bit 32, with no change in exponent, +so the lower 32 bits act as a fraction of one. The 31 zero bits in the integer part +can be set as high as 2147483647 (31 1s) without affecting the precision. + +The upper 32 bits are 1094189056, or 01000001 00111000 00000000 00000000 in binary. +The sign bit is zero, or positive. The upper bit of the exponent is one, meaning +the number is normalized, or full precision.The exponent is 1043 minus the bias +of 1023, or 20. + +This is the representation of the number 1.5 X 2^20 = 1572864: + +IEEE Standard 754 Double Precision Storage Format (double): +63 62 52 51 32 31 0 ++--------+----------------+-------------------+------------------------------+ +| s 1bit | e[62:52] 11bit | f[51:32] 20bit | f[31:0] 32bit | ++--------+----------------+-------------------+------------------------------+ +B0-------------->B1---------->B2----->B3----->B4----->B5----->B6----->B7-----> +0 10000010011 1000000000000000000000000000000000000000000000000000 + +2-> there is the tabfudge like: + +Union tabfudge +{ + double tf_d; + int32 tf_i[2]; +} + +In dsp_perfrom: + +Union tabfudge tf; + +tf.tf_d = UNITBIT32; + +3-> the phase is accumulated into tf_d plus the UNITBIT32 + +Double dphase = x_phase + UNITBIT32; + +4-> byte 0 to byte 3 is stored as a separate variable as: + +int normhipart = tf.tf_I[HIOFFSET]; //where HIOFFSET is used to find MSB + +5-> in the actual dsp block, although the phase is accumulated into +tf.tf_d, bytes 0 to 3 of tf.tf_d are constantly forced to be a +constant by: + +tf.tf_i[HIOFFSET] = normhipart; + +6-> because of this, to get the fraction part out of the tf.tf_d, simply do: + +tf.tf_d - UNITBIT32; + +7-> mission accomplished!!!!! + +*/ + +static t_int *sqosc_perform(t_int *w) +{ + t_sqosc *x = (t_sqosc *)(w[1]); + t_float *in = (t_float *)(w[2]); + t_float *out = (t_float *)(w[3]); + t_float sample; + int n = (int)(w[4]); + float *tab = sqosc_table, *addr, f1, f2, frac; + int index; + double dphase = x->x_phase + UNITBIT32; + int normhipart; + union tabfudge tf; + float conv = x->x_conv; + double lastin, findex, slewindex; + static double twothirds = 2.0/3.0; + static double onethird = 1.0/3.0; + + tf.tf_d = UNITBIT32; /* set the phase accumulator to (1.5 X 2^20) making it effectively fixed-point */ + normhipart = tf.tf_i[HIOFFSET]; /* save the sign, exponent, and integer part of a fixed accumulator */ + tf.tf_d = dphase; /* the current phase plus the "frame" */ + lastin = *in++; /* latest frequency */ + if (lastin < 0) lastin = -lastin;/* negative frequency is the same as positive here */ + if (lastin > x->x_bw) lastin = x->x_bw;// limit frequency to bandwidth + slewindex = x->x_slew*lastin; + dphase += lastin * conv; /* new phase is old phase + (frequency * table period) */ + //addr = tab + (tf.tf_i[HIOFFSET] & (SQOSCTABSIZE-1)); /* point to the current sample in the table */ + index = tf.tf_i[HIOFFSET] & (SQOSCTABSIZE-1); + tf.tf_i[HIOFFSET] = normhipart; /* zero the non-fractional part of the phase */ + frac = tf.tf_d - UNITBIT32; /* extract the fractional part of the phase */ + while (--n) + { + tf.tf_d = dphase; + //f1 = addr[0]; /* first sample */ + if (index <= slewindex) + { /* rising phase */ + if(x->x_pulse_ended) + {/* set pw for this pulse once only*/ + if(x->x_pw < slewindex)x->x_dpw = slewindex; + else if (x->x_pw > SQOSCTABSIZE-slewindex)x->x_dpw = SQOSCTABSIZE-slewindex; + else x->x_dpw = x->x_pw; + x->x_pulse_ended = 0; + } + //findex = (index/(x->x_slew*lastin))*HALFSQOSCTABSIZE; + //addr = tab + HALFSQOSCTABSIZE + (int)findex; + f1 = 1.0-2.0*(slewindex-index)/slewindex;// a ramp from -1 to +1 // addr[0]; + f1 = f1 - pow(f1, 3.0)*onethird;// smooth the ramp +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "rise index %d slewindex %f f1 %f frac %f\n", index, slewindex, f1, frac); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else if (index < x->x_dpw) f1 = twothirds; /* risen */ + else if (index <= slewindex+x->x_dpw) + { /* falling phase */ +// findex = ((index-HALFSQOSCTABSIZE)/(x->x_slew*lastin))*HALFSQOSCTABSIZE; +// addr = tab + (int)findex; + f1 = -1.0+2.0*(slewindex-index+x->x_dpw)/slewindex;// a ramp from +1 to -1 // addr[0]; + f1 = f1 - pow(f1, 3.0)*onethird;// smooth the ramp + x->x_pulse_ended = 1; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "fall index %d slewindex %f f1 %f frac %f\n", index, slewindex, f1, frac); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else + { /* fallen */ + f1 = -twothirds; + } + lastin = *in++; + if (lastin < 0) lastin = -lastin;/* negative frequency is the same as positive here */ + if (lastin > x->x_bw) lastin = x->x_bw;// limit frequency to bandwidth + slewindex = x->x_slew*lastin; + dphase += lastin * conv; /* next phase */ + //f2 = addr[1]; /* second sample */ + if (index+1 <= slewindex) + { + f2 = 1.0-2.0*(slewindex-index-1)/slewindex;// addr[1]; + f2 = f2 - pow(f2, 3.0)*onethird; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "rise index %d slewindex %f f2 %f frac %f\n", index+1, slewindex, f2, frac); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else if (index+1 < x->x_dpw) f2 = twothirds; + else if (index+1 <= slewindex+x->x_dpw) + { + f2 = -1.0+2.0*(slewindex-index-1+x->x_dpw)/slewindex;// addr[1]; + f2 = f2 - pow(f2, 3.0)*onethird; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "fall index %d slewindex %f f2 %f frac %f\n", index+1, slewindex, f2, frac); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else f2 = -twothirds; + + sample = f1 + frac * (f2 - f1); /* output first sample plus fraction of second sample (linear interpolation) */ + *out++ = sample; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "index %ld f1 %f f2 %f frac %f out %f\n", index, f1, f2, frac, sample); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + //addr = tab + (tf.tf_i[HIOFFSET] & (SQOSCTABSIZE-1)); /* point to the next sample */ + index = tf.tf_i[HIOFFSET] & (SQOSCTABSIZE-1); + tf.tf_i[HIOFFSET] = normhipart; /* zero the non-fractional part */ + + frac = tf.tf_d - UNITBIT32; /* get next fractional part */ + } + //f1 = addr[0]; + if (index <= slewindex) + { + if(x->x_pulse_ended) + {/* set pw for this pulse once only*/ + if(x->x_pw < slewindex)x->x_dpw = slewindex; + else if (x->x_pw > SQOSCTABSIZE-slewindex)x->x_dpw = SQOSCTABSIZE-slewindex; + else x->x_dpw = x->x_pw; + x->x_pulse_ended = 0; + } + //findex = (index/(x->x_slew*lastin))*HALFSQOSCTABSIZE; + //addr = tab + HALFSQOSCTABSIZE + (int)findex; + f1 = 1.0-2.0*(slewindex-index)/slewindex;// addr[0]; + f1 = f1 - pow(f1, 3.0)*onethird; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "rise2 index %d slewindex %f f1 %f frac %f\n", index, slewindex, f1, frac); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else if (index < x->x_dpw) f1 = twothirds; /* risen */ + else if (index <= slewindex+x->x_dpw) + { /* falling phase */ +// findex = ((index-HALFSQOSCTABSIZE)/(x->x_slew*lastin))*HALFSQOSCTABSIZE; +// addr = tab + (int)findex; + f1 = -1.0+2.0*(slewindex-index+x->x_dpw)/slewindex;// addr[0]; + f1 = f1 - pow(f1, 3.0)*onethird; + x->x_pulse_ended = 1; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "fall2 index %d slewindex %f f1 %f frac %f\n", index, slewindex, f1, frac); +// ++x->x_logcount; +/// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else + { /* fallen */ + f1 = -twothirds; + } + //f2 = addr[1]; /* second sample */ + if (index+1 <= slewindex) + { + f2 = 1.0-2.0*(slewindex-index-1)/slewindex;// addr[1]; + f2 = f2 - pow(f2, 3.0)*onethird; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "rise2 index %d slewindex %f f2 %f frac %f\n", index+1, slewindex, f2, frac); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else if (index+1 < x->x_dpw) f2 = twothirds; + else if (index+1 <= slewindex+x->x_dpw) + { + f2 = -1.0+2.0*(slewindex-index-1+x->x_dpw)/slewindex;// addr[1]; + f2 = f2 - pow(f2, 3.0)*onethird; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "fall2 index %d slewindex %f f2 %f frac %f\n", index+1, slewindex, f2, frac); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + } + else f2 = -twothirds; + sample = f1 + frac * (f2 - f1); /* the final sample */ + *out++ = sample; +// if (x->x_logcount < 1000) +// { +// fprintf(x->x_logfp, "*index %ld f1 %f f2 %f frac %f out %f\n", index, f1, f2, frac, sample); +// ++x->x_logcount; +// if (x->x_logcount >= 1000) fclose(x->x_logfp); +// } + + tf.tf_d = UNITBIT32 * SQOSCTABSIZE; /* this just changes the exponent if the table size is a power of 2 */ + normhipart = tf.tf_i[HIOFFSET]; /* ...so we get more integer digits but fewer fractional ones */ + tf.tf_d = dphase + (UNITBIT32 * SQOSCTABSIZE - UNITBIT32); /* subtract one UNITBIT32 we added at the beginning */ + tf.tf_i[HIOFFSET] = normhipart; /* wrap any overflow to the table size */ + x->x_phase = tf.tf_d - UNITBIT32 * SQOSCTABSIZE; /* extract just the phase */ + return (w+5); +} +static void sqosc_dsp(t_sqosc *x, t_signal **sp) +{ +// static int once = 0; + + x->x_conv = SQOSCTABSIZE/sp[0]->s_sr; +/* conv = table period = (samples/cycle)/(samples/sec) = sec/cycle = 0.011610sec for 512/44100 */ + +// if (once == 0) +// { +// ++once; +// post ("x->x_slew = %f, x->x_bw = %f, sp[0]->s_sr = %f", x->x_slew, x->x_bw, sp[0]->s_sr); +// } + dsp_add(sqosc_perform, 4, x, sp[0]->s_vec, sp[1]->s_vec, sp[0]->s_n); +} + +static void sqosc_ft1(t_sqosc *x, t_float f) +{ + x->x_phase = SQOSCTABSIZE * f; +} + +static void sqosc_pw(t_sqosc *x, t_float pw) +{ + if ((pw <= 0)||(pw >= 1)) return; + //post("sqosc: pulse width must be greater than 0 and less than 1");// this is an annoying message... + x->x_pw = pw * SQOSCTABSIZE; +} + +void sqosc_tilde_setup(void) +{ + sqosc_class = class_new(gensym("sqosc~"), (t_newmethod)sqosc_new, 0, + sizeof(t_sqosc), 0, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, 0); + CLASS_MAINSIGNALIN(sqosc_class, t_sqosc, x_f);/* x_f is used when no signal is input */ + class_addmethod(sqosc_class, (t_method)sqosc_dsp, gensym("dsp"), 0); + class_addmethod(sqosc_class, (t_method)sqosc_ft1, gensym("ft1"), A_FLOAT, 0); + class_addmethod(sqosc_class, (t_method)sqosc_pw, gensym("pw"), A_FLOAT, 0); + + sqosc_maketable(); /* make the same table as cos_table */ +} + + +/* end of sqosc.c */ -- cgit v1.2.1