aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWinfried Ritsch <ritsch@users.sourceforge.net>2005-05-03 07:18:08 +0000
committerWinfried Ritsch <ritsch@users.sourceforge.net>2005-05-03 07:18:08 +0000
commitf3f3f15d90ae9c0a8fec183efdfd0dcb8bd054eb (patch)
tree2e607ed794800edd3da1f95630f44c9f9550d7e2
This commit was generated by cvs2svn to compensate for changes in r2884,HEADsvn2git-rootsvn2git-headexternals/iem/iemstream
which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/iem/iemstream/; revision=2885
-rw-r--r--LICENCE.txt360
-rw-r--r--Makefile83
-rw-r--r--README.txt38
-rw-r--r--fifo.h142
-rw-r--r--help-stream.pd96
-rw-r--r--main.cpp241
-rw-r--r--socket.cpp188
-rw-r--r--socket.h21
-rw-r--r--stream.cpp557
-rw-r--r--stream.dsp134
-rw-r--r--stream.dsw29
-rw-r--r--stream.h160
-rw-r--r--stream.vcproj152
-rw-r--r--streamogg.cpp113
-rw-r--r--streamogg.h64
15 files changed, 2378 insertions, 0 deletions
diff --git a/LICENCE.txt b/LICENCE.txt
new file mode 100644
index 0000000..c626a60
--- /dev/null
+++ b/LICENCE.txt
@@ -0,0 +1,360 @@
+stream - external library for PD (Puredata) for
+streaming multichannel files and others
+
+Copyright (C) 1998-2005 Institute for Electronic Music and Acoustics, Graz
+Author: Thomas Grill
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version. (see below)
+
+--------------------------- GPL.TXT -------------------------
+
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ 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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..043c987
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,83 @@
+# stream - stream externals for PD
+#
+# Makefile for gcc @ linux
+#
+# usage:
+# to build run "make -f Makefile.linux"
+# to install (as root), do "make -f Makefile.linux install"
+#
+
+# Configuration: ##########################
+
+
+# where are the PD header files?
+# leave it blank if it is a system directory (like /usr/local/include),
+# since gcc 3.2 complains about it
+PDPATH=
+
+# where should flext libraries be built?
+TARGDIR=./pd-linux
+
+# where should the external be installed?
+# (leave blank to omit installation)
+INSTPATH=/usr/local/lib/pd/extra
+
+# additional compiler flags
+# (check if they fit for your system!)
+# UFLAGS=-mcpu=pentiumpro # gcc 2.95
+UFLAGS=-mcpu=pentium3 -msse # gcc 3.xx
+
+###########################################
+
+# compiler+linker stuff
+INCLUDES=$(PDPATH)
+LIBPATH=
+FLAGS=-DPD ${U_FLAGS}
+CFLAGS=-O6
+#CFLAGS=-g
+LIBS=m util ogg vorbis vorbisenc vorbisfile samplerate
+
+
+# ---------------------------------------------
+# the rest can stay untouched
+# ----------------------------------------------
+
+NAME=stream
+
+# all the source files from the package
+DIR=.
+SRCS=main.cpp socket.cpp stream.cpp streamogg.cpp
+
+MAKEFILE=Makefile
+
+TARGET=$(TARGDIR)/$(NAME).pd_linux
+
+# default target
+all: $(TARGDIR) $(TARGET)
+
+$(patsubst %,$(DIR)/%,$(SRCS)): $(patsubst %,$(DIR)/%,$(HDRS)) $(MAKEFILE)
+ touch $@
+
+$(TARGDIR):
+ mkdir $(TARGDIR)
+
+$(TARGDIR)/%.o : $(DIR)/%.cpp
+ $(CXX) -c $(CFLAGS) $(FLAGS) $(patsubst %,-I%,$(INCLUDES)) $< -o $@
+
+$(TARGET) : $(patsubst %.cpp,$(TARGDIR)/%.o,$(SRCS))
+ $(CXX) -shared $^ $(patsubst %,-L%,$(LIBPATH)) $(patsubst %,-l%,$(LIBS)) -o $@
+ chmod 755 $@
+
+$(INSTPATH):
+ mkdir $(INSTPATH)
+
+install:: $(INSTPATH)
+
+install:: $(TARGET)
+ cp $^ $(INSTPATH)
+ chown root.root $(patsubst %,$(INSTPATH)/%,$(notdir $^))
+ chmod 755 $(patsubst %,$(INSTPATH)/%,$(notdir $^))
+
+.PHONY: clean
+clean:
+ rm -f $(TARGDIR)/*.o $(TARGET)
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..feb4643
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,38 @@
+stream - external library for streaming
+
+Momentan ein Object amp~ zur Wiedergabe von ogg-Streams.
+
+Die Idee stammt von readanysf~ (August Black), wurde aber völlig neu implementiert.
+Ein paar unwichtige Code-Schnippsel sind noch drin, werden aber auch neu geschrieben.
+
+
+Features (im Vergleich pdogg and readanysf~):
+- Mehrkanalfähigkeit
+- Genau dosierbare Pufferung
+- nur ein thread für Datenempfang und Decodierung
+- Stream-Resampling, wenn nötig
+
+
+Abhängigkeiten:
+- OGG und Vorbis
+- libsamplerate
+
+Bedienung:
+[amp~ n] erzeugt ein Objekt mit n Signal-Outlets. Der Stream kann eine andere Kanalzahl haben.
+Überzählige Stream-Kanäle werden ignoriert, überzählige Outlets haben 0~-Signale.
+
+Messages:
+[connect URI( ... Verbinde zu Server:Port/Mountpoint und starte Wiedergabe
+[disconnect( .... Stoppe Wiedergabe
+[strbuf samples ( ... Stream-Buffer-Größe in Samples (default 10000)
+[strchunk samples ( ... Samples die auf einmal vom Socket gelesen werden (default 500)
+[strthresh ratio ( ... Threshold 0...1 unter welchem Anteil der Buffer-Füllung nachgeladen wird. (default 0.95)
+
+[debug 0/1 ( .... Debug-Output zur Konsole
+
+
+Probleme:
+resampling funktioniert nicht einwandfrei
+
+LICENCE: GPL
+ see LICENCE.txt
diff --git a/fifo.h b/fifo.h
new file mode 100644
index 0000000..0ed7843
--- /dev/null
+++ b/fifo.h
@@ -0,0 +1,142 @@
+/*************************************************************
+ *
+ * streaming external for PD
+ *
+ * File: fifo.h
+ *
+ * Description: Implementation of a FIFO template class
+ *
+ * Author: Thomas Grill (t.grill@gmx.net)
+ *
+ *************************************************************/
+
+#ifndef _FIFO_H_
+#define _FIFO_H_
+
+#include <memory.h>
+
+#include <assert.h>
+#define ASSERT assert
+
+
+/*! FIFO class
+ \note not thread-safe
+*/
+template<class T>
+class Fifo
+{
+public:
+ Fifo(int n = 0): size(n), arr(new T[n]),rd(0),wr(0),have(0) {}
+ ~Fifo() { if(arr) delete[] arr; }
+
+ //! Clear fifo
+ void Clear() { rd = wr = have = 0; }
+
+ //! Get total fifo size
+ int Size() const { return size; }
+ //! Get number of items in fifo
+ int Have() const { return have; }
+ //! Get free fifo size
+ int Free() const { return size-have; }
+
+ //! Get pointer to beginning of contained data
+ T *ReadPtr() const { return arr+rd; }
+ //! Get number of items from read pos to end of contiguous space
+ int ReadSamples() const { return size-rd; }
+ //! Get pointer to beginning of free space
+ T *WritePtr() const { return arr+wr; }
+ //! Get number of items from write pos to end of contiguous space
+ int WriteSamples() const { return size-wr; }
+
+ /*! Resize FIFO
+ \param nsz new FIFO size
+ \param keep keep data?
+ \return true if all data could be kept
+ */
+ bool Resize(int nsz,bool keep)
+ {
+ if(keep) {
+ bool all = true;
+ T *narr = new T[nsz];
+ // try to keep newest data
+ int s = Have()-nsz;
+ if(s > 0) {
+ // skip items that don't fit into new buffer
+ all = false;
+ int r = Read(s,NULL);
+ ASSERT(r == s);
+ }
+ s = Have();
+ int r = Read(s,narr);
+ ASSERT(r == s);
+
+ delete[] arr;
+ arr = narr; size = nsz;
+ Clear();
+ return all;
+ }
+ else {
+ delete[] arr;
+ arr = new T[size = nsz];
+ Clear();
+ return true;
+ }
+ }
+
+ /*! Write data to fifo
+ \param n number of items to write
+ \param buf memory buffer containing items
+ \return number of written items
+ */
+ int Write(int n,const T *buf)
+ {
+ if(n > Free()) n = Free();
+
+ if(wr+n >= size) {
+ // wrap-over
+ int ch1 = size-wr;
+ memcpy(arr+wr,buf,ch1*sizeof(T));
+ wr = n-ch1;
+ memcpy(arr,buf+ch1,wr*sizeof(T));
+ }
+ else {
+ memcpy(arr+wr,buf,n*sizeof(T));
+ wr += n;
+ }
+
+ have += n;
+ return n;
+ }
+
+ /*! Read data from fifo
+ \param n number of items to read
+ \param buf memory buffer to store items (NULL to simply skip items)
+ \return number of read items
+ */
+ int Read(int n,T *buf)
+ {
+ if(n > Have()) n = Have();
+
+ if(rd+n >= size) {
+ // wrap-over
+ int ch1 = size-rd;
+ if(buf) memcpy(buf,arr+rd,ch1*sizeof(T));
+ rd = n-ch1;
+ if(buf) memcpy(buf+ch1,arr,rd*sizeof(T));
+ }
+ else {
+ if(buf) memcpy(buf,arr+rd,n*sizeof(T));
+ rd += n;
+ }
+
+ have -= n;
+ return n;
+ }
+
+protected:
+ int size;
+ int rd,wr,have;
+ mutable T *arr;
+};
+
+#endif
diff --git a/help-stream.pd b/help-stream.pd
new file mode 100644
index 0000000..db285c9
--- /dev/null
+++ b/help-stream.pd
@@ -0,0 +1,96 @@
+#N canvas 431 70 743 474 12;
+#X obj 273 303 dac~ 1 2 3 4;
+#X obj 227 275 *~ 0;
+#X obj 264 275 *~ 0;
+#X obj 300 276 *~ 0;
+#X obj 337 276 *~ 0;
+#X obj 342 244 hsl 128 15 0.001 1 1 1 empty empty empty -2 -6 0 8 -262131
+-1 -1 12000 1;
+#X obj 14 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 0 0;
+#X obj 74 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 1 0;
+#X obj 11 272 env~;
+#X obj 118 272 env~;
+#X obj 12 298 - 100;
+#X obj 119 298 - 100;
+#X obj 628 23 loadbang;
+#X msg 628 53 \; pd dsp 1;
+#X msg 69 84 disconnect;
+#X msg 23 11 connect darkstar:8008/ices.ogg;
+#X obj 104 147 tgl 15 0 empty empty empty 0 -6 0 8 -260818 -1 -1 0
+1;
+#X msg 122 144 debug \$1;
+#X msg 43 37 connect dev.iem.at:8002/test.ogg;
+#X msg 58 62 connect radio.jcraft.com:8000/test.ogg;
+#X obj 215 104 nbx 5 14 0 1e+006 0 1 empty empty empty 0 -6 0 10 -262131
+-1 -1 30000 256;
+#X msg 214 123 strbuf \$1;
+#X obj 71 200 amp~ 4;
+#X obj 34 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 0 0;
+#X obj 48 272 env~;
+#X obj 49 298 - 100;
+#X obj 54 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 0 0;
+#X obj 83 272 env~;
+#X obj 84 298 - 100;
+#X msg 376 93 getchannels;
+#X msg 376 115 getsrate;
+#X obj 278 351 route float;
+#X obj 376 350 print;
+#X obj 213 151 nbx 5 14 0 1e+006 0 1 empty empty empty 0 -6 0 10 -262131
+-1 -1 10 256;
+#X msg 212 170 tick \$1;
+#X obj 281 388 hsl 128 15 0 1 0 1 empty empty empty -2 -6 0 8 -225271
+-1 -1 0 1;
+#X text 276 403 filling ratio;
+#X text 340 226 volume;
+#X msg 376 138 getbrate;
+#X msg 376 160 gettag title;
+#X text 472 94 stream channels;
+#X text 452 114 stream sample rate;
+#X text 452 136 nominal stream bit rate;
+#X text 482 161 title tag;
+#X msg 376 183 gettag artist;
+#X text 487 184 artist tag;
+#X text 570 172 etc.;
+#X connect 1 0 0 0;
+#X connect 2 0 0 1;
+#X connect 3 0 0 2;
+#X connect 4 0 0 3;
+#X connect 5 0 1 1;
+#X connect 5 0 2 1;
+#X connect 5 0 3 1;
+#X connect 5 0 4 1;
+#X connect 8 0 10 0;
+#X connect 9 0 11 0;
+#X connect 10 0 6 0;
+#X connect 11 0 7 0;
+#X connect 12 0 13 0;
+#X connect 14 0 22 0;
+#X connect 15 0 22 0;
+#X connect 16 0 17 0;
+#X connect 17 0 22 0;
+#X connect 18 0 22 0;
+#X connect 19 0 22 0;
+#X connect 20 0 21 0;
+#X connect 21 0 22 0;
+#X connect 22 0 1 0;
+#X connect 22 0 8 0;
+#X connect 22 1 2 0;
+#X connect 22 1 24 0;
+#X connect 22 2 3 0;
+#X connect 22 2 27 0;
+#X connect 22 3 4 0;
+#X connect 22 3 9 0;
+#X connect 22 4 31 0;
+#X connect 24 0 25 0;
+#X connect 25 0 23 0;
+#X connect 27 0 28 0;
+#X connect 28 0 26 0;
+#X connect 29 0 22 0;
+#X connect 30 0 22 0;
+#X connect 31 0 35 0;
+#X connect 31 1 32 0;
+#X connect 33 0 34 0;
+#X connect 34 0 22 0;
+#X connect 38 0 22 0;
+#X connect 39 0 22 0;
+#X connect 44 0 22 0;
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..0533361
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,241 @@
+/*************************************************************
+ *
+ * streaming external for PD
+ *
+ * File: main.cpp
+ *
+ * Description: PD class definition
+ *
+ * Author: Thomas Grill (t.grill@gmx.net)
+ *
+ *************************************************************/
+
+
+//! Version number of this external
+#define __STREAM_VERSION "0.1.0"
+
+
+// prevent MSVC "extern" warning
+#ifdef _MSC_VER
+#pragma warning( disable : 4091 )
+#endif
+
+// headers contained in the PD distribution
+#include <m_pd.h>
+#include <pthread.h>
+
+// include stream class
+#include "streamogg.h"
+
+
+//! PD class
+static t_class *amp_class = NULL;
+
+//! PD object structure
+struct t_amp
+{
+ t_object obj;
+ Stream *stream;
+
+ int channels;
+ int vecsz;
+ float srate;
+ t_sample **outsigs;
+ t_outlet *dumpout;
+ t_clock *clk;
+ int clkrate;
+};
+
+/*! periodic clock function for output of buffer filling status */
+static void amp_clock(t_amp *x)
+{
+ outlet_float(x->dumpout,x->stream->getFilling());
+
+ // retrigger clock
+ if(x->clkrate) clock_delay(x->clk,x->clkrate);
+}
+
+/*! PD object constructor
+ \param fchannels number of channels (stream can have less)
+*/
+static void *amp_new(t_floatarg fchannels)
+{
+ int channels = (int)fchannels;
+ if(channels == 0) channels = 2;
+ else
+ if(channels < 1) return NULL;
+
+ // make PD object
+ t_amp *x = (t_amp *)pd_new(amp_class);
+
+ x->channels = channels;
+ x->outsigs = new t_sample *[channels];
+ x->stream = new StreamOGG;
+
+ // make signal outlets
+ for(int i = 0; i < channels; i++)
+ outlet_new(&x->obj, gensym("signal"));
+ // additional dump outlet (e.g. buffer filling status)
+ x->dumpout = outlet_new(&x->obj, gensym("anything"));
+
+ // set clock function
+ x->clk = clock_new(x,(t_method)amp_clock);
+ x->clkrate = 0;
+
+ return x;
+ }
+
+/*! PD object destructor */
+static void amp_free(t_amp *x)
+{
+ delete[] x->outsigs;
+ delete x->stream;
+ clock_free(x->clk);
+}
+
+/*! DSP function -> delegate processing to stream */
+static t_int *amp_perform(t_int *w)
+{
+ t_amp *x = (t_amp *)w[1];
+
+ x->stream->doGet(x->channels,x->outsigs,x->vecsz,x->srate);
+
+ return w+2;
+}
+
+/*! set up DSP */
+static void amp_dsp(t_amp *x, t_signal **sp)
+{
+ x->vecsz = sp[0]->s_n;
+ x->srate = sp[0]->s_sr;
+
+ // store outlet signal vectors
+ for(int i = 0; i < x->channels; ++i)
+ x->outsigs[i] = sp[i]->s_vec;
+
+ dsp_add(amp_perform, 1, x);
+}
+
+/*! Connect to stream
+ \param s URL of stream
+*/
+static void amp_connect(t_amp *x,const t_symbol *s)
+{
+ x->stream->doInit(s->s_name);
+}
+
+/*! Disconnect from stream */
+static void amp_disconnect(t_amp *x)
+{
+ x->stream->doExit();
+}
+
+/*! Set/Clear debug mode
+ \param f 0/1 ... debug mode off/on
+*/
+static void amp_debug(t_amp *x,t_floatarg f)
+{
+ x->stream->debug = (f != 0);
+}
+
+/*! Set tick rate for buffer filling status
+ \param f tick rate in ms
+*/
+static void amp_tick(t_amp *x,t_floatarg f)
+{
+ ASSERT(f >= 0);
+ x->clkrate = (int)f;
+ if(f) clock_delay(x->clk,f);
+ else clock_unset(x->clk);
+}
+
+/*! Set decoder FIFO size */
+static void amp_strbuf(t_amp *x,t_floatarg f)
+{
+ x->stream->setStreamBufSize((int)f);
+}
+
+/*! Set decoder chunk size */
+static void amp_strchunk(t_amp *x,t_floatarg f)
+{
+ x->stream->setStreamBufChunk((int)f);
+}
+
+/*! Set decoder filling threshold ratio */
+static void amp_strthresh(t_amp *x,t_floatarg f)
+{
+ x->stream->setStreamBufThresh(f);
+}
+
+/*! Output number of stream channels to dump outlet */
+static void amp_getchannels(t_amp *x)
+{
+ t_atom a;
+ SETFLOAT(&a,x->stream->getChannels());
+ outlet_anything(x->dumpout,gensym("channels"),1,&a);
+}
+
+/*! Output sample rate to dump outlet */
+static void amp_getsrate(t_amp *x)
+{
+ t_atom a;
+ SETFLOAT(&a,x->stream->getSamplerate());
+ outlet_anything(x->dumpout,gensym("srate"),1,&a);
+}
+
+/*! Output bitrate to dump outlet */
+static void amp_getbrate(t_amp *x)
+{
+ t_atom a;
+ SETFLOAT(&a,x->stream->getBitrate());
+ outlet_anything(x->dumpout,gensym("brate"),1,&a);
+}
+
+/*! Output a stream tag to dump outlet
+ \param sym name of tag to output
+*/
+static void amp_gettag(t_amp *x,const t_symbol *sym)
+{
+ t_atom a[2];
+ SETSYMBOL(a+0,const_cast<t_symbol *>(sym));
+ SETSYMBOL(a+1,gensym(const_cast<char *>(x->stream->getTag(sym->s_name).c_str())));
+ outlet_anything(x->dumpout,gensym("tag"),2,a);
+}
+
+
+/*! External setup routine of PD class
+ \note Must be exported from shared library
+*/
+extern "C"
+#ifdef _MSC_VER
+__declspec(dllexport)
+#endif
+void stream_setup()
+{
+ // post some message to the console
+ post("Stream, version " __STREAM_VERSION ", (C)2003 IEM Graz");
+ post("objects: amp~");
+ post("");
+
+ // register the xmlrpc class
+ amp_class = class_new(gensym("amp~"),(t_newmethod)amp_new,(t_method)amp_free,sizeof(t_amp),0, A_DEFFLOAT,A_NULL);
+
+ // register methods
+ class_addmethod(amp_class, (t_method)amp_dsp, gensym("dsp"), A_NULL);
+
+ class_addmethod(amp_class, (t_method)amp_connect, gensym("connect"), A_SYMBOL, A_NULL);
+ class_addmethod(amp_class, (t_method)amp_disconnect, gensym("disconnect"), A_NULL);
+
+ class_addmethod(amp_class, (t_method)amp_debug, gensym("debug"), A_FLOAT, A_NULL);
+ class_addmethod(amp_class, (t_method)amp_tick, gensym("tick"), A_FLOAT, A_NULL);
+
+ class_addmethod(amp_class, (t_method)amp_strbuf, gensym("strbuf"), A_FLOAT, A_NULL);
+ class_addmethod(amp_class, (t_method)amp_strchunk, gensym("strchunk"), A_FLOAT, A_NULL);
+ class_addmethod(amp_class, (t_method)amp_strthresh, gensym("strthresh"), A_FLOAT, A_NULL);
+
+ class_addmethod(amp_class, (t_method)amp_getchannels, gensym("getchannels"), A_NULL);
+ class_addmethod(amp_class, (t_method)amp_getsrate, gensym("getsrate"), A_NULL);
+ class_addmethod(amp_class, (t_method)amp_getbrate, gensym("getbrate"), A_NULL);
+
+ class_addmethod(amp_class, (t_method)amp_gettag, gensym("gettag"), A_SYMBOL,A_NULL);
+}
diff --git a/socket.cpp b/socket.cpp
new file mode 100644
index 0000000..75be726
--- /dev/null
+++ b/socket.cpp
@@ -0,0 +1,188 @@
+/*************************************************************
+ *
+ * streaming external for PD
+ *
+ * File: socket.cpp
+ *
+ * Description: Implementation of socket functions
+ *
+ * Author: Thomas Grill (t.grill@gmx.net)
+ *
+ *************************************************************/
+
+#include "stream.h"
+
+// explicit definition of PD report functions
+extern "C" {
+ extern void post(char *fmt, ...);
+ extern void error(char *fmt, ...);
+}
+
+/* Checks if data is waiting on the socket */
+static bool CheckForData(SOCKET sock,int wait = 20)
+{
+ struct timeval tv;
+ tv.tv_sec = wait/1000;
+ tv.tv_usec = (wait%1000)*1000;
+
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(sock,&set);
+
+ // return true when data is waiting
+ return ::select((int)sock+1, &set, NULL, NULL, &tv) > 0;
+}
+
+SOCKET Stream::Connect(const char *hostname,const char *mountpoint,int portno)
+{
+ const int STRBUF_SIZE = 1024;
+ char request[STRBUF_SIZE]; // string to be sent to server
+ sockaddr_in server;
+
+ SOCKET sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if(sockfd == SOCKET_ERROR) {
+ throw "Error opening socket";
+ }
+
+ // get IP address
+ hostent *hp = gethostbyname(hostname);
+ if(!hp) {
+ closesocket(sockfd);
+ throw "Could not get IP address of hostname";
+ }
+
+ server.sin_family = AF_INET;
+ memcpy(&server.sin_addr,hp->h_addr, hp->h_length);
+ server.sin_port = htons((unsigned short)portno);
+
+ // try to connect
+ if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) == SOCKET_ERROR) {
+ closesocket(sockfd);
+ throw "Connection failed!";
+ }
+
+ // check if we can read from the socket
+ {
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(sockfd,&fdset);
+
+ timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 500;
+
+ int ret = select((int)sockfd + 1, &fdset, NULL, NULL, &tv);
+ if(ret < 0) {
+ closesocket(sockfd);
+ throw "Can not read from socket";
+ }
+ }
+
+ // build up stuff we need to send to server
+ sprintf(request, "GET /%s HTTP/1.0 \r\nHost: %s\r\nUser-Agent: 0.2\r\nAccept: audio/x-ogg\r\n\r\n", mountpoint, hostname);
+
+ // try to contact server
+ if(send(sockfd, request, (int)strlen(request), 0) == SOCKET_ERROR) {
+ closesocket(sockfd);
+ throw "Could not contact server";
+ }
+
+ // read first line of response
+ int i = 0;
+ while(i < STRBUF_SIZE-1) {
+ if( CheckForData(sockfd) ) {
+ if(recv(sockfd, request+i, 1, 0) <= 0) {
+ closesocket(sockfd);
+ throw "Could not read from socket, quitting";
+ }
+
+ if(request[i] == '\n') break;
+ if(request[i] != '\r') i++;
+ }
+ }
+ request[i] = '\0';
+
+ bool eof = false;
+
+ // parse content of the response...
+ if(strstr(request, "HTTP/1.0 200 OK")) /* server is ready */
+ {
+ //post(" : IceCast2 server detected");
+
+ while(!eof) {
+ i = 0;
+ while(i < STRBUF_SIZE-1) {
+ if( CheckForData(sockfd) ) {
+ if(recv(sockfd, request + i, 1, 0) <= 0) {
+ closesocket(sockfd);
+ throw "Could not read from socket, quitting";
+ }
+
+ 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++;
+ }
+ }
+
+ // make it a null terminated string
+ request[i] = '\0';
+
+ char *sptr;
+ if( ( sptr = strstr(request, "application/x-ogg") ) ) {
+ /* check for content type */
+ //post(" : Ogg Vorbis stream found");
+ }
+
+ if( ( sptr = strstr(request, "ice-name:") ) ) {
+ /* display ice-name */
+ //post(" : \"%s\"", sptr + 10);
+ }
+
+ if(i == 0)
+ // we got last '\r\n' from server
+ eof = true;
+ }
+ }
+ else if(strstr(request, "HTTP/1.0 404")) {
+ // file not found
+ closesocket(sockfd);
+ throw "File not found";
+ }
+ else {
+ // wrong server or wrong answer
+ closesocket(sockfd);
+ throw request;
+ }
+
+ //post(" : connected to http://%s:%d/%s", hp->h_name, portno, mountpoint);
+
+ return sockfd;
+}
+
+
+void Stream::Disconnect(SOCKET fd)
+{
+ if(fd != INVALID_SOCKET) {
+ closesocket(fd);
+ post("connection closed");
+ }
+}
+
+int Stream::Read(SOCKET fd,char *buf,int size,int timeout)
+{
+ // check for data availability so that recv doesn't block
+ if(CheckForData(fd,timeout)) {
+ int ret = recv(fd,buf,size, 0);
+ if(ret == SOCKET_ERROR) {
+ post("socket error!");
+ return -1;
+ }
+ else
+ return ret;
+ }
+ else
+ return -1;
+}
diff --git a/socket.h b/socket.h
new file mode 100644
index 0000000..ccdb60c
--- /dev/null
+++ b/socket.h
@@ -0,0 +1,21 @@
+#ifndef __SOCKET_H
+#define __SOCKET_H
+
+#ifdef _WIN32
+ #include <io.h>
+ #include <winsock.h>
+#else
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+ #include <netinet/tcp.h>
+ #include <arpa/inet.h>
+ #include <netdb.h>
+ #include <unistd.h>
+
+ #define closesocket close
+ #define SOCKET int
+ #define INVALID_SOCKET -1
+ #define SOCKET_ERROR -1
+#endif
+
+#endif
diff --git a/stream.cpp b/stream.cpp
new file mode 100644
index 0000000..e684443
--- /dev/null
+++ b/stream.cpp
@@ -0,0 +1,557 @@
+/*************************************************************
+ *
+ * streaming external for PD
+ *
+ * File: stream.cpp
+ *
+ * Description: Implementation of the streamer class
+ *
+ * Author: Thomas Grill (t.grill@gmx.net)
+ *
+ *************************************************************/
+
+#ifdef _WIN32
+ #include <windows.h>
+#else
+ #include <unistd.h>
+ #include <ctype.h>
+ #define Sleep(ms) usleep((ms)*1000)
+#endif
+
+#include "stream.h"
+
+
+// default size of encoded data fifo
+#define ENCSIZE 10000
+// default size for encoding chunk
+#define ENCCHUNK 500
+// default ratio for fifo filling
+#define ENCTHRESH 0.95f
+
+// additional buffer frames for resampling algorithm
+#define DECMORE 100
+
+
+// relative thread priority (-2...0)
+#define THRPRIOR -1
+
+// default time grain to wait on error (ms)
+#define WAITGRAIN 100
+// default time until reconnecting (ms)
+#define WAITRECONNECT 3000
+
+
+// explicit definition of report functions
+extern "C" {
+ extern void post(char *fmt, ...);
+ extern void error(char *fmt, ...);
+}
+
+Stream::Stream():
+ encoded(ENCSIZE),encchunk(ENCCHUNK),encthresh(ENCTHRESH),
+ waitgrain(WAITGRAIN),waitreconnect(WAITRECONNECT), //waitthread(WAITTHREAD),
+ file(-1),
+ exit(false),state(ST_IDLE),debug(false),
+ bufch(0),bufs(NULL),decoded(NULL),
+ src_channels(0),src_factor(1),src_state(NULL)
+{
+ pthread_mutex_init(&mutex,NULL);
+ pthread_cond_init(&cond,NULL);
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
+ pthread_create(&thrid,&attr,thr_func,this);
+ pthread_attr_destroy(&attr);
+}
+
+Stream::~Stream()
+{
+ // cause thread to exit
+ exit = true;
+ pthread_cond_signal(&cond);
+ if(pthread_join(thrid,NULL) != 0) post("join failed");
+
+ pthread_cond_destroy(&cond);
+ pthread_mutex_destroy(&mutex);
+
+ Reset();
+
+ if(bufs) delete[] bufs;
+ if(decoded) delete[] decoded;
+ if(src_state) {
+ for(int i = 0; i < bufch; ++i) src_delete(src_state[i]);
+ delete[] src_state;
+ }
+}
+
+void Stream::Reset()
+{
+ if(file >= 0) { Disconnect(file); file = -1; }
+ encoded.Clear();
+
+ if(src_state) {
+ for(int i = 0; i < bufch; ++i) {
+ src_reset(src_state[i]);
+ decoded[i].Clear();
+ }
+ }
+}
+
+void Stream::ResetHost()
+{
+ hostname = mountpt = ""; port = -1;
+}
+
+bool Stream::doInit(const char *url)
+{
+ if(isInitializing()) {
+ // this is not completely clean (should be within the lock) but otherwise the
+ // caller would have to wait for the thread lock
+
+ post("Still initializing %s/%s:%i",hostname.c_str(),mountpt.c_str(),port);
+ return false;
+ }
+
+ bool ok = true;
+
+ pthread_mutex_lock(&mutex);
+
+ // close open file
+ Reset();
+
+ // try to set host name, mount point, port number
+ if(ok) {
+ char *err = "Invalid URL";
+ try { ok = SetURL(url); }
+ catch(char *tx) { err = tx; ok = false; }
+ catch(...) { ok = false; }
+
+ if(!ok) { post(err); ResetHost(); }
+ }
+
+ if(ok) {
+ state = ST_INIT;
+ pthread_cond_signal(&cond);
+ }
+
+ pthread_mutex_unlock(&mutex);
+
+ // let the thread worker do the rest
+
+ return ok;
+}
+
+bool Stream::doExit()
+{
+ pthread_mutex_lock(&mutex);
+
+ Reset();
+ ResetHost();
+ state = ST_IDLE;
+
+ pthread_mutex_unlock(&mutex);
+
+ return true;
+}
+
+/*! Get sample frames
+ \param buf pointer array to channel buffers
+ \param frames number of maximum frames to get
+ \return frames put into buffers
+*/
+int Stream::doGet(int ch,float *const *buf,int frames,float sr)
+{
+ ASSERT(ch > 0 && frames >= 0 && sr > 0);
+
+ if(isOk() && !isInitializing()) {
+ // signal thread worker
+ pthread_cond_signal(&cond);
+
+ // check/(re)allocate buffers
+
+ int strch = getChannels();
+ if(bufs && bufch < strch) {
+ delete[] decoded;
+ for(int i = 0; i < bufch; ++i) src_delete(src_state[i]);
+ delete[] src_state;
+ delete[] bufs; bufs = NULL;
+ }
+
+ if(!bufs) {
+ if(bufch < strch) bufch = strch;
+ bufs = new float *[bufch];
+ decoded = new Fifo<float>[bufch];
+
+ src_state = new SRC_STATE *[bufch];
+ for(int i = 0; i < bufch; ++i) {
+ int error;
+ src_state[i] = src_new(SRC_ZERO_ORDER_HOLD,1,&error);
+ if(!src_state[i]) post("src init error %i",error);
+ }
+ }
+
+ // get frames
+
+ float ratio = sr/getSamplerate();
+ int frneed = (int)(frames/ratio)+DECMORE; // number of frames to read from decoder fifo
+
+ if(decoded[0].Size() < frneed) {
+ // fifos are too small -> resize them (while keeping their contents)
+ for(int i = 0; i < bufch; ++i) decoded[i].Resize(frneed,true);
+ }
+
+ // how many frames do we need to get from decoder?
+ int frread = frneed-decoded[0].Have();
+
+ int ret = state == ST_WAIT?0:DataRead(frread);
+
+ if(ret < 0) {
+ if(debug) post("read error");
+ // clear output
+ for(int c = 0; c < ch; ++c)
+ memset(buf[c],0,frames*sizeof *buf[c]);
+ return 0;
+ }
+ else {
+ // how many channels do we really need for output?
+ // this should be set elsewhere, because we can't change anyway!!!
+ // (SRC_STATE for dangling channels would be incorrect)
+ int cmin = strch;
+ if(ch < cmin) cmin = ch;
+
+ // write data to fifo
+ for(int i = 0; i < cmin; ++i) {
+ int wr = decoded[i].Write(ret,bufs[i]);
+ if(wr < ret) post("fifo overflow");
+ }
+
+// state = ST_PROCESS;
+
+ if(ratio == 1) {
+ // no resampling necessary
+
+ // hopefully all channel fifos advance uniformly.....
+ for(int i = 0; i < cmin; ++i) {
+
+ for(int got = 0; got < frames; ) {
+ int cnt = frames-got;
+
+ if(decoded[i].Have()) {
+ got += decoded[i].Read(cnt,buf[i]+got);
+ }
+ else {
+ state = ST_WAIT;
+ if(debug) post("fifo underrun");
+
+ // Buffer underrun!! -> zero output buffer
+ memset(buf[i]+got,0,cnt*sizeof(*buf[i]));
+ got += cnt;
+ }
+ }
+ }
+ }
+ else
+ {
+ SRC_DATA src_data;
+ src_data.src_ratio = ratio;
+ src_data.end_of_input = 0;
+
+ // hopefully all channel fifos advance uniformly.....
+ for(int i = 0; i < cmin; ++i) {
+ src_set_ratio(src_state[i],ratio);
+
+ for(int got = 0; got < frames; ) {
+ src_data.data_out = buf[i]+got;
+ src_data.output_frames = frames-got;
+
+ if(decoded[i].Have()) {
+ src_data.data_in = decoded[i].ReadPtr();
+ src_data.input_frames = decoded[i].ReadSamples();
+
+ int err = src_process(src_state[i],&src_data);
+ if(err) post("src_process error %i",err);
+
+ // advance buffer
+ decoded[i].Read(src_data.input_frames_used,NULL);
+ }
+ else {
+ state = ST_WAIT;
+ if(debug) post("fifo underrun");
+
+ // Buffer underrun!! -> zero output buffer
+ memset(src_data.data_out,0,src_data.output_frames*sizeof(*src_data.data_out));
+ src_data.output_frames_gen = src_data.output_frames;
+ }
+ got += src_data.output_frames_gen;
+ }
+ }
+ }
+
+ // zero remaining channels
+ for(int c = cmin; c < ch; ++c)
+ memset(buf[c],0,frames*sizeof *buf[c]);
+
+ return ret;
+ }
+ }
+ else {
+ for(int c = 0; c < ch; ++c)
+ memset(buf[c],0,frames*sizeof *buf[c]);
+ return 0;
+ }
+}
+
+#define MAXZEROES 5
+
+/*!
+ \param chunk amount of data to read
+ \param unlock unlock mutex
+*/
+int Stream::ReadChunk(int chunk,bool unlock)
+{
+ if(chunk <= 0) return 0;
+
+ bool ok = true;
+ char tmp[1024];
+ int n = 0,errcnt = 0;
+ while(ok) {
+ int c = chunk-n;
+ if(c <= 0) break; // read enough data
+ if(c > sizeof tmp) c = sizeof tmp;
+ SOCKET fd = file;
+
+ if(unlock) pthread_mutex_unlock(&mutex);
+
+ int ret = Read(fd, tmp, c);
+
+ if(unlock) pthread_mutex_lock(&mutex);
+
+ if(ret < 0 || (!ret && ++errcnt == MAXZEROES)) {
+ if(debug) post("Receive error");
+ ok = false;
+ }
+ else if(ret > 0) {
+ if(debug) post("read %i bytes",ret);
+ errcnt = 0;
+ encoded.Write(ret,tmp);
+ n += ret;
+ }
+ }
+ return n;
+}
+
+/*!
+ \param buf data buffer
+ \param chunk amount of data to read
+ \param unlock unlock mutex
+*/
+int Stream::ReadChunk(char *buf,int chunk,bool unlock)
+{
+ if(chunk <= 0) return 0;
+
+ bool ok = true;
+ int n = 0,errcnt = 0;
+ while(ok) {
+ int c = chunk-n;
+ if(c <= 0) break; // read enough data
+ SOCKET fd = file;
+
+ if(unlock) pthread_mutex_unlock(&mutex);
+
+ int ret = Read(fd, buf+n, c);
+
+ if(unlock) pthread_mutex_lock(&mutex);
+
+ if(ret < 0 || (!ret && ++errcnt == MAXZEROES)) {
+ if(debug) post("Receive error");
+ ok = false;
+ }
+ else if(ret > 0) {
+ if(debug) post("read %i bytes",ret);
+ errcnt = 0;
+ n += ret;
+ }
+ }
+ return n;
+}
+
+#define MAXINITTRIES 5
+
+/*! static pthreads thread function */
+void *Stream::thr_func(void *th)
+{
+ ((Stream *)th)->Work();
+ return NULL;
+}
+
+/*! Thread worker - fill the fifo with socket data */
+void Stream::Work()
+{
+ int waittime = 0;
+
+ // lower thread priority
+ {
+ struct sched_param parm;
+ int policy;
+ if(pthread_getschedparam(pthread_self(),&policy,&parm) >= 0) {
+ int minprio = sched_get_priority_min(policy);
+
+ if(debug) post("priority was %i (min = %i)",parm.sched_priority,minprio);
+
+ parm.sched_priority += THRPRIOR;
+
+ if(parm.sched_priority < minprio) parm.sched_priority = minprio;
+ pthread_setschedparam(pthread_self(),policy,&parm);
+ }
+
+ if(pthread_getschedparam(pthread_self(),&policy,&parm) >= 0) {
+ if(debug) post("priority set to %i",parm.sched_priority);
+ }
+ }
+
+ while(!exit) {
+ pthread_mutex_lock(&mutex);
+
+ bool wait = true;
+
+ if(!hostname.length() || !mountpt.length() || port < 0) {}
+ else
+ if(state == ST_INIT || state == ST_RECONNECT) {
+ // initialize!
+
+ bool ok = true;
+
+ try {
+ file = Connect( hostname.c_str(),mountpt.c_str(),port);
+ }
+ catch(char *str) {
+ if(state != ST_RECONNECT) post(str);
+ ok = false;
+ }
+ catch(...) {
+ post("Unknown error while connecting");
+ ok = false;
+ }
+
+ // initialize decoder
+ if(ok) ok = WorkInit();
+
+ // try to fill buffer
+ if(ok) {
+ int i,lim = (int)(encoded.Size()*encthresh);
+ for(i = MAXINITTRIES; i > 0 && encoded.Have() < lim; ) {
+ int n = ReadChunk(encoded.Free(),true);
+ if(!n) --i;
+ }
+ if(!i) ok = false;
+ }
+
+ if(!ok) {
+ Reset();
+
+ if(state == ST_INIT) state = ST_IDLE;
+ // if reconnecting keep on doing that...
+ }
+ else {
+ state = ST_PROCESS;
+ waittime = 0;
+ }
+ }
+ else if(isOk()) {
+ SOCKET fd = file;
+ int chunk = encoded.Free();
+ if(chunk > encchunk) chunk = encchunk;
+
+ if(chunk) {
+ int n = ReadChunk(chunk,true);
+
+ if(n == 0) {
+ if(debug) post("error receiving data");
+ state = ST_WAIT;
+ }
+ else
+ // reset error state
+ state = ST_PROCESS;
+ }
+
+ if(encoded.Have() < encoded.Size()*encthresh)
+ // immediately get the next chunk
+ wait = false;
+ }
+
+ if(debug && encoded.Free()) {
+ post("fifo: sz/fill = %5i/%3.0f%%",encoded.Size(),(float)encoded.Have()/encoded.Size()*100);
+ }
+
+ if(state == ST_WAIT) {
+ if(debug) post("Wait for data");
+ Sleep(waitgrain);
+ waittime += waitgrain;
+ if(waittime > waitreconnect) {
+ if(debug) post("do reconnect");
+ state = ST_RECONNECT;
+ }
+ wait = false;
+ }
+ else if(state == ST_RECONNECT) {
+ if(debug) post("Reconnecting again");
+ Sleep(waitgrain);
+ wait = false;
+ }
+
+
+ if(wait) pthread_cond_wait(&cond,&mutex);
+
+ pthread_mutex_unlock(&mutex);
+ }
+
+ state = ST_FINISHED;
+}
+
+bool Stream::SetURL(const char *url)
+{
+ char *p = (char *)url;
+
+ // strip prefixes
+ if(!strncmp(p, "http://", 7)) p += 7;
+ if(!strncmp(p, "ftp://", 6)) p += 6;
+
+ char *hostptr = p; // points to host name
+
+ char *pathptr = strchr(hostptr,'/');
+ if(pathptr)
+ // / found -> skip /
+ ++pathptr;
+ else
+ // no / found!! ILLEGAL
+ throw "URL path not found";
+
+ // get port number
+ int portno;
+ char *portptr = strchr(hostptr,':');
+ if(portptr && portptr < pathptr) {
+ portptr++;
+ int sl = (int)(pathptr-portptr-1);
+ char *p0 = new char[sl+1];
+ ASSERT(p0);
+ strncpy(p0,portptr,sl);
+ p0[sl] = 0;
+
+ for(p = p0; *p && isdigit(*p); p++) ;
+ *p = 0;
+
+ // convert port from string to int
+ portno = (int)strtol(p0, NULL, 10);
+ delete[] p0;
+ }
+ else
+ portno = 8000;
+
+ // assign found things to function parameters
+ hostname = std::string(hostptr,(portptr?portptr:pathptr)-1-hostptr);
+ mountpt = pathptr;
+ port = portno;
+
+ return true;
+}
diff --git a/stream.dsp b/stream.dsp
new file mode 100644
index 0000000..4f4e4f6
--- /dev/null
+++ b/stream.dsp
@@ -0,0 +1,134 @@
+# Microsoft Developer Studio Project File - Name="stream" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** NICHT BEARBEITEN **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=stream - Win32 Debug
+!MESSAGE Dies ist kein gültiges Makefile. Zum Erstellen dieses Projekts mit NMAKE
+!MESSAGE verwenden Sie den Befehl "Makefile exportieren" und führen Sie den Befehl
+!MESSAGE
+!MESSAGE NMAKE /f "stream.mak".
+!MESSAGE
+!MESSAGE Sie können beim Ausführen von NMAKE eine Konfiguration angeben
+!MESSAGE durch Definieren des Makros CFG in der Befehlszeile. Zum Beispiel:
+!MESSAGE
+!MESSAGE NMAKE /f "stream.mak" CFG="stream - Win32 Debug"
+!MESSAGE
+!MESSAGE Für die Konfiguration stehen zur Auswahl:
+!MESSAGE
+!MESSAGE "stream - Win32 Release" (basierend auf "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "stream - Win32 Debug" (basierend auf "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "stream - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0xc07 /d "NDEBUG"
+# ADD RSC /l 0xc07 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+
+!ELSEIF "$(CFG)" == "stream - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "d:\pd\src" /I "D:\IAEM\oggvorbis-win32sdk-1.0.1\include" /I "D:\IAEM\libsamplerate-0.1.2\src" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0xc07 /d "_DEBUG"
+# ADD RSC /l 0xc07 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 pd.lib pthreadVC.lib Ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib libsamplerate.lib ogg.lib vorbis.lib vorbisfile.lib /nologo /dll /debug /machine:I386 /pdbtype:sept /libpath:"d:\pd\bin" /libpath:"D:\IAEM\oggvorbis-win32sdk-1.0.1\lib" /libpath:"D:\IAEM\libsamplerate-0.1.2"
+
+!ENDIF
+
+# Begin Target
+
+# Name "stream - Win32 Release"
+# Name "stream - Win32 Debug"
+# Begin Group "Quellcodedateien"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\main.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\socket.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\stream.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\streamogg.cpp
+# End Source File
+# End Group
+# Begin Group "Header-Dateien"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\fifo.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\socket.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\stream.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\streamogg.h
+# End Source File
+# End Group
+# Begin Group "Ressourcendateien"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/stream.dsw b/stream.dsw
new file mode 100644
index 0000000..78fd028
--- /dev/null
+++ b/stream.dsw
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNUNG: DIESE ARBEITSBEREICHSDATEI DARF NICHT BEARBEITET ODER GELÖSCHT WERDEN!
+
+###############################################################################
+
+Project: "stream"=.\stream.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/stream.h b/stream.h
new file mode 100644
index 0000000..b55c8ed
--- /dev/null
+++ b/stream.h
@@ -0,0 +1,160 @@
+/*************************************************************
+ *
+ * streaming external for PD
+ *
+ * File: stream.h
+ *
+ * Description: Declaration of the streamer class
+ *
+ * Author: Thomas Grill (t.grill@gmx.net)
+ *
+ *************************************************************/
+
+#ifndef __STREAM_H
+#define __STREAM_H
+
+#include <pthread.h>
+
+#include <assert.h>
+#define ASSERT assert
+
+#include <string>
+
+#include <samplerate.h>
+
+#include "fifo.h"
+#include "socket.h"
+
+
+/*! Class representing an abstract stream.
+ \note Must be inherited for a special stream format
+*/
+class Stream
+{
+public:
+ Stream();
+ virtual ~Stream();
+
+ //! Return true if in initializing state
+ bool isInitializing() const { return state == ST_INIT || state == ST_RECONNECT; }
+ //! Return true if socket is valid
+ bool isOk() const { return file >= 0; }
+
+ // Get/set size of encoder FIFO
+ int getStreamBufSize() const { return encoded.Size(); }
+ bool setStreamBufSize(int sz) { return encoded.Resize(sz,true); }
+
+ // Get/set size of encoder chunk (amount of data written from the stream at once)
+ int getStreamBufChunk() const { return encchunk; }
+ void setStreamBufChunk(int sz) { if(encchunk > 0) encchunk = sz; }
+
+ // Get/set buffer size ratio under which refilling is triggered (0...1)
+ float getStreamBufThresh() const { return encthresh; }
+ void setStreamBufThresh(float r) { if(encthresh > 0 && encthresh <= 1) encthresh = r; }
+
+ //! Get ratio of filling (0...1)
+ float getFilling() const { return (float)encoded.Have()/encoded.Size(); }
+
+ //! name of stream
+ virtual std::string getTag(const char *tag) const { return ""; }
+ //! number of stream channels
+ virtual int getChannels() const = 0;
+ //! stream sample rate
+ virtual float getSamplerate() const = 0;
+ //! nominal stream bit rate
+ virtual float getBitrate() const = 0;
+
+ const std::string &getHostname() const { return hostname; }
+ const std::string &getMountpoint() const { return mountpt; }
+ int getPort() const { return port; }
+
+ /*! Initialize stream */
+ bool doInit(const char *url);
+
+ /*! Disconnect from stream */
+ bool doExit();
+
+ //! Get a number of sample frames
+ int doGet(int ch,float *const *buf,int frames,float sr);
+
+ // Debug flag
+ volatile bool debug;
+
+protected:
+
+ //! Reset encoder state (disconnect and clear FIFOs)
+ virtual void Reset();
+ //! Reset URL
+ void ResetHost();
+
+ //! Init decoder
+ virtual bool WorkInit() = 0;
+ //! Decode data to channel buffers
+ virtual int DataRead(int frames) = 0;
+
+ //! Read stream data to encoder FIFO
+ int ReadChunk(int chunk,bool unlock);
+ //! Read stream data to buffer
+ int ReadChunk(char *buf,int chunk,bool unlock);
+
+ //! Set hostname, mountpt, port
+ bool SetURL(const char *url);
+
+ std::string hostname,mountpt;
+ int port;
+
+ // --- FIFO for encoded stream data -----------------------
+
+ int encchunk; //! Size of data chunk to get from socket at once
+ float encthresh; //! Ratio of fifo filling to keep up to
+ Fifo<char> encoded; //! Fifo for encoded stream data
+
+ // --- low-level socket stuff ------------------------------
+
+ //! stream socket
+ volatile SOCKET file;
+
+ //! Connect to stream
+ static SOCKET Connect(const char *hostname,const char *mountpoint,int portno);
+ //! Disonnect from stream
+ static void Disconnect(SOCKET fd);
+ //! Read data from stream
+ static int Read(SOCKET fd,char *buf,int size,int timeout = 1000);
+
+ // --- threading stuff -------------------------------------
+
+ //! status type
+ enum state_t {
+ ST_IDLE, // nothing to do
+ ST_INIT, // shall connect
+ ST_PROCESS, // do decoding
+ ST_WAIT, // wait a bit
+ ST_RECONNECT, // try to reconnect
+ ST_FINISHED // waiting for shutdown
+ };
+
+ volatile bool exit; //! exit flag
+ volatile state_t state; //! decoder state
+ pthread_mutex_t mutex; //! thread mutex
+ pthread_cond_t cond; //! thread conditional
+ pthread_t thrid; //! worker thread ID
+
+ int waitgrain,waitreconnect;
+
+ static void *thr_func(void *th);
+ void Work();
+
+ // --- channel buffers --------------------------------------
+
+ int bufch;
+ float **bufs;
+ Fifo<float> *decoded;
+
+ // --- SRC stuff --------------------------------------------
+
+ int src_channels;
+ double src_factor;
+ SRC_STATE **src_state;
+};
+
+#endif // __STREAM_H
diff --git a/stream.vcproj b/stream.vcproj
new file mode 100644
index 0000000..cc5fd30
--- /dev/null
+++ b/stream.vcproj
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="7.10"
+ Name="stream"
+ ProjectGUID="{993DDC78-8370-4E3C-9AE6-EFCF8BA92FD7}"
+ Keyword="Win32Proj">
+ <Platforms>
+ <Platform
+ Name="Win32"/>
+ </Platforms>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="Debug"
+ IntermediateDirectory="Debug"
+ ConfigurationType="2"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="&quot;f:\prog\pd\pd-cvs\src&quot;;f:\prog\packs\pthreads;F:\prog\audio\libsamplerate\src;F:\prog\audio\xiph\ogg\include;F:\prog\audio\xiph\vorbis\include"
+ PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;READ_VORBIS_URL"
+ MinimalRebuild="TRUE"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="TRUE"
+ DebugInformationFormat="4"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="pthreadVC.lib wsock32.lib"
+ OutputFile="$(OutDir)/stream.dll"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories="f:\prog\packs\pthreads"
+ GenerateDebugInformation="TRUE"
+ ProgramDatabaseFile="$(OutDir)/stream.pdb"
+ SubSystem="2"
+ ImportLibrary="$(OutDir)/stream.lib"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="Release"
+ IntermediateDirectory="Release"
+ ConfigurationType="2"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="3"
+ GlobalOptimizations="TRUE"
+ AdditionalIncludeDirectories="&quot;f:\prog\pd\pd-cvs\src&quot;;f:\prog\packs\pthreads;F:\prog\audio\libsamplerate\src;F:\prog\audio\xiph\ogg\include;F:\prog\audio\xiph\vorbis\include"
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;undefined_EXPORTS"
+ RuntimeLibrary="0"
+ EnableEnhancedInstructionSet="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="TRUE"
+ DebugInformationFormat="0"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="pthreadVC.lib wsock32.lib"
+ OutputFile="$(OutDir)/stream.dll"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories="f:\prog\packs\pthreads"
+ GenerateDebugInformation="FALSE"
+ SubSystem="2"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ ImportLibrary="$(OutDir)/stream.lib"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <File
+ RelativePath=".\fifo.h">
+ </File>
+ <File
+ RelativePath=".\main.cpp">
+ </File>
+ <File
+ RelativePath=".\readme.txt">
+ </File>
+ <File
+ RelativePath=".\socket.cpp">
+ </File>
+ <File
+ RelativePath=".\socket.h">
+ </File>
+ <File
+ RelativePath=".\stream.cpp">
+ </File>
+ <File
+ RelativePath=".\stream.h">
+ </File>
+ <File
+ RelativePath=".\streamogg.cpp">
+ </File>
+ <File
+ RelativePath=".\streamogg.h">
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/streamogg.cpp b/streamogg.cpp
new file mode 100644
index 0000000..947753b
--- /dev/null
+++ b/streamogg.cpp
@@ -0,0 +1,113 @@
+/*************************************************************
+ *
+ * streaming external for PD
+ *
+ * File: streamogg.cpp
+ *
+ * Description: Implementation of the streamer class for OGG/vorbis
+ *
+ * Author: Thomas Grill (t.grill@gmx.net)
+ *
+ *************************************************************/
+
+#include "streamogg.h"
+
+
+// explicit definition of report functions
+extern "C" {
+ extern void post(char *fmt, ...);
+ extern void error(char *fmt, ...);
+}
+
+
+StreamOGG::StreamOGG():
+ ov_inf(NULL),ov_comm(NULL)
+{
+ // set OGG callbacks
+ callbacks.read_func = read_func;
+ callbacks.seek_func = seek_func;
+ callbacks.close_func = close_func;
+ callbacks.tell_func = tell_func;
+}
+
+void StreamOGG::Reset()
+{
+ Stream::Reset();
+ ov_inf = NULL;
+ ov_comm = NULL;
+}
+
+std::string StreamOGG::getTag(const char *tag) const
+{
+ const char *c = NULL;
+ if(ov_comm) c = vorbis_comment_query(ov_comm,const_cast<char *>(tag),0);
+ return c?c:"";
+}
+
+
+#define MAXINITTRIES 5
+
+bool StreamOGG::WorkInit()
+{
+ bool ok = true;
+
+ // read in enough data to represent the OGG header
+ char hdrbuf[8500];
+ int i,need = sizeof hdrbuf;
+ for(i = MAXINITTRIES; i > 0 && need > 0; ) {
+ int n = ReadChunk(hdrbuf,need,true);
+ if(n)
+ need -= n;
+ else {
+ --i;
+ if(debug) post("Try to init again (%i)....",i);
+ }
+ }
+ if(!i) ok = false;
+
+ // got n bytes into fifo
+
+ if(ok && ov_open_callbacks(this,&ov_file,hdrbuf,sizeof hdrbuf,callbacks) < 0 ) {
+ post("Header format error");
+ ok = false;
+ }
+ else {
+ ov_inf = ov_info(&ov_file,-1);
+ ov_comm = ov_comment(&ov_file,-1);
+ }
+
+ return ok;
+}
+
+int StreamOGG::DataRead(int frames)
+{
+ int current_section;
+ return ov_read_float(&ov_file,&bufs,frames,&current_section);
+}
+
+size_t StreamOGG::read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
+{
+ StreamOGG *str = (StreamOGG *)datasource;
+
+ if(!str->isInitializing()) pthread_mutex_lock(&str->mutex);
+
+ int ret = str->encoded.Read(size*nmemb,(char *)ptr);
+
+ if(str->debug) post("read %i of %i bytes from fifo",ret,size*nmemb);
+
+ if(!str->isInitializing()) pthread_mutex_unlock(&str->mutex);
+
+ return ret;
+}
+
+int StreamOGG::close_func(void *datasource)
+{
+ post("Closing stream");
+
+ StreamOGG *str = (StreamOGG *)datasource;
+ pthread_mutex_lock(&str->mutex);
+ str->Reset();
+ pthread_mutex_unlock(&str->mutex);
+ return 0;
+}
+
diff --git a/streamogg.h b/streamogg.h
new file mode 100644
index 0000000..568e9c2
--- /dev/null
+++ b/streamogg.h
@@ -0,0 +1,64 @@
+/*************************************************************
+ *
+ * streaming external for PD
+ *
+ * File: streamogg.h
+ *
+ * Description: Declaration of the streamer class for OGG/vorbis
+ *
+ * Author: Thomas Grill (t.grill@gmx.net)
+ *
+ *************************************************************/
+
+#ifndef __STREAMOGG_H
+#define __STREAMOGG_H
+
+#include "stream.h"
+
+extern "C" {
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+}
+
+
+//! Class representing an OGG stream.
+class StreamOGG:
+ public Stream
+{
+public:
+ StreamOGG();
+
+ //! Get named tag of stream
+ virtual std::string getTag(const char *tag) const;
+
+ //! Get number of stream channels
+ virtual int getChannels() const { return ov_inf?ov_inf->channels:0; }
+ //! Get stream sample rate
+ virtual float getSamplerate() const { return ov_inf?(float)ov_inf->rate:0; }
+ //! Get nominal stream bit rate
+ virtual float getBitrate() const { return ov_inf?(float)ov_inf->bitrate_nominal:0; }
+
+protected:
+
+ //! Reset encoder state (disconnect and clear FIFOs)
+ virtual void Reset();
+
+ //! Init decoder
+ virtual bool WorkInit();
+ //! Decode data to channel buffers
+ virtual int DataRead(int frames);
+
+ // OGG/Vorbis data
+ OggVorbis_File ov_file;
+ ov_callbacks callbacks;
+ vorbis_info *ov_inf;
+ vorbis_comment *ov_comm;
+
+ // OGG callbacks
+ static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource);
+ static int close_func(void *datasource);
+ static int seek_func(void *datasource, ogg_int64_t offset, int whence) { return -1; }
+ static long tell_func(void *datasource) { return -1; }
+};
+
+#endif // __STREAMOGG_H