/* ------------------------- shoutamp~ ---------------------------------------- */ /* */ /* Tilde object to receive an mp3-stream from a shoutcast/icecast server. */ /* Written by Olaf Matthes */ /* Graphical buffer status display by Yves Degoyon . */ /* Get source at http://www.akustische-kunst.org/puredata/shout/ */ /* */ /* This library is free software; you can redistribute it and/or */ /* modify it under the terms of the GNU Lesser General Public */ /* License as published by the Free Software Foundation; either */ /* version 2 of the License, or (at your option) any later version. */ /* */ /* This library 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 */ /* Lesser General Public License for more details. */ /* */ /* You should have received a copy of the GNU Lesser General Public */ /* License along with this library; 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. */ /* Uses the LAME MPEG 1 Layer 3 encoding library which can be found at */ /* http://www.mp3dev.org */ /* */ /* ---------------------------------------------------------------------------- */ /* Pd includes */ #include "m_pd.h" #include "g_canvas.h" /* mpg123 includes (from Lame 3.92) */ #include "mpg123.h" #include "mpglib.h" #include "interface.h" #include #include #include #include #include #include #include #ifdef UNIX #include #include #include #include #include #include #include #include #define SOCKET_ERROR -1 #else #include /* for 'write' in pute-function only */ #include #include #endif #ifdef NT #pragma warning( disable : 4244 ) #pragma warning( disable : 4305 ) #endif #ifndef NT #define sys_closesocket close #define STRDUP strdup #else /* if NT */ #define sys_closesocket closesocket #define STRDUP _strdup #endif /************************* shoutamp~ object ******************************/ /* Each instance of shoutamp~ owns a "child" thread for doing the data transfer. The parent thread signals the child each time: (1) a connection wants opening or closing; (2) we've eaten another 1/16 of the shared buffer (so that the child thread should check if it's time to receive some more.) The child signals the parent whenever a receive has completed. Signalling is done by setting "conditions" and putting data in mutex-controlled common areas. */ #define REQUEST_NOTHING 0 #define REQUEST_CONNECT 1 #define REQUEST_CLOSE 2 #define REQUEST_QUIT 3 #define REQUEST_BUSY 4 #define REQUEST_DATA 5 #define REQUEST_RECONNECT 6 #define STATE_IDLE 0 #define STATE_STARTUP 1 /* connecting and filling the buffer */ #define STATE_STREAM 2 /* streaming aund audio output */ #define READSIZE 131072 /* size of mp3 stream receive buffer */ #define READ 4096 /* amount of mp3 data we pass on to decoder */ #define DECODE_PACKET_SIZE 32768 /* max size of the data returned by mpglib : 32k */ #define DEFBUFPERCHAN 262144 /* audio output buffer default: 256k */ #define MINBUFSIZE (2 * DECODE_PACKET_SIZE) /* minimum buffer size (64k) */ #define MAXBUFSIZE 16777216 /* arbitrary; just don't want to hang malloc */ #define STRBUF_SIZE 1024 /* char received from server on startup */ #define OBJWIDTH 68 /* width of buffer status display */ #define OBJHEIGHT 10 /* height of buffer status display */ #define MAXSTREAMCHANS 2 /* maximum number of channels: restricted to 2 by now */ #define MAX_DECODERS 10 /* maximum number of decoders / object instances */ /* useful debugging functions from mpglib */ extern int decode_header( struct frame* fr, unsigned long newhead ); extern void print_header_compact( struct frame* fr ); extern int head_check( unsigned long head, int check_layer ); static char *shoutamp_version = "shoutamp~: mp3 streaming client v0.5, written by Olaf Matthes"; /* too bad, this needs to be static, handling an array to enable several decoders in pd */ static MPSTR mps[MAX_DECODERS]; /* decoder buffer */ int nbinstances = 0; extern const long freqs[9]; /* the samplerate taken from mpg123 */ static t_class *shoutamp_class; typedef struct _shoutamp { t_object x_obj; t_int x_instance; /* instance of the object */ t_canvas *x_canvas; /* remember canvas */ t_outlet *x_connection; t_clock *x_clock; t_float *x_buf; /* audio data buffer (at samplerate of stream) */ t_float *x_rsbuf; /* resampled audio data buffer */ t_int x_bufsize; /* buffer size in bytes */ t_int x_noutlets; /* number of audio outlets */ t_sample *(x_outvec[MAXSTREAMCHANS]); /* audio vectors */ t_int x_vecsize; /* vector size for transfers */ t_int x_state; /* opened, running, or idle */ char x_out[DECODE_PACKET_SIZE]; /* buffer for decoded PCM samples */ /* parameters to communicate with subthread */ t_int x_requestcode; /* pending request from parent to I/O thread */ t_int x_connecterror; /* slot for "errno" return */ t_int x_streamchannels; /* number of channels in Ogg mpg123 bitstream */ t_int x_streamrate; /* sample rate of stream */ /* buffer stuff */ t_int x_fifosize; /* buffer size appropriately rounded down */ t_int x_fifohead; /* index of next byte to get from file */ t_int x_fifotail; /* index of next byte the ugen will read */ t_int x_fifobytes; /* number of bytes available in buffer */ t_int x_eof; /* true if ogg stream has ended */ t_int x_sigcountdown; /* counter for signalling child for more data */ t_int x_sigperiod; /* number of ticks per signal */ t_int x_siginterval; /* number of times per buffer (depends on data rate) */ /* mpg123 related stuff */ t_int x_eos; /* end of stream */ t_int x_bitrateindex; /* bitrate index for each frame, might change dynamically */ t_int x_packetsize; /* size of the packets */ char x_mp3buffer[READSIZE]; /* input buffer for mpg123 */ t_int x_mp3inpos; /* writing position in mp3 stream buffer */ t_int x_mpg123; /* info about encoder status */ t_int x_verbose; /* turn mpg123 error printout on or off */ t_int x_connectstate; /* indicates the state of socket connection */ t_int x_fd; /* the socket number */ t_int x_graphic; /* indicates if we show a graphic bar */ t_int x_resample; /* indicates if we need to resample signal (1 = no resampling) */ t_int x_recover; /* indicate how to behave on buffer underruns */ t_int x_disconnect; /* indicates that user want's to disconnect */ t_int x_samplerate; /* Pd's sample rate */ t_int x_bitrate; /* server stuff */ char *x_hostname; /* name or IP of host to connect to */ char *x_mountpoint; /* mountpoint of ogg-bitstream */ t_int x_port; /* port number on which the connection is made */ /* tread stuff */ pthread_mutex_t x_mutex; pthread_cond_t x_requestcondition; pthread_cond_t x_answercondition; pthread_t x_childthread; } t_shoutamp; /* some prototypes */ static void shoutamp_child_disconnect(t_int fd); /* check if we can read from socket */ static int shoutamp_check_for_data(t_int sock) { fd_set set; struct timeval tv; t_int ret; tv.tv_sec = 0; tv.tv_usec = 20000; FD_ZERO(&set); FD_SET(sock, &set); ret = select(sock + 1, &set, NULL, NULL, &tv); if (ret > 0) return 1; return 0; } /* receive 'size' bytes from server */ static int shoutamp_child_receive(int fd, char *rcvbuffer, int size) { int ret = -1; int i; ret = recv(fd, rcvbuffer, size, 0); if(ret < 0) { post("shoutamp~: receive error" ); } return ret; } static int strip_shout_header(char *head, int n) { int i; for (i = 0; i < (n - 2); i++) { if (head[i] == 10 && head[i + 1] == 13) break; if (head[i] == '\n' && head[i + 1] == '\n') break; } head[i + 1] = '\0'; return n - (i + 1); } static int strip_ice_header(char *head, int n) { int i; for (i = 0; i < (n - 2); i++) { if ((head[i] == '\n') && (head[i + 1] == '\n')) break; } head[i + 1] = '\0'; return n - (i + 1); } /* mpg123 decoder setup */ static int shoutamp_mpg123_init(t_shoutamp *x, int fd) { int offset, bytes; float resample = 0; struct frame hframe; unsigned int a,b,c,d; unsigned long cheader; InitMP3(&mps[x->x_instance]); /* receive some bytes and check them */ bytes = shoutamp_child_receive(fd, x->x_mp3buffer, READ); if(bytes < 0) { return (bytes); } offset=0; /* search for an header to check dynamic bitrate */ while(offset < READ - 4) { /* decode first 4 bytes as the header */ a = *((unsigned char*)x->x_mp3buffer+offset); b = *((unsigned char*)x->x_mp3buffer+offset+1); c = *((unsigned char*)x->x_mp3buffer+offset+2); d = *((unsigned char*)x->x_mp3buffer+offset+3); cheader = 0; cheader = a; cheader <<= 8; cheader |= b; cheader <<= 8; cheader |= c; cheader <<= 8; cheader |= d; if ( head_check( cheader, 0 ) ) { decode_header( &hframe, cheader ); print_header_compact( &hframe ); x->x_packetsize = hframe.framesize+sizeof(unsigned long); x->x_streamchannels = hframe.stereo; /* how many channels ? */ if ( hframe.framesize == 0 ) { post( "shoutamp~: weird header ( frame size = 0 ) .... ignored" ); offset++; continue; } // when the bitrate change, reinit decoder x->x_bitrateindex = hframe.bitrate_index; /* setting for resampling */ x->x_streamrate = freqs[hframe.sampling_frequency]; if(x->x_streamrate != x->x_samplerate) { if(x->x_streamrate > 0) resample = (float)x->x_samplerate / (float)x->x_streamrate; if(resample == 1.0)x->x_resample = 1; else if(resample == 2.0)x->x_resample = 2; else if(resample == 4.0)x->x_resample = 4; else if(resample == 3.0)x->x_resample = 3; else x->x_resample = -1; if(x->x_resample==-1) { post("shoutamp~: resampling from %ld Hz to %d not supported", x->x_streamrate, x->x_samplerate ); return -1; } else post("shoutamp~: resampling from %ld Hz to %d Hz", x->x_streamrate, x->x_samplerate ); } else x->x_resample = 1; break; } offset++; } x->x_mp3inpos = READ - offset; /* throw away first few bytes */ memcpy(x->x_mp3buffer, x->x_mp3buffer+offset, x->x_mp3inpos); /* move start of header to start of buffer */ /* read next 4k of data */ bytes = shoutamp_child_receive(fd, x->x_mp3buffer+x->x_mp3inpos, READ); if(bytes < 0) { x->x_eos = 1; /* indicate end of stream */ return (bytes); } else x->x_mp3inpos += bytes; x->x_mpg123 = 1; /* indicate that mpg123 is OK */ return (1); } /* deinit the mpg123 decoder */ static void shoutamp_mpg123_deinit(t_shoutamp *x) { if(x->x_mpg123) { x->x_mpg123 = 0; ExitMP3(&mps[x->x_instance]); post("shoutamp~: decoder deinitialised"); } } /* decode ogg/mpg123 and receive new data */ static int shoutamp_decode_input(t_shoutamp *x, float *buf, int fifohead, int fifosize, int fd) { int i, j, ch, ret; signed short int *p = (signed short int *) x->x_out; int samples = 0;/* number of samples returned by decoder at each block! */ int channels; /* number of channels */ int n = 0; /* total number of samples returned by decoder at this call */ int bytes; /* number of bytes submitted to decoder */ int position = fifohead; int pbytes; struct frame hframe; unsigned int a,b,c,d; unsigned long cheader; int offset; while(x->x_mp3inpos > READ) /* at least two packets in buffer */ { offset = 0; /* search for an header to check dynamic bitrate */ while(offset < x->x_mp3inpos - 4) { /* decode first 4 bytes as the header */ a = *((unsigned char*)x->x_mp3buffer+offset); b = *((unsigned char*)x->x_mp3buffer+offset+1); c = *((unsigned char*)x->x_mp3buffer+offset+2); d = *((unsigned char*)x->x_mp3buffer+offset+3); cheader = 0; cheader = a; cheader <<= 8; cheader |= b; cheader <<= 8; cheader |= c; cheader <<= 8; cheader |= d; if(head_check( cheader, 0 )) /* is this a header ? */ { decode_header(&hframe, cheader); x->x_packetsize = hframe.framesize + (int)sizeof(unsigned long); if(hframe.framesize == 0) { post( "shoutamp~: weird header ( frame size = 0 ) .... ignored" ); offset++; continue; } // print_header_compact( &hframe ); /* when the bitrate changes, reinit decoder */ if(x->x_bitrateindex != hframe.bitrate_index) { post("shoutamp~: reinitialize decoder (bitrate changed)"); ExitMP3(&mps[x->x_instance]); InitMP3(&mps[x->x_instance]); x->x_bitrateindex = hframe.bitrate_index; } break; } offset++; } ret = decodeMP3(&mps[x->x_instance], (unsigned char*)(x->x_mp3buffer+offset), x->x_packetsize, (char *)p, sizeof(x->x_out), &pbytes); channels = mps[x->x_instance].fr.stereo; /* get current number of channels */ switch (ret) { case MP3_OK: switch (channels) { case 1: case 2: samples = ((channels==1)?pbytes >> 1 : pbytes >> 2); /* copy decoded PCM into our output buffer */ pthread_mutex_lock(&x->x_mutex); for(j = 0; j < samples; j++) { for(ch = 0; ch < channels; ch++) { buf[position] = ((t_float)(*p++))/32767.0; position++; } if (position >= fifosize) /* check for buffer boundaries */ position = 0; } pthread_mutex_unlock(&x->x_mutex); n += (samples * channels); /* count bytes we wrote to buffer */ p -= (samples * channels); /* point to start of buffer again */ /* move unused data to start of buffer ("roll buffer") */ if(x->x_mp3inpos > x->x_packetsize) { x->x_mp3inpos -= x->x_packetsize + offset; /* move one frame */ memcpy( (void *)(x->x_mp3buffer), (void *)(x->x_mp3buffer+x->x_packetsize + offset), x->x_mp3inpos); } else /* sorry, it will be ignored */ { /* error( "shoutamp~: incomplete frame...ignored"); x->x_mp3inpos = 0; */ goto receive; } break; default: samples = 0; /* nothing added to buffer */ break; } break; case MP3_NEED_MORE: if(mps[x->x_instance].framesize == 0 && mps[x->x_instance].fsizeold != 0) { post( "shoutamp~: decoding done"); } else { post( "shoutamp~: retry lame decoding (more data needed)" ); goto receive; } break; case MP3_ERR: post( "shoutamp~: lame decoding failed" ); return (-1); break; } } receive: /* read data from socket */ if(x->x_mp3inpos < READSIZE - READ) /* any space in buffer ? */ { if(!x->x_eos) /* read data from input */ { /* read next 4k of data out of buffer */ bytes = shoutamp_child_receive(fd, x->x_mp3buffer+x->x_mp3inpos, READ); if(bytes < 0) { x->x_eos = 1; /* indicate end of stream */ return (bytes); } else x->x_mp3inpos += bytes; } else /* we read through all the file... */ { /* will have to reinit decoder for new file */ post("shoutamp~: end of stream detected"); return (0); } } else /* no? - drop the whole thing */ { post("shoutamp~: mp3 stream buffer dropped"); x->x_mp3inpos = 0; shoutamp_mpg123_init(x, fd); } // post("n = %d", n); return (n); } /* connect to shoutcast server */ static int shoutamp_child_connect(char *hostname, char *mountpoint, t_int portno) { struct sockaddr_in server; struct hostent *hp; /* variables used for communication with server */ char *sptr = NULL; char request[STRBUF_SIZE]; /* string to be send to server */ char *url; /* used for relocation */ char* bcname; /* name of broadcast */ char* bcurl; /* url of broadcast */ char* bcgenre; /* genre of broadcast */ char* bcaim; /* aim of broadcast */ fd_set fdset; struct timeval tv; t_int sockfd; /* socket to server */ t_int relocate, numrelocs = 0; t_int i, ret, rest, nanswers=0; char *cpoint = NULL; t_int eof = 0; sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd < 0) { error("shoutamp~: internal error while attempting to open socket"); return (-1); } /* connect socket using hostname provided in command line */ server.sin_family = AF_INET; hp = gethostbyname(hostname); if (hp == 0) { post("shoutamp~: bad host?"); sys_closesocket(sockfd); return (-1); } memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); /* assign client port number */ server.sin_port = htons((unsigned short)portno); /* check mountpoint :: SHOUcast does not support mountpoints */ if(strstr(mountpoint, "listen.pls") || strstr(mountpoint, "/")) { mountpoint = ""; /* the '/' is not part of the mountpoint !!! */ } /* try to connect. */ post("shoutamp~: connecting to http://%s:%d/%s", hostname, portno, mountpoint); if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) { error("shoutamp~: connection failed!\n"); sys_closesocket(sockfd); return (-1); } /* sheck if we can read/write from/to the socket */ FD_ZERO( &fdset); FD_SET( sockfd, &fdset); tv.tv_sec = 0; /* seconds */ tv.tv_usec = 500; /* microseconds */ ret = select(sockfd + 1, &fdset, NULL, NULL, &tv); if(ret != 0) { error("shoutamp~: can not read from socket"); sys_closesocket(sockfd); return (-1); } /* build up stuff we need to send to server */ sprintf(request, "GET /%s HTTP/1.0 \r\nHost: %s\r\nUser-Agent: shoutamp~ 0.5\r\nAccept: */*\r\n\r\n", mountpoint, hostname); if(send(sockfd, request, strlen(request), 0) < 0) /* say hello to server */ { post("shoutamp~: could not contact server... "); #ifndef NT perror("send"); #endif return (-1); } relocate = FALSE; if( recv(sockfd, request, STRBUF_SIZE, 0)<0) { error("shoutamp~: no response from server"); #ifndef NT perror( "recv" ); #endif shoutamp_child_disconnect(sockfd); return (-1); } /* time to parse content of the response... */ if(strstr(request, "audio/x-scpls") ) /* SHOUTcast playlist */ { /* LATER: parse playlist and retry */ post("shoutamp~: SHOUTcast server returned a playlist, quitting"); shoutamp_child_disconnect(sockfd); return (-1); } if(strstr(request, "HTTP")) /* seems to be IceCast server */ { strip_ice_header(request, STRBUF_SIZE); if(sptr = strstr(request, "302")) { cpoint = strstr(request, "Location:"); url = STRDUP(cpoint + 10); post("shoutamp~: relocation requested, try \"%s\" instead", url); shoutamp_child_disconnect(sockfd); // return (shoutamp_connect_url(x, gensym(url))); relocate = TRUE; return (-1); } if(!(sptr = strstr(request, "200")) && !relocate ) { error("shoutamp~: cannot connect to the (default) stream"); shoutamp_child_disconnect(sockfd); return (-1); } post("shoutamp~: IceCast server detected"); /* eventually read the rest of server's announcement */ while((!(cpoint = strstr(request, "x-audiocast-public") ) || !(cpoint = strstr(request, "x-audiocast-mount") ) || !(cpoint = strstr(request, "x-audiocast-server-url") ) || !(cpoint = strstr(request, "x-audiocast-location") ) || !(cpoint = strstr(request, "x-audiocast-admin") ) || !(cpoint = strstr(request, "x-audiocast-name") ) || !(cpoint = strstr(request, "x-audiocast-genre") ) || !(cpoint = strstr(request, "x-audiocast-url") ) || !(cpoint = strstr(request, "x-audiocast-public") ) || !(cpoint = strstr(request, "x-audiocast-bitrate") ) ) && ( ret < STRBUF_SIZE ) ) { if((rest = recv(sockfd, request+ret, STRBUF_SIZE-ret, 0) ) < 0 ) { error("shoutamp~: no response from server"); #ifndef NT perror("recv"); #endif shoutamp_child_disconnect(sockfd); return(-1); } ret += rest; } // post("shoutamp~: server's header : %s", request ); /* check what we got */ if(cpoint = strstr(request, "x-audiocast-mount:")) { mountpoint = STRDUP(cpoint + 18); for ( i=0; i<(int)strlen(mountpoint); i++ ) { if(mountpoint[i] == '\n' ) { mountpoint[i] = '\0'; break; } } post(" mountpoint: %s", mountpoint); } if(cpoint = strstr(request, "x-audiocast-server-url:")) { sptr = STRDUP( cpoint + 24); for ( i=0; i<(int)strlen(sptr); i++ ) { if ( sptr[i] == '\n' ) { sptr[i] = '\0'; break; } } post(" server-url: %s", sptr); } if(cpoint = strstr(request, "x-audiocast-location:")) { sptr = STRDUP( cpoint + 22); for ( i=0; i<(int)strlen(sptr); i++ ) { if ( sptr[i] == '\n' ) { sptr[i] = '\0'; break; } } post(" location: %s", sptr); } if(cpoint = strstr(request, "x-audiocast-admin:")) { sptr = STRDUP( cpoint + 19); for ( i=0; i<(int)strlen(sptr); i++ ) { if ( sptr[i] == '\n' ) { sptr[i] = '\0'; break; } } post(" admin: %s", sptr); } if(cpoint = strstr(request, "x-audiocast-name:")) { bcname = STRDUP( cpoint + 17); for ( i=0; i<(int)strlen(bcname); i++ ) { if ( bcname[i] == '\n' ) { bcname[i] = '\0'; break; } } post(" name: %s", bcname); } if(cpoint = strstr(request, "x-audiocast-genre:")) { bcgenre = STRDUP( cpoint + 18); for ( i=0; i<(int)strlen(bcgenre); i++ ) { if ( bcgenre[i] == '\n' ) { bcgenre[i] = '\0'; break; } } post(" genre: %s", bcgenre); } if(cpoint = strstr(request, "x-audiocast-url:")) { bcurl = STRDUP( cpoint + 16); for ( i=0; i<(int)strlen(bcurl); i++ ) { if ( bcurl[i] == '\n' ) { bcurl[i] = '\0'; break; } } post(" url: %s", bcurl); } if(cpoint = strstr(request, "x-audiocast-public:1")) { post(" broadcast is public"); } else if(cpoint = strstr(request, "x-audiocast-public:0")) { post(" broadcast is NOT public"); } if(cpoint = strstr(request, "x-audiocast-bitrate:")) { sptr = STRDUP( cpoint + 20); for ( i=0; i<(int)strlen(sptr); i++ ) { if ( sptr[i] == '\n' ) { sptr[i] = '\0'; break; } } post(" bitrate: %s", sptr); } if(cpoint = strstr(request, "x-audiocast-udpport:")) { post("shoutamp~: sorry, server wants UDP connection!"); return (-1); } } else /* it is a SHOUTcast server */ { strip_shout_header (request, STRBUF_SIZE); post("shoutamp~: SHOUTcast server detected"); if(strncmp(request, "ICY 401", 7) == 0) { post("shoutamp~: ICY 401 Service Unavailable"); return (-1); } if(strncmp(request, "ICY 200 OK", 10) == 0) { /* recv and decode info about broadcast line by line */ post("shoutamp~: connecting to stream..."); i = ret; /* break response down to single lines */ while(i < STRBUF_SIZE - 1) { for(rest = ret; rest < STRBUF_SIZE - 1; rest++) { if(recv(sockfd, request+rest, 1, 0)!=1) break; if (request[rest] == '\n') break; if (request[rest] == '\r') break; i++; } request[rest] = '\0'; /* check what we got */ if(!strncmp(request, "icy-name:", 9)) { bcname = STRDUP(request + 9); post(" name: %s", bcname); } else if(!strncmp(request, "x-audiocast-name:", 17)) { bcname = STRDUP(request + 17); post(" name: %s", bcname); } if(!strncmp(request, "icy-genre:", 9)) { bcgenre = STRDUP(request + 10); post(" genre: %s", bcgenre); } if(!strncmp(request, "icy-aim:", 7)) { bcaim = STRDUP(request + 8); post(" aim: %s", bcaim); } if(!strncmp(request, "icy-url:", 7)) { bcurl = STRDUP(request + 8); post(" url: %s", bcurl); } if(!strncmp(request, "icy-pub:1", 8)) { post(" broadcast is public"); } else if(!strncmp(request, "icy-pub:0", 8)) { post(" broadcast is NOT public"); } if(!strncmp(request, "icy-br:", 6)) { sptr = STRDUP(request + 7); post(" bitrate: %s", sptr); } if(!strncmp(request, "x-audiocast-udpport:", 20)) { post("shoutamp~: sorry, server wants UDP connection!"); return (-1); } } } else { post("shoutamp~: unknown response from server"); return (-1); } relocate = FALSE; } if(relocate) { error("shoutamp~: too many HTTP relocations"); return (-1); } post("shoutamp~: connected to http://%s:%d/%s", hp->h_name, portno, mountpoint); return (sockfd); } static void shoutamp_child_dographics(t_shoutamp *x) { /* do graphics stuff :: create rectangle */ if ( x->x_graphic && glist_isvisible( x->x_canvas ) ) { sys_vgui(".x%x.c create rectangle %d %d %d %d -fill lightblue -tags %xPBAR\n", x->x_canvas, x->x_obj.te_xpix, x->x_obj.te_ypix-OBJHEIGHT-1, x->x_obj.te_xpix + OBJWIDTH, x->x_obj.te_ypix - 1, x ); } } static void shoutamp_child_updategraphics(t_shoutamp *x) { /* update buffer status display */ if(x->x_graphic && glist_isvisible(x->x_canvas)) { /* update graphical read status */ char color[32]; sys_vgui(".x%x.c delete rectangle %xSTATUS\n", x->x_canvas, x); if(x->x_fifobytes < (x->x_fifosize / 8)) { strcpy(color, "red"); } else { strcpy(color, "lightgreen"); } sys_vgui(".x%x.c create rectangle %d %d %d %d -fill %s -tags %xSTATUS\n", x->x_canvas, x->x_obj.te_xpix, x->x_obj.te_ypix-OBJHEIGHT-1, x->x_obj.te_xpix+((x->x_fifobytes*OBJWIDTH)/x->x_fifosize), x->x_obj.te_ypix - 1, color, x); } } static void shoutamp_child_delgraphics(t_shoutamp *x) { if(x->x_graphic) /* delete graphics */ { sys_vgui(".x%x.c delete rectangle %xPBAR\n", x->x_canvas, x ); sys_vgui(".x%x.c delete rectangle %xSTATUS\n", x->x_canvas, x ); } } static void shoutamp_child_disconnect(t_int fd) { sys_closesocket(fd); post("shoutamp~: connection closed"); } /************** the child thread which performs data I/O ***********/ #if 0 /* set this to get debugging output */ static void pute(char *s) /* debug routine */ { write(2, s, strlen(s)); } #else #define pute(x) #endif #if 1 #define shoutamp_cond_wait pthread_cond_wait #define shoutamp_cond_signal pthread_cond_signal #else #include /* debugging version... */ #include static void shoutamp_fakewait(pthread_mutex_t *b) { struct timeval timout; timout.tv_sec = 0; timout.tv_usec = 1000000; pthread_mutex_unlock(b); select(0, 0, 0, 0, &timout); pthread_mutex_lock(b); } void shoutamp_banana( void) { struct timeval timout; timout.tv_sec = 0; timout.tv_usec = 200000; pute("banana1\n"); select(0, 0, 0, 0, &timout); pute("banana2\n"); } #define shoutamp_cond_wait(a,b) shoutamp_fakewait(b) #define shoutamp_cond_signal(a) #endif static void *shoutamp_child_main(void *zz) { t_shoutamp *x = zz; pute("1\n"); pthread_mutex_lock(&x->x_mutex); while (1) { int fd, fifohead; char *buffer; /* mp3 data */ float *buf; /* encoded PCM floats */ pute("0\n"); if (x->x_requestcode == REQUEST_NOTHING) { pute("wait 2\n"); shoutamp_cond_signal(&x->x_answercondition); shoutamp_cond_wait(&x->x_requestcondition, &x->x_mutex); pute("3\n"); } // connect to SHOUTcast or Icecast server else if (x->x_requestcode == REQUEST_CONNECT) { char boo[80]; int sysrtn, wantbytes; /* copy connect stuff out of the data structure so we can relinquish the mutex while we're in oggcast_child_connect(). */ char *hostname = x->x_hostname; char *mountpoint = x->x_mountpoint; t_int portno = x->x_port; x->x_disconnect = 0; /* alter the request code so that an ensuing "open" will get noticed. */ pute("4\n"); x->x_requestcode = REQUEST_BUSY; x->x_connecterror = 0; /* if there's already a connection open, close it */ if (x->x_fd >= 0) { fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); shoutamp_child_disconnect(fd); pthread_mutex_lock(&x->x_mutex); x->x_connectstate = 0; clock_delay(x->x_clock, 0); x->x_fd = -1; if (x->x_requestcode != REQUEST_BUSY) goto lost; } /* open the socket with the mutex unlocked */ pthread_mutex_unlock(&x->x_mutex); fd = shoutamp_child_connect(hostname, mountpoint, portno); pthread_mutex_lock(&x->x_mutex); pute("5\n"); /* copy back into the instance structure. */ x->x_connectstate = 1; clock_delay(x->x_clock, 0); x->x_fd = fd; if (fd < 0) { x->x_connecterror = fd; x->x_eof = 1; x->x_connectstate = 0; clock_delay(x->x_clock, 0); pute("connect failed\n"); goto lost; } else { /* initialise the decoder */ if(shoutamp_mpg123_init(x, fd) != -1) { post("shoutamp~: decoder initialised"); shoutamp_child_dographics(x); } else { post("shoutamp~: could not init decoder"); shoutamp_child_disconnect(fd); post("shoutamp~: connection closed due to bitstream error"); x->x_disconnect = 1; x->x_fd = -1; x->x_eof = 1; x->x_connectstate = 0; clock_delay(x->x_clock, 0); pute("initialisation failed\n"); goto lost; } } /* check if another request has been made; if so, field it */ if (x->x_requestcode != REQUEST_BUSY) goto lost; pute("6\n"); x->x_fifohead = fifohead = 0; /* set fifosize from bufsize. fifosize must be a multiple of the number of bytes eaten for each DSP tick. We pessimistically assume MAXVECSIZE samples per tick since that could change. There could be a problem here if the vector size increases while a stream is being played... */ x->x_fifosize = x->x_bufsize - (x->x_bufsize % (x->x_streamchannels * 2)); /* arrange for the "request" condition to be signalled x->x_siginterval times per buffer */ sprintf(boo, "fifosize %d\n", x->x_fifosize); pute(boo); x->x_sigcountdown = x->x_sigperiod = (x->x_fifosize / (x->x_siginterval * x->x_streamchannels * x->x_vecsize)); /* in a loop, wait for the fifo to get hungry and feed it */ while (x->x_requestcode == REQUEST_BUSY) { int fifosize = x->x_fifosize; buf = x->x_buf; pute("77\n"); if (x->x_eof) break; /* try to get new data from decoder whenever there is some space at end of buffer */ if(x->x_fifobytes < fifosize - DECODE_PACKET_SIZE) { sprintf(boo, "head %d, tail %d\n", x->x_fifohead, x->x_fifotail); pute(boo); /* we pass x on to the routine since we need the mpg123 stuff to be presend. all other values should not be changed because mutex is unlocked ! */ pute("decode... "); pthread_mutex_unlock(&x->x_mutex); sysrtn = shoutamp_decode_input(x, buf, fifohead, fifosize, fd); shoutamp_child_updategraphics(x); pthread_mutex_lock(&x->x_mutex); if (x->x_requestcode != REQUEST_BUSY) break; if (sysrtn == 0) { if (x->x_eos && !x->x_disconnect) /* got end of stream */ { pute("end of stream\n"); shoutamp_mpg123_deinit(x); if(shoutamp_mpg123_init(x, fd) == -1) /* reinit stream */ { x->x_state = STATE_IDLE; x->x_disconnect = 1; goto quit; } } else if (x->x_eos && x->x_disconnect) /* we're disconnecting */ { x->x_state = STATE_IDLE; pute("end of stream: disconnecting\n"); x->x_connectstate = 0; clock_delay(x->x_clock, 0); break; /* go to disconnect */ } } else if (sysrtn < 0) /* got any other error from decoder */ { pute("connecterror\n"); x->x_connecterror = sysrtn; x->x_state = STATE_IDLE; x->x_connectstate = 0; clock_delay(x->x_clock, 0); break; } x->x_fifohead = (fifohead + sysrtn) % fifosize; x->x_fifobytes += sysrtn; sprintf(boo, "after: head %d, tail %d\n", x->x_fifohead, x->x_fifotail); pute(boo); /* check wether the buffer is filled enough to start streaming */ } else /* there is enough data in the buffer :: do nothing */ { x->x_state = STATE_STREAM; pute("wait 7...\n"); shoutamp_cond_signal(&x->x_answercondition); shoutamp_cond_wait(&x->x_requestcondition, &x->x_mutex); pute("7 done\n"); continue; } pute("8\n"); fd = x->x_fd; buf = x->x_buf; fifohead = x->x_fifohead; pthread_mutex_unlock(&x->x_mutex); /* signal parent in case it's waiting for data */ shoutamp_cond_signal(&x->x_answercondition); } lost: if (x->x_requestcode == REQUEST_BUSY) x->x_requestcode = REQUEST_NOTHING; /* fell out of read loop: close connection if necessary, set EOF and signal once more */ if (x->x_fd >= 0) { fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); shoutamp_mpg123_deinit(x); shoutamp_child_disconnect(fd); pthread_mutex_lock(&x->x_mutex); x->x_connectstate = 0; clock_delay(x->x_clock, 0); x->x_fd = -1; shoutamp_child_delgraphics(x); } shoutamp_cond_signal(&x->x_answercondition); } /* reconnect to server */ else if (x->x_requestcode == REQUEST_RECONNECT) { if (x->x_fd >= 0) { fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); shoutamp_mpg123_deinit(x); shoutamp_child_disconnect(fd); pthread_mutex_lock(&x->x_mutex); shoutamp_child_delgraphics(x); x->x_fd = -1; } /* connect again */ x->x_requestcode = REQUEST_CONNECT; x->x_connectstate = 0; clock_delay(x->x_clock, 0); shoutamp_cond_signal(&x->x_answercondition); } /* close connection to server (disconnect) */ else if (x->x_requestcode == REQUEST_CLOSE) { quit: if (x->x_fd >= 0) { fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); shoutamp_mpg123_deinit(x); shoutamp_child_disconnect(fd); pthread_mutex_lock(&x->x_mutex); shoutamp_child_delgraphics(x); x->x_fd = -1; } if (x->x_requestcode == REQUEST_CLOSE) x->x_requestcode = REQUEST_NOTHING; else if (x->x_requestcode == REQUEST_BUSY) x->x_requestcode = REQUEST_NOTHING; else if (x->x_requestcode == REQUEST_CONNECT) x->x_requestcode = REQUEST_NOTHING; x->x_connectstate = 0; clock_delay(x->x_clock, 0); shoutamp_cond_signal(&x->x_answercondition); } // quit everything else if (x->x_requestcode == REQUEST_QUIT) { if (x->x_fd >= 0) { fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); shoutamp_mpg123_deinit(x); shoutamp_child_disconnect(fd); pthread_mutex_lock(&x->x_mutex); shoutamp_child_delgraphics(x); x->x_fd = -1; } x->x_connectstate = 0; clock_delay(x->x_clock, 0); x->x_requestcode = REQUEST_NOTHING; shoutamp_cond_signal(&x->x_answercondition); break; } else { pute("13\n"); } } pute("thread exit\n"); pthread_mutex_unlock(&x->x_mutex); return (0); } /******** the object proper runs in the calling (parent) thread ****/ static void shoutamp_tick(t_shoutamp *x) { outlet_float(x->x_connection, x->x_connectstate); } static void *shoutamp_new(t_floatarg fdographics, t_floatarg fnchannels, t_floatarg fbufsize) { t_shoutamp *x; int nchannels = fnchannels, bufsize = fbufsize * 1024, i; float *buf; if (nchannels < 1) nchannels = 2; /* two channels as default */ else if (nchannels > MAXSTREAMCHANS) nchannels = MAXSTREAMCHANS; /* check / set buffer size */ if (!bufsize) bufsize = DEFBUFPERCHAN * nchannels; else if (bufsize < MINBUFSIZE) bufsize = MINBUFSIZE; else if (bufsize > MAXBUFSIZE) bufsize = MAXBUFSIZE; buf = getbytes(bufsize*sizeof(t_float)); if (!buf) return (0); x = (t_shoutamp *)pd_new(shoutamp_class); if ( nbinstances < MAX_DECODERS ) { x->x_instance = nbinstances++; } else { post( "shoutamp~: cannot create more decoders (memory issues), sorry" ); return NULL; } /* what happens if blacksize changes ? */ x->x_rsbuf = getbytes(sys_getblksize()*sizeof(t_float)*2); for (i = 0; i < nchannels; i++) outlet_new(&x->x_obj, gensym("signal")); x->x_noutlets = nchannels; x->x_connection = outlet_new(&x->x_obj, gensym("float")); x->x_clock = clock_new(x, (t_method)shoutamp_tick); pthread_mutex_init(&x->x_mutex, 0); pthread_cond_init(&x->x_requestcondition, 0); pthread_cond_init(&x->x_answercondition, 0); x->x_vecsize = sys_getblksize(); x->x_disconnect = 0; x->x_state = STATE_IDLE; x->x_canvas = canvas_getcurrent(); x->x_streamchannels = 2; x->x_fd = -1; x->x_buf = buf; x->x_bufsize = bufsize; x->x_siginterval = 16; /* signal 16 times per buffer */ x->x_fifosize = x->x_fifohead = x->x_fifotail = x->x_fifobytes = x->x_requestcode = 0; x->x_verbose = 1; x->x_connectstate = 0; /* indicating state of connection */ x->x_samplerate = x->x_streamrate = sys_getsr(); x->x_resample = 0; x->x_mpg123 = 0; x->x_recover = -1; /* just ignore buffer underruns */ x->x_mp3inpos = 0; /* graphical buffer status display */ x->x_graphic = (int)fdographics; x->x_canvas = canvas_getcurrent(); post(shoutamp_version); post("shoutamp~: set buffer to %dk bytes", bufsize/1024); /* start child thread */ pthread_create(&x->x_childthread, 0, shoutamp_child_main, x); return (x); } /* resample audio data */ static int shoutamp_resample(t_float *indata, int insize, t_float *outdata, int outsize) { if(insize == outsize) /* no resampling needed */ { memcpy(indata, outdata, insize); /* just copy audio data */ } } static t_int *shoutamp_perform(t_int *w) { t_shoutamp *x = (t_shoutamp *)(w[1]); t_int vecsize = x->x_vecsize, noutlets = x->x_noutlets, i, j, r; t_float *fp; t_float *sp = x->x_buf; t_float *buffer = x->x_rsbuf; /* buffer for resampled data */ t_float resample = x->x_resample; t_int skip = (t_int)(1.0 / resample); if (x->x_state == STATE_STREAM) { t_int wantbytes, getbytes, havebytes, nchannels, streamchannels = x->x_streamchannels; pthread_mutex_lock(&x->x_mutex); /* get 'getbytes' bytes from input buffer, convert them to 'wantbytes' which is the number of bytes after resampling */ getbytes = streamchannels * vecsize; /* number of bytes we need to get after resampling */ wantbytes = (t_float)getbytes / resample; /* we need vecsize bytes per channel */ havebytes = x->x_fifobytes; /* check for error */ if(havebytes < wantbytes) { // post("shoutamp~: found error"); if(x->x_connecterror) { /* report error and close connection */ pd_error(x, "dsp: error %d", x->x_connecterror); x->x_state = STATE_IDLE; x->x_requestcode = REQUEST_CLOSE; x->x_disconnect = 1; shoutamp_cond_signal(&x->x_requestcondition); pthread_mutex_unlock(&x->x_mutex); } if(!x->x_disconnect) /* it's not due to disconnect */ { if(x->x_recover == 0) /* disconnect */ { x->x_state = STATE_IDLE; x->x_requestcode = REQUEST_CLOSE; x->x_disconnect = 1; shoutamp_cond_signal(&x->x_requestcondition); pthread_mutex_unlock(&x->x_mutex); } else if(x->x_recover == 1) /* reconnect */ { x->x_state = STATE_IDLE; x->x_requestcode = REQUEST_RECONNECT; x->x_disconnect = 1; shoutamp_cond_signal(&x->x_requestcondition); pthread_mutex_unlock(&x->x_mutex); } else /* resume */ { x->x_state = STATE_IDLE; x->x_disconnect = 0; shoutamp_cond_signal(&x->x_requestcondition); pthread_mutex_unlock(&x->x_mutex); } } goto idle; } /* output audio */ sp += x->x_fifotail; /* go to actual audio position */ /* resample if necessary */ if (resample > 1.0) /* upsampling */ { int parent = vecsize / resample; /* how many samples to read from buffer */ for(j = 0; j < parent; j++) { for(i = 0; i < streamchannels; i++) { /* copy same sample several times */ for (r = 0; r < resample; r++) *buffer++ = *sp; sp++; /* get next sample from stream */ } } } else if (resample < 1.0) /* downsampling */ { int parent = vecsize * skip;/* how many samples to read from buffer */ for(j = 0; j < parent; j++) { for(i = 0; i < streamchannels; i++) { *buffer++ = *sp; /* get one sample */ sp += skip; /* skip next few samples in stream */ } } } else if (resample == 1.0) /* no resampling */ { /* copy without any changes */ for(i = 0; i < getbytes; i++)*buffer++ = *sp++; } buffer -= getbytes; /* reset to beginning of buffer */ if(noutlets == streamchannels) { /* normal output */ for(j = 0; j < vecsize; j++) { for(i = 0; i < noutlets; i++) { x->x_outvec[i][j] = *buffer++; } } } else if((noutlets / 2) == streamchannels) { /* mono to stereo conversion */ for(j = 0; j < vecsize; j++) { for(i = 0; i < noutlets; i++) { x->x_outvec[i][j] = *buffer; } buffer++; } } else if((noutlets * 2) == streamchannels) { /* stereo to mono conversion */ for(j = 0; j < vecsize; j++) { for(i = 0; i < streamchannels; i++) { x->x_outvec[i/2][j] += (float) (*buffer++ * 0.5); } } } else goto idle; x->x_fifotail += wantbytes; x->x_fifobytes -= wantbytes; if (x->x_fifotail >= x->x_fifosize) { x->x_fifotail = 0; } /* signal the child thread */ if ((--x->x_sigcountdown) <= 0) { shoutamp_cond_signal(&x->x_requestcondition); x->x_sigcountdown = x->x_sigperiod; } pthread_mutex_unlock(&x->x_mutex); } else { idle: for (i = 0; i < noutlets; i++) for (j = vecsize, fp = x->x_outvec[i]; j--; ) *fp++ = 0; } return (w+2); } static void shoutamp_disconnect(t_shoutamp *x) { /* LATER rethink whether you need the mutex just to set a variable? */ pthread_mutex_lock(&x->x_mutex); x->x_disconnect = 1; x->x_state = STATE_IDLE; x->x_requestcode = REQUEST_CLOSE; shoutamp_cond_signal(&x->x_requestcondition); pthread_mutex_unlock(&x->x_mutex); } /* connect method. Called as: connect */ static void shoutamp_connect(t_shoutamp *x, t_symbol *s, int argc, t_atom *argv) { t_symbol *hostsym = atom_getsymbolarg(0, argc, argv); t_symbol *mountsym = atom_getsymbolarg(1, argc, argv); t_float portno = atom_getfloatarg(2, argc, argv); if (!*hostsym->s_name) /* check for hostname */ return; if (!portno) /* check wether the portnumber is specified */ portno = 8000; /* ...assume port 8000 as standard */ pthread_mutex_lock(&x->x_mutex); if(x->x_fd == -1) { x->x_hostname = hostsym->s_name; x->x_mountpoint = mountsym->s_name; x->x_port = portno; x->x_requestcode = REQUEST_CONNECT; /* empty buffer */ x->x_fifotail = 0; x->x_fifohead = 0; x->x_fifobytes = 0; x->x_mp3inpos = 0; /* also forget all mp3 data */ x->x_streamchannels = 2; x->x_eof = 0; x->x_connecterror = 0; x->x_state = STATE_STARTUP; x->x_disconnect = 0; shoutamp_cond_signal(&x->x_requestcondition); } else post("shoutamp~: already connected"); pthread_mutex_unlock(&x->x_mutex); } /* connect using url like "http://localhost:8000/mountpoint" */ static void shoutamp_connect_url(t_shoutamp *x, t_symbol *url) { char *hname, *port; char *h, *p; char *hostptr; char *r_hostptr; char *pathptr; char *portptr; char *p0; char *defaultportstr = "8000"; t_int stringlength; t_int portno; /* strip http:// or ftp:// */ p = url->s_name; if (strncmp(p, "http://", 7) == 0) p += 7; if (strncmp(p, "ftp://", 6) == 0) p += 6; hostptr = p; while (*p && *p != '/') /* look for end of hostname:port */ p++; p++; /* also skip '/' */ pathptr = p; r_hostptr = --p; while (*p && hostptr < p && *p != ':' && *p != ']') /* split at ':' */ p--; if (!*p || p < hostptr || *p != ':') { portptr = NULL; } else{ portptr = p + 1; r_hostptr = p - 1; } if (*hostptr == '[' && *r_hostptr == ']') { hostptr++; r_hostptr--; } stringlength = r_hostptr - hostptr + 1; h = getbytes(stringlength + 1); if (h == NULL) { hname = NULL; port = NULL; pathptr = NULL; } strncpy(h, hostptr, stringlength); *(h+stringlength) = '\0'; hname = h; /* the hostname */ if (portptr) { stringlength = (pathptr - portptr); if(!stringlength) portptr = NULL; } if (portptr == NULL) { portptr = defaultportstr; stringlength = strlen(defaultportstr); } p0 = getbytes(stringlength + 1); if (p0 == NULL) { freebytes(h, stringlength + 1); hname = NULL; port = NULL; pathptr = NULL; } strncpy(p0, portptr, stringlength); *(p0 + stringlength) = '\0'; for (p = p0; *p && isdigit((unsigned char) *p); p++) ; *p = '\0'; port = (unsigned char *) p0; /* convert port from string to int */ portno = (int)strtol(port, NULL, 10); freebytes(p0, stringlength + 1); /* set values and signal child to connect */ pthread_mutex_lock(&x->x_mutex); if(x->x_fd == -1) { x->x_hostname = hname; x->x_mountpoint = pathptr; x->x_port = portno; x->x_requestcode = REQUEST_CONNECT; x->x_fifotail = 0; x->x_fifohead = 0; x->x_fifobytes = 0; x->x_streamchannels = 2; x->x_eof = 0; x->x_connecterror = 0; x->x_state = STATE_STARTUP; shoutamp_cond_signal(&x->x_requestcondition); } else post("shoutamp~: already connected"); pthread_mutex_unlock(&x->x_mutex); } static void shoutamp_float(t_shoutamp *x, t_floatarg f) { if (f != 0) { pthread_mutex_lock(&x->x_mutex); if(x->x_fd == -1) { x->x_requestcode = REQUEST_CONNECT; x->x_fifotail = 0; x->x_fifohead = 0; x->x_fifobytes = 0; x->x_streamchannels = 2; x->x_eof = 0; x->x_connecterror = 0; x->x_state = STATE_STARTUP; shoutamp_cond_signal(&x->x_requestcondition); } else post("shoutamp~: already connected"); pthread_mutex_unlock(&x->x_mutex); } else shoutamp_disconnect(x); } static void shoutamp_dsp(t_shoutamp *x, t_signal **sp) { int i, noutlets = x->x_noutlets; pthread_mutex_lock(&x->x_mutex); x->x_vecsize = sp[0]->s_n; x->x_sigperiod = (x->x_fifosize / (x->x_siginterval * x->x_streamchannels * x->x_vecsize)); for (i = 0; i < noutlets; i++) x->x_outvec[i] = sp[i]->s_vec; pthread_mutex_unlock(&x->x_mutex); dsp_add(shoutamp_perform, 1, x); } static void shoutamp_print(t_shoutamp *x) { pthread_mutex_lock(&x->x_mutex); if(x->x_fd >= 0) { post("shoutamp~: connected to http://%s:%d/%s", x->x_hostname, x->x_port, x->x_mountpoint); post("shoutamp~: bitstream is %d channels @ %ld Hz with %ldkbps nominal bitrate", x->x_streamchannels, x->x_streamrate, x->x_bitrateindex / 1000); } else post("shoutamp~: not connected"); if(x->x_recover == 0) post("shoutamp~: recover mode set to \"disconnect\" (0)"); else if(x->x_recover == 1) post("shoutamp~: recover mode set to \"reconnect\" (1)"); else if(x->x_recover == -1) post("shoutamp~: recover mode set to \"resume\" (-1)"); pthread_mutex_unlock(&x->x_mutex); } /* set behavior for buffer underruns */ static void shoutamp_recover(t_shoutamp *x, t_floatarg f) { pthread_mutex_lock(&x->x_mutex); if(f <= -1) { /* mute audio and try to fill buffer again: the default */ post("shoutamp~: set recover mode to \"resume\" (-1)"); f = -1; } else if(f >= 1) { /* reconnect to server */ post("shoutamp~: set recover mode to \"reconnect\" (1)"); f = 1; } else { /* disconnect from server */ post("shoutamp~: set recover mode to \"disconnect\" (0)"); f = 0; } x->x_recover = f; pthread_mutex_unlock(&x->x_mutex); } static void shoutamp_free(t_shoutamp *x) { /* request QUIT and wait for acknowledge */ void *threadrtn; pthread_mutex_lock(&x->x_mutex); x->x_requestcode = REQUEST_QUIT; x->x_disconnect = 1; post("stopping shoutamp thread..."); shoutamp_cond_signal(&x->x_requestcondition); while (x->x_requestcode != REQUEST_NOTHING) { post("signalling..."); shoutamp_cond_signal(&x->x_requestcondition); shoutamp_cond_wait(&x->x_answercondition, &x->x_mutex); } pthread_mutex_unlock(&x->x_mutex); if (pthread_join(x->x_childthread, &threadrtn)) error("shoutamp_free: join failed"); post("... done."); pthread_cond_destroy(&x->x_requestcondition); pthread_cond_destroy(&x->x_answercondition); pthread_mutex_destroy(&x->x_mutex); freebytes(x->x_buf, x->x_bufsize*sizeof(t_float)); freebytes(x->x_rsbuf, sys_getblksize()*sizeof(t_float)*2); clock_free(x->x_clock); } void shoutamp_tilde_setup(void) { shoutamp_class = class_new(gensym("shoutamp~"), (t_newmethod)shoutamp_new, (t_method)shoutamp_free, sizeof(t_shoutamp), 0, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, 0); class_addfloat(shoutamp_class, (t_method)shoutamp_float); class_addmethod(shoutamp_class, (t_method)shoutamp_disconnect, gensym("disconnect"), 0); class_addmethod(shoutamp_class, (t_method)shoutamp_dsp, gensym("dsp"), 0); class_addmethod(shoutamp_class, (t_method)shoutamp_connect, gensym("connect"), A_GIMME, 0); class_addmethod(shoutamp_class, (t_method)shoutamp_connect_url, gensym("connecturl"), A_SYMBOL, 0); class_addmethod(shoutamp_class, (t_method)shoutamp_recover, gensym("recover"), A_FLOAT, 0); class_addmethod(shoutamp_class, (t_method)shoutamp_print, gensym("print"), 0); class_sethelpsymbol(shoutamp_class, gensym("help-shoutamp~.pd")); }