aboutsummaryrefslogtreecommitdiff
path: root/oggamp~/oggamp~.c
diff options
context:
space:
mode:
Diffstat (limited to 'oggamp~/oggamp~.c')
-rw-r--r--oggamp~/oggamp~.c1491
1 files changed, 1491 insertions, 0 deletions
diff --git a/oggamp~/oggamp~.c b/oggamp~/oggamp~.c
new file mode 100644
index 0000000..807a415
--- /dev/null
+++ b/oggamp~/oggamp~.c
@@ -0,0 +1,1491 @@
+/* ------------------------- oggamp~ ------------------------------------------ */
+/* */
+/* Tilde object to receive an Ogg Vorbis stream from an IceCast2 server. */
+/* Written by Olaf Matthes <olaf.matthes@gmx.de> */
+/* Get source at http://www.akustische-kunst.de/puredata/ */
+/* */
+/* Graphical buffer status display written by Yves Degoyon. */
+/* */
+/* Thanks for hours (maybe days?) of beta testing to Oliver Thuns. */
+/* */
+/* 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 Ogg Vorbis decoding library which can be found at */
+/* http://www.vorbis.com/ */
+/* */
+/* ---------------------------------------------------------------------------- */
+
+ /* Pd includes */
+#include "m_pd.h"
+#include "g_canvas.h"
+ /* Vorbis includes */
+#include "codec.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#ifdef UNIX
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/time.h>
+#include <unistd.h>
+#define SOCKET_ERROR -1
+#else
+#include <io.h> /* for 'write' in pute-function only */
+#include <winsock.h>
+#include <winbase.h>
+#endif
+
+#ifdef NT
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4305 )
+#endif
+
+#ifdef UNIX
+#define sys_closesocket close
+#endif
+#ifdef NT
+#define sys_closesocket closesocket
+#endif
+
+
+/************************* oggamp~ object ******************************/
+
+/*
+ Each instance of oggamp~ 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 65536 /* _encoded_ data we request from buffer */
+#define READ 4096 /* amount of data we pass on to decoder */
+#define DEFBUFPERCHAN 262144 /* audio output buffer default: 256k */
+#define MINBUFSIZE (4 * READSIZE)
+#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 statis display */
+#define OBJHEIGHT 10 /* height of buffer statis display */
+#define MAXSTREAMCHANS 2 /* maximum number of channels: restricted to 2 by Ogg specs */
+
+static char *oggamp_version = "oggamp~: ogg/vorbis streaming client version 0.2f, written by Olaf Matthes";
+
+static t_class *oggamp_class;
+
+typedef struct _oggamp
+{
+ t_object x_obj;
+ t_canvas *x_canvas; /* remember canvas */
+ t_outlet *x_connection;
+ t_clock *x_clock;
+
+ t_float *x_buf; /* 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 */
+
+ /* 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 Vorbis 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) */
+
+ /* ogg/vorbis related stuff */
+ ogg_stream_state x_os; /* take physical pages, weld into a logical stream of packets */
+ ogg_sync_state x_oy; /* sync and verify incoming physical bitstream */
+ ogg_page x_og; /* one Ogg bitstream page. Vorbis packets are inside */
+ ogg_packet x_op; /* one raw packet of data for decode */
+ vorbis_info x_vi; /* struct that stores all the static vorbis bitstream settings */
+ vorbis_comment x_vc; /* struct that stores all the user comments */
+ vorbis_dsp_state x_vd; /* central working state for the packet->PCM decoder */
+ vorbis_block x_vb; /* local working space for packet->PCM decode */
+ t_int x_eos; /* end of stream */
+ char *x_buffer; /* buffer used to pass on to ogg/vorbis */
+
+ t_int x_vorbis; /* info about encoder status */
+ t_int x_sync; /* indicates whether the decoder has been synced */
+ t_float x_pages; /* number of pages that have been output to server */
+ t_outlet *x_outpages; /* output to send them to */
+
+
+ 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_float 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 */
+
+ /* 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_oggamp;
+
+ /* check if we can read from socket */
+static int oggamp_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 oggamp_child_receive(int fd, char *buffer, int size)
+{
+ int ret = -1;
+ int i;
+
+ ret = recv(fd, buffer, size, 0);
+ if(ret < 0)
+ {
+ post("oggamp~: receive error" );
+ }
+ return ret;
+}
+
+ /* ogg/vorbis decoder sync init */
+static void oggamp_vorbis_sync_init(t_oggamp *x)
+{
+ ogg_sync_init(&(x->x_oy)); /* Now we can read pages */
+ x->x_sync = 1;
+}
+
+ /* ogg/vorbis decoder setup */
+static int oggamp_vorbis_init(t_oggamp *x, int fd)
+{
+ int i;
+ int result; /* error return code */
+ int bytes; /* number of bytes receive returned */
+ char *buffer; /* buffer for undecoded ogg vorbis data */
+
+ if(!x->x_sync)oggamp_vorbis_sync_init(x); /* init the sync */
+
+ x->x_eos = 0; /* indicate beginning of new stream */
+
+ /* submit a 4k block to libvorbis' ogg layer */
+ buffer = ogg_sync_buffer(&(x->x_oy),READ);
+ post("oggamp~: prebuffering...");
+ bytes = oggamp_child_receive(fd, buffer, READ);
+ ogg_sync_wrote(&(x->x_oy),bytes);
+ result = ogg_sync_pageout(&(x->x_oy),&(x->x_og));
+
+ /* we might need more data */
+ if(result == -1)
+ {
+ post("reading more...");
+ buffer = ogg_sync_buffer(&(x->x_oy),READ);
+ bytes = oggamp_child_receive(fd, buffer, READ);
+ ogg_sync_wrote(&(x->x_oy),bytes);
+ result = ogg_sync_pageout(&(x->x_oy),&(x->x_og));
+ }
+ /* Get the first page. */
+ if(result != 1)
+ {
+ /* error case. Must not be Vorbis data */
+ error("oggamp~: input does not appear to be an ogg bitstream (error %d)", result);
+ return -1;
+ }
+
+ ogg_stream_init(&(x->x_os),ogg_page_serialno(&(x->x_og)));
+
+ vorbis_info_init(&(x->x_vi));
+ vorbis_comment_init(&(x->x_vc));
+ if(ogg_stream_pagein(&(x->x_os),&(x->x_og))<0){
+ /* error; stream version mismatch perhaps */
+ error("oggamp~: error reading first page of ogg bitstream data");
+ return -1;
+ }
+
+ if(ogg_stream_packetout(&(x->x_os),&(x->x_op))!=1){
+ /* no page? must not be vorbis */
+ error("oggamp~: error reading initial header packet");
+ return -1;
+ }
+
+ if(vorbis_synthesis_headerin(&(x->x_vi),&(x->x_vc),&(x->x_op))<0){
+ /* error case; not a vorbis header */
+ error("oggamp~: this ogg bitstream does not contain Vorbis audio data");
+ return -1;
+ }
+
+ i=0;
+ while(i<2){
+ while(i<2){
+ result = ogg_sync_pageout(&(x->x_oy),&(x->x_og));
+ if(result == 0)break; /* Need more data */
+ /* Don't complain about missing or corrupt data yet. We'll
+ catch it at the packet output phase */
+ if(result == 1)
+ {
+ ogg_stream_pagein(&(x->x_os),&(x->x_og)); /* we can ignore any errors here
+ as they'll also become apparent
+ at packetout */
+ while(i<2){
+ result = ogg_stream_packetout(&(x->x_os),&(x->x_op));
+ if(result==0)break;
+ if(result<0){
+ /* Uh oh; data at some point was corrupted or missing!
+ We can't tolerate that in a header. Die. */
+ error("oggamp~: corrupt secondary header, exiting");
+ return -1;
+ }
+ vorbis_synthesis_headerin(&(x->x_vi),&(x->x_vc),&(x->x_op));
+ i++;
+ }
+ }
+ }
+ /* no harm in not checking before adding more */
+ buffer = ogg_sync_buffer(&(x->x_oy),READ);
+ /* read in next 4k of data */
+ bytes = oggamp_child_receive(fd, buffer, READ);
+ if(bytes==0 && i<2){
+ error("oggamp~: end of stream before finding all Vorbis headers");
+ return -1;
+ }
+ ogg_sync_wrote(&(x->x_oy),bytes);
+ }
+
+ /* Throw the comments plus a few lines about the bitstream we're decoding */
+ post("oggamp~: reading Ogg Vorbis header...");
+ {
+ char **ptr = x->x_vc.user_comments;
+ while(*ptr){
+ post(" %s",*ptr);
+ ++ptr;
+ }
+ post("oggamp~: bitstream is %d channels @ %ld Hz with %ldkbps", x->x_vi.channels, x->x_vi.rate, x->x_vi.bitrate_nominal / 1000);
+ x->x_streamchannels = x->x_vi.channels;
+ x->x_streamrate = x->x_vi.rate;
+ // x->x_siginterval = 64 / x->x_vi.channels; /* estimate interval in which we need to decode */
+ if(x->x_samplerate > x->x_streamrate) /* upsampling */
+ { /* we need to use upsampling */
+ if(x->x_samplerate % x->x_streamrate)
+ {
+ post("oggamp~: upsampling from %ld Hz to %ld Hz not supported !", x->x_vi.rate, x->x_samplerate);
+ }
+ else
+ {
+ post("oggamp~: upsampling from %ld Hz to %ld Hz", x->x_vi.rate, x->x_samplerate);
+ x->x_resample = x->x_samplerate / x->x_streamrate;
+ }
+ }
+ else if(x->x_samplerate < x->x_streamrate)
+ { /* we need to use downsampling */
+ if(x->x_streamrate % x->x_samplerate)
+ {
+ post("oggamp~: downsampling from %ld Hz to %ld Hz not supported !", x->x_vi.rate, x->x_samplerate);
+ }
+ else
+ {
+ post("oggamp~: downsampling from %ld Hz to %ld Hz", x->x_vi.rate, x->x_samplerate);
+ x->x_resample = (t_float)x->x_samplerate / x->x_vi.rate;
+ }
+ }
+ else /* no resampling */
+ {
+ x->x_resample = 1;
+ }
+ post("oggamp~: encoded by: %s", x->x_vc.vendor);
+ }
+
+ /* OK, got and parsed all three headers. Initialize the Vorbis packet->PCM decoder. */
+ vorbis_synthesis_init(&(x->x_vd),&(x->x_vi)); /* central decode state */
+ vorbis_block_init(&(x->x_vd),&(x->x_vb)); /* local state */
+ x->x_vorbis = 1;
+ return (1);
+}
+
+ /* clear the ogg/vorbis decoder */
+static void oggamp_vorbis_sync_clear(t_oggamp *x)
+{
+ /* OK, clean up the framer */
+ ogg_sync_clear(&(x->x_oy));
+ x->x_sync = 0;
+ post("oggamp~: decoder cleared");
+}
+ /* deinit the ogg/vorbis decoder */
+static void oggamp_vorbis_deinit(t_oggamp *x)
+{
+ if(x->x_vorbis)
+ {
+ x->x_vorbis = 0;
+
+ /* clean up this logical bitstream; before exit we see if we're
+ followed by another [chained] */
+ ogg_stream_clear(&(x->x_os));
+
+ /* ogg_page and ogg_packet structs always point to storage in
+ libvorbis. They're never freed or manipulated directly */
+
+ vorbis_block_clear(&(x->x_vb));
+ vorbis_dsp_clear(&(x->x_vd));
+ vorbis_comment_clear(&(x->x_vc));
+ vorbis_info_clear(&(x->x_vi)); /* must be called last */
+
+ post("oggamp~: decoder deinitialised");
+ /* only clear completely in case we're going to disconnect */
+ /* !! must not be called when receiving chained streams !! */
+ if(x->x_disconnect)oggamp_vorbis_sync_clear(x);
+ }
+}
+
+ /* decode ogg/vorbis and receive new data */
+static int oggamp_decode_input(t_oggamp *x, float *buf, int fifohead, int fifosize, int fd)
+{
+ int i, result;
+
+ float **pcm; /* pointer to decoded float samples */
+ char *buffer; /* buffer for ogg vorbis */
+ int samples; /* number of samples returned by decoder at each block! */
+ int n = 0; /* total number of samples returned by decoder at this call */
+ int bytes; /* number of bytes submitted to decoder */
+ int position = fifohead;
+
+
+ /* the rest is just a straight decode loop until end of stream */
+ while(!x->x_eos)
+ {
+ result = ogg_sync_pageout(&(x->x_oy),&(x->x_og));
+ if(result == 0)break; /* need more data */
+ if(result < 0)
+ { /* missing or corrupt data at this page position */
+ error("oggamp~: corrupt or missing data in bitstream, continuing...");
+ }
+ else{
+ ogg_stream_pagein(&(x->x_os),&(x->x_og)); /* can safely ignore errors at this point */
+ while(1)
+ {
+ result = ogg_stream_packetout(&(x->x_os),&(x->x_op));
+
+ if(result==0)break; /* need more data */
+ if(result<0)
+ { /* missing or corrupt data at this page position */
+ /* no reason to complain; already complained above */
+ }else
+ {
+ /* we have a packet. Decode it */
+ if(vorbis_synthesis(&(x->x_vb),&(x->x_op))==0) /* test for success! */
+ vorbis_synthesis_blockin(&(x->x_vd),&(x->x_vb));
+ /*
+
+ **pcm is a multichannel float vector. In stereo, for
+ example, pcm[0] is left, and pcm[1] is right. samples is
+ the size of each channel. Convert the float values
+ (-1.<=range<=1.) to whatever PCM format and write it out */
+
+ while((samples = vorbis_synthesis_pcmout(&(x->x_vd),&pcm))>0)
+ {
+ int j;
+ int clipflag = 0;
+
+ /* copy into our output buffer */
+ for(j = 0; j < samples; j++)
+ {
+ for(i = 0; i < x->x_vi.channels; i++)
+ {
+ buf[position] = pcm[i][j];
+ position = (position + 1) % fifosize;
+ }
+ }
+
+ if(clipflag)post("oggamp~: warning: clipping in frame %ld",(long)(x->x_vd.sequence));
+
+ vorbis_synthesis_read(&(x->x_vd),samples); /* tell libvorbis how
+ many samples we
+ actually consumed */
+ n += samples; /* sum up the samples we got from decoder */
+ }
+ }
+ }
+ if(ogg_page_eos(&(x->x_og)))x->x_eos=1;
+ }
+ }
+
+ /* read data from socket */
+ if(!x->x_eos) /* read from input until end of stream */
+ {
+ buffer = ogg_sync_buffer(&(x->x_oy), READ);
+ /* read next 4k of data out of buffer */
+ bytes = oggamp_child_receive(fd, buffer, READ);
+ if(bytes < 0)
+ {
+ x->x_eos = 1; /* indicate end of stream */
+ return (bytes);
+ }
+ ogg_sync_wrote(&(x->x_oy),bytes);
+ }
+ else /* we read through all the file... */
+ { /* will have to reinit decoder for new file */
+ post("oggamp~: end of stream detected");
+ return (0);
+ }
+ return (n*x->x_vi.channels);
+}
+
+ /* connect to shoutcast server */
+static int oggamp_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 */
+ 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("oggamp~: 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("oggamp~: 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);
+
+ /* try to connect. */
+ post("oggamp~: connecting to http://%s:%d/%s", hostname, portno, mountpoint);
+ if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0)
+ {
+ error("oggamp~: 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("oggamp~: 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: oggamp~ 0.2\r\nAccept: audio/x-ogg\r\n\r\n",
+ mountpoint, hostname);
+
+ if ( send(sockfd, request, strlen(request), 0) < 0 ) /* say hello to server */
+ {
+ post("oggamp~: could not contact server...");
+ return (-1);
+ }
+
+ /* read first line of response */
+ i = 0;
+ while(i < STRBUF_SIZE - 1)
+ {
+ if(oggamp_check_for_data(sockfd))
+ {
+ if (recv(sockfd, request + i, 1, 0) <= 0)
+ {
+ error("oggamp~: could not read from socket, quitting");
+ sys_closesocket(sockfd);
+ return (-1);
+ }
+ if (request[i] == '\n')
+ break;
+ if (request[i] != '\r')
+ i++;
+ }
+ }
+ request[i] = '\0';
+
+ /* time to parse content of the response... */
+ if(strstr(request, "HTTP/1.0 200 OK")) /* server is ready */
+ {
+ post("oggamp~: IceCast2 server detected");
+ while(!eof) /* read everything we can get */
+ {
+ i = 0;
+ while(i < STRBUF_SIZE - 1)
+ {
+ if(oggamp_check_for_data(sockfd))
+ {
+ if (recv(sockfd, request + i, 1, 0) <= 0)
+ {
+ error("oggamp~: could not read from socket, quitting");
+ sys_closesocket(sockfd);
+ return (-1);
+ }
+ if(request[i] == '\n') /* leave at end of line */
+ break;
+ if(request[i] == 0x0A) /* leave at end of line */
+ break;
+ if(request[i] != '\r') /* go on until 'return' */
+ i++;
+ }
+ }
+ request[i] = '\0'; /* make it a null terminated string */
+
+ if(sptr = strstr(request, "application/x-ogg"))
+ { /* check for content type */
+ post("oggamp~: Ogg Vorbis stream found");
+ }
+ if(sptr = strstr(request, "ice-name:"))
+ { /* display ice-name */
+ post("oggamp~: \"%s\"", sptr + 10);
+ }
+ if(i == 0)eof = 1; /* we got last '\r\n' from server */
+ }
+ }
+ else /* wrong server or wrong answer */
+ {
+ post("oggamp~: unknown response from server");
+ sys_closesocket(sockfd);
+ return (-1);
+ }
+ post("oggamp~: connected to http://%s:%d/%s", hp->h_name, portno, mountpoint);
+
+ return (sockfd);
+}
+
+
+static void oggamp_child_dographics(t_oggamp *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 oggamp_child_updategraphics(t_oggamp *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 oggamp_child_delgraphics(t_oggamp *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 oggamp_child_disconnect(t_int fd)
+{
+ sys_closesocket(fd);
+ post("oggamp~: 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 oggamp_cond_wait pthread_cond_wait
+#define oggamp_cond_signal pthread_cond_signal
+#else
+#include <sys/time.h> /* debugging version... */
+#include <sys/types.h>
+static void oggamp_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 oggamp_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 oggamp_cond_wait(a,b) oggamp_fakewait(b)
+#define oggamp_cond_signal(a)
+#endif
+
+static void *oggamp_child_main(void *zz)
+{
+ t_oggamp *x = zz;
+ pute("1\n");
+ pthread_mutex_lock(&x->x_mutex);
+ while (1)
+ {
+ int fd, fifohead;
+ char *buffer; /* Ogg Vorbis data */
+ float *buf; /* encoded PCM floats */
+ pute("0\n");
+ if (x->x_requestcode == REQUEST_NOTHING)
+ {
+ pute("wait 2\n");
+ oggamp_cond_signal(&x->x_answercondition);
+ oggamp_cond_wait(&x->x_requestcondition, &x->x_mutex);
+ pute("3\n");
+ }
+ // connect to Icecast2 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);
+ oggamp_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 = oggamp_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(oggamp_vorbis_init(x, fd) != -1)
+ {
+ post("oggamp~: decoder initialised");
+ oggamp_child_dographics(x);
+ }
+ else
+ {
+ post("oggamp~: could not init decoder");
+ oggamp_child_disconnect(fd);
+ post("oggamp~: 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 - READSIZE)
+ {
+ 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 ogg vorbis stuff
+ to be presend. all other values should not be changed because
+ mutex is unlocked ! */
+ pute("decode... ");
+ pthread_mutex_unlock(&x->x_mutex);
+ sysrtn = oggamp_decode_input(x, buf, fifohead, fifosize, fd);
+ oggamp_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");
+ oggamp_vorbis_deinit(x);
+ if(oggamp_vorbis_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 */
+ {
+ pute("end of stream: disconnecting\n");
+ break; /* go to disconnect */
+ }
+ }
+ else if (sysrtn < 0) /* got any other error from decoder */
+ {
+ pute("connecterror\n");
+ x->x_connecterror = sysrtn;
+ 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");
+ oggamp_cond_signal(&x->x_answercondition);
+ oggamp_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 */
+ oggamp_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);
+ oggamp_vorbis_deinit(x);
+ oggamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ x->x_fd = -1;
+ oggamp_child_delgraphics(x);
+ }
+ oggamp_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);
+ oggamp_vorbis_deinit(x);
+ oggamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ oggamp_child_delgraphics(x);
+ x->x_fd = -1;
+ }
+ /* connect again */
+ x->x_requestcode = REQUEST_CONNECT;
+
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ oggamp_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);
+ oggamp_vorbis_deinit(x);
+ oggamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ oggamp_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);
+ oggamp_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);
+ oggamp_vorbis_deinit(x);
+ oggamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ x->x_fd = -1;
+ }
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ x->x_requestcode = REQUEST_NOTHING;
+ oggamp_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 oggamp_tick(t_oggamp *x)
+{
+ outlet_float(x->x_connection, x->x_connectstate);
+}
+
+static void *oggamp_new(t_floatarg fdographics, t_floatarg fnchannels, t_floatarg fbufsize)
+{
+ t_oggamp *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_oggamp *)pd_new(oggamp_class);
+
+ 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)oggamp_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 = 2;
+ 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_connectstate = 0; /* indicating state of connection */
+
+ x->x_samplerate = x->x_streamrate = sys_getsr();
+ x->x_resample = 0;
+ x->x_vorbis = 0;
+ x->x_sync = 0;
+ x->x_recover = -1; /* just ignore buffer underruns */
+
+ /* graphical buffer status display */
+ x->x_graphic = (int)fdographics;
+ x->x_canvas = canvas_getcurrent();
+
+ post(oggamp_version);
+ post("oggamp~: set buffer to %dk bytes", bufsize/1024);
+
+ /* start child thread */
+ pthread_create(&x->x_childthread, 0, oggamp_child_main, x);
+ return (x);
+}
+
+static t_int *oggamp_perform(t_int *w)
+{
+ t_oggamp *x = (t_oggamp *)(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_buf;
+ 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 get after resampling */
+ wantbytes = (t_float)getbytes / resample; /* we need vecsize bytes per channel */
+ havebytes = x->x_fifobytes;
+
+ /* check for error */
+ if(havebytes < wantbytes)
+ {
+ 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;
+ oggamp_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;
+ oggamp_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;
+ oggamp_cond_signal(&x->x_requestcondition);
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ else /* resume */
+ {
+ x->x_state = STATE_IDLE;
+ x->x_disconnect = 0;
+ oggamp_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)
+ {
+ oggamp_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 oggamp_disconnect(t_oggamp *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;
+ oggamp_cond_signal(&x->x_requestcondition);
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+
+ /* connect method. Called as:
+ connect <hostname or IP> <mountpoint> <portnumber>
+ */
+
+static void oggamp_connect(t_oggamp *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_streamchannels = 2;
+ x->x_eof = 0;
+ x->x_connecterror = 0;
+ x->x_state = STATE_STARTUP;
+ x->x_disconnect = 0;
+ oggamp_cond_signal(&x->x_requestcondition);
+ }
+ else post("oggamp~: already connected");
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+ /* connect using url like "http://localhost:8000/mountpoint" */
+static void oggamp_connect_url(t_oggamp *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;
+ oggamp_cond_signal(&x->x_requestcondition);
+ }
+ else post("oggamp~: already connected");
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+static void oggamp_float(t_oggamp *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;
+ oggamp_cond_signal(&x->x_requestcondition);
+ }
+ else post("oggamp~: already connected");
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ else oggamp_disconnect(x);
+}
+
+static void oggamp_dsp(t_oggamp *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(oggamp_perform, 1, x);
+}
+
+static void oggamp_print(t_oggamp *x)
+{
+ pthread_mutex_lock(&x->x_mutex);
+ if(x->x_fd >= 0)
+ {
+ post("oggamp~: connected to http://%s:%d/%s", x->x_hostname, x->x_port, x->x_mountpoint);
+ post("oggamp~: bitstream is %d channels @ %ld Hz with %ldkbps nominal bitrate",
+ x->x_streamchannels, x->x_streamrate, x->x_vi.bitrate_nominal / 1000);
+ } else post("oggamp~: not connected");
+ if(x->x_recover == 0)
+ post("oggamp~: recover mode set to \"disconnect\" (0)");
+ else if(x->x_recover == 1)
+ post("oggamp~: recover mode set to \"reconnect\" (1)");
+ else if(x->x_recover == -1)
+ post("oggamp~: recover mode set to \"resume\" (-1)");
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+ /* set behavior for buffer underruns */
+static void oggamp_recover(t_oggamp *x, t_floatarg f)
+{
+ pthread_mutex_lock(&x->x_mutex);
+ if(f <= -1)
+ { /* mute audio and try to fill buffer again: the default */
+ post("oggamp~: set recover mode to \"resume\" (-1)");
+ f = -1;
+ }
+ else if(f >= 1)
+ { /* reconnect to server */
+ post("oggamp~: set recover mode to \"reconnect\" (1)");
+ f = 1;
+ }
+ else
+ { /* disconnect from server */
+ post("oggamp~: set recover mode to \"disconnect\" (0)");
+ f = 0;
+ }
+ x->x_recover = f;
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+static void oggamp_free(t_oggamp *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 oggamp thread...");
+ oggamp_cond_signal(&x->x_requestcondition);
+ while (x->x_requestcode != REQUEST_NOTHING)
+ {
+ post("signalling...");
+ oggamp_cond_signal(&x->x_requestcondition);
+ oggamp_cond_wait(&x->x_answercondition, &x->x_mutex);
+ }
+ pthread_mutex_unlock(&x->x_mutex);
+ if (pthread_join(x->x_childthread, &threadrtn))
+ error("oggamp_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));
+ clock_free(x->x_clock);
+}
+
+void oggamp_tilde_setup(void)
+{
+ oggamp_class = class_new(gensym("oggamp~"), (t_newmethod)oggamp_new,
+ (t_method)oggamp_free, sizeof(t_oggamp), 0, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, 0);
+ class_addfloat(oggamp_class, (t_method)oggamp_float);
+ class_addmethod(oggamp_class, (t_method)oggamp_disconnect, gensym("disconnect"), 0);
+ class_addmethod(oggamp_class, (t_method)oggamp_dsp, gensym("dsp"), 0);
+ class_addmethod(oggamp_class, (t_method)oggamp_connect, gensym("connect"), A_GIMME, 0);
+ class_addmethod(oggamp_class, (t_method)oggamp_connect_url, gensym("connecturl"), A_SYMBOL, 0);
+ class_addmethod(oggamp_class, (t_method)oggamp_recover, gensym("recover"), A_FLOAT, 0);
+ class_addmethod(oggamp_class, (t_method)oggamp_print, gensym("print"), 0);
+ class_sethelpsymbol(oggamp_class, gensym("help-oggamp~.pd"));
+} \ No newline at end of file